Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | /* |
2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
3 | * described in COPYING.txt. |
||
4 | * Portions of this file are copyright Parallax Software and licensed |
||
5 | * according to the Parallax license below. |
||
6 | * See COPYING.txt for license details. |
||
7 | |||
8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
18 | */ |
||
19 | |||
20 | /* |
||
21 | * |
||
22 | * object rendering |
||
23 | * |
||
24 | */ |
||
25 | |||
26 | #include <algorithm> |
||
27 | #include <cstdlib> |
||
28 | #include <stdio.h> |
||
29 | |||
30 | #include "inferno.h" |
||
31 | #include "game.h" |
||
32 | #include "gr.h" |
||
33 | #include "bm.h" |
||
34 | #include "3d.h" |
||
35 | #include "segment.h" |
||
36 | #include "texmap.h" |
||
37 | #include "laser.h" |
||
38 | #include "key.h" |
||
39 | #include "gameseg.h" |
||
40 | #include "textures.h" |
||
41 | #include "object.h" |
||
42 | #include "controls.h" |
||
43 | #include "physics.h" |
||
44 | #include "slew.h" |
||
45 | #include "render.h" |
||
46 | #include "wall.h" |
||
47 | #include "vclip.h" |
||
48 | #include "robot.h" |
||
49 | #include "interp.h" |
||
50 | #include "fireball.h" |
||
51 | #include "laser.h" |
||
52 | #include "dxxerror.h" |
||
53 | #include "ai.h" |
||
54 | #include "hostage.h" |
||
55 | #include "morph.h" |
||
56 | #include "cntrlcen.h" |
||
57 | #include "powerup.h" |
||
58 | #include "fuelcen.h" |
||
59 | #include "endlevel.h" |
||
60 | #include "hudmsg.h" |
||
61 | #include "sounds.h" |
||
62 | #include "collide.h" |
||
63 | #include "lighting.h" |
||
64 | #include "newdemo.h" |
||
65 | #include "player.h" |
||
66 | #include "weapon.h" |
||
67 | #include "newmenu.h" |
||
68 | #include "gauges.h" |
||
69 | #include "multi.h" |
||
70 | #include "menu.h" |
||
71 | #include "args.h" |
||
72 | #include "text.h" |
||
73 | #include "piggy.h" |
||
74 | #include "switch.h" |
||
75 | #include "gameseq.h" |
||
76 | #include "playsave.h" |
||
77 | #include "timer.h" |
||
78 | #if DXX_USE_EDITOR |
||
79 | #include "editor/editor.h" |
||
80 | #endif |
||
81 | |||
82 | #include "compiler-range_for.h" |
||
83 | #include "d_range.h" |
||
84 | #include "partial_range.h" |
||
85 | #include <utility> |
||
86 | |||
87 | using std::min; |
||
88 | using std::max; |
||
89 | |||
90 | namespace dsx { |
||
91 | static void obj_detach_all(object_array &Objects, object_base &parent); |
||
92 | static void obj_detach_one(object_array &Objects, object &sub); |
||
93 | |||
94 | static int is_proximity_bomb_or_any_smart_mine(const weapon_id_type id) |
||
95 | { |
||
96 | const auto r = is_proximity_bomb_or_player_smart_mine(id); |
||
97 | #if defined(DXX_BUILD_DESCENT_II) |
||
98 | if (r) |
||
99 | return r; |
||
100 | // superprox dropped by robots have their own ID not considered by is_proximity_bomb_or_player_smart_mine() and since that function is used in many other places, I didn't feel safe to add this weapon type in it |
||
101 | if (id == weapon_id_type::ROBOT_SUPERPROX_ID) |
||
102 | return 1; |
||
103 | #endif |
||
104 | return r; |
||
105 | } |
||
106 | |||
107 | /* |
||
108 | * Global variables |
||
109 | */ |
||
110 | |||
111 | object *ConsoleObject; //the object that is the player |
||
112 | } |
||
113 | |||
114 | namespace dcx { |
||
115 | |||
116 | //Data for objects |
||
117 | |||
118 | // -- Object stuff |
||
119 | |||
120 | //info on the various types of objects |
||
121 | } |
||
122 | |||
123 | namespace dsx { |
||
124 | #ifndef RELEASE |
||
125 | //set viewer object to next object in array |
||
126 | void object_goto_next_viewer() |
||
127 | { |
||
128 | auto &Objects = LevelUniqueObjectState.Objects; |
||
129 | auto &vcobjptr = Objects.vcptr; |
||
130 | auto &vcobjptridx = Objects.vcptridx; |
||
131 | auto &vmobjptr = Objects.vmptr; |
||
132 | objnum_t start_obj; |
||
133 | start_obj = vcobjptridx(Viewer); //get viewer object number |
||
134 | |||
135 | range_for (const auto &&i, vcobjptr) |
||
136 | { |
||
137 | (void)i; |
||
138 | start_obj++; |
||
139 | if (start_obj > Highest_object_index ) start_obj = 0; |
||
140 | |||
141 | auto &objp = *vmobjptr(start_obj); |
||
142 | if (objp.type != OBJ_NONE) |
||
143 | { |
||
144 | Viewer = &objp; |
||
145 | return; |
||
146 | } |
||
147 | } |
||
148 | |||
149 | Error( "Could not find a viewer object!" ); |
||
150 | |||
151 | } |
||
152 | #endif |
||
153 | |||
154 | imobjptridx_t obj_find_first_of_type(fvmobjptridx &vmobjptridx, const object_type_t type) |
||
155 | { |
||
156 | range_for (const auto &&i, vmobjptridx) |
||
157 | { |
||
158 | if (i->type==type) |
||
159 | return i; |
||
160 | } |
||
161 | return object_none; |
||
162 | } |
||
163 | |||
164 | } |
||
165 | |||
166 | namespace dcx { |
||
167 | |||
168 | icobjidx_t laser_info::get_last_hitobj() const |
||
169 | { |
||
170 | if (!hitobj_count) |
||
171 | /* If no elements, return object_none */ |
||
172 | return object_none; |
||
173 | /* Return the most recently written element. `hitobj_pos` |
||
174 | * indicates the element to write next, so return |
||
175 | * hitobj_values[hitobj_pos - 1]. When hitobj_pos == 0, the |
||
176 | * most recently written element is at the end of the array, not |
||
177 | * before the beginning of the array. |
||
178 | */ |
||
179 | if (!hitobj_pos) |
||
180 | return hitobj_values.back(); |
||
181 | return hitobj_values[hitobj_pos - 1]; |
||
182 | } |
||
183 | |||
184 | //draw an object that has one bitmap & doesn't rotate |
||
185 | void draw_object_blob(grs_canvas &canvas, const object_base &obj, const bitmap_index bmi) |
||
186 | { |
||
187 | auto &bm = GameBitmaps[bmi.index]; |
||
188 | PIGGY_PAGE_IN( bmi ); |
||
189 | |||
190 | const auto osize = obj.size; |
||
191 | // draw these with slight offset to viewer preventing too much ugly clipping |
||
192 | auto pos = obj.pos; |
||
193 | if (obj.type == OBJ_FIREBALL && get_fireball_id(obj) == VCLIP_VOLATILE_WALL_HIT) |
||
194 | { |
||
195 | vms_vector offs_vec; |
||
196 | vm_vec_normalized_dir_quick(offs_vec, Viewer->pos, pos); |
||
197 | vm_vec_scale_add2(pos,offs_vec,F1_0); |
||
198 | } |
||
199 | |||
200 | using wh = std::pair<fix, fix>; |
||
201 | const auto bm_w = bm.bm_w; |
||
202 | const auto bm_h = bm.bm_h; |
||
203 | const auto p = (bm_w > bm_h) |
||
204 | ? wh(osize, fixmuldiv(osize, bm_h, bm_w)) |
||
205 | : wh(fixmuldiv(osize, bm_w, bm_h), osize); |
||
206 | g3_draw_bitmap(canvas, pos, p.first, p.second, bm); |
||
207 | } |
||
208 | |||
209 | } |
||
210 | |||
211 | namespace dsx { |
||
212 | |||
213 | //draw an object that is a texture-mapped rod |
||
214 | void draw_object_tmap_rod(grs_canvas &canvas, const d_level_unique_light_state *const LevelUniqueLightState, const vcobjptridx_t obj, const bitmap_index bitmapi) |
||
215 | { |
||
216 | g3s_lrgb light; |
||
217 | PIGGY_PAGE_IN(bitmapi); |
||
218 | |||
219 | auto &bitmap = GameBitmaps[bitmapi.index]; |
||
220 | |||
221 | const auto delta = vm_vec_copy_scale(obj->orient.uvec,obj->size); |
||
222 | |||
223 | const auto top_v = vm_vec_add(obj->pos,delta); |
||
224 | const auto bot_v = vm_vec_sub(obj->pos,delta); |
||
225 | |||
226 | const auto top_p = g3_rotate_point(top_v); |
||
227 | const auto bot_p = g3_rotate_point(bot_v); |
||
228 | |||
229 | if (LevelUniqueLightState) |
||
230 | { |
||
231 | light = compute_object_light(*LevelUniqueLightState, obj); |
||
232 | } |
||
233 | else |
||
234 | { |
||
235 | light.r = light.g = light.b = f1_0; |
||
236 | } |
||
237 | g3_draw_rod_tmap(canvas, bitmap, bot_p, obj->size, top_p, obj->size, light); |
||
238 | } |
||
239 | |||
240 | //used for robot engine glow |
||
241 | #define MAX_VELOCITY i2f(50) |
||
242 | |||
243 | //what darkening level to use when cloaked |
||
244 | #define CLOAKED_FADE_LEVEL 28 |
||
245 | |||
246 | #define CLOAK_FADEIN_DURATION_PLAYER F2_0 |
||
247 | #define CLOAK_FADEOUT_DURATION_PLAYER F2_0 |
||
248 | |||
249 | #define CLOAK_FADEIN_DURATION_ROBOT F1_0 |
||
250 | #define CLOAK_FADEOUT_DURATION_ROBOT F1_0 |
||
251 | |||
252 | //do special cloaked render |
||
253 | static void draw_cloaked_object(grs_canvas &canvas, const object_base &obj, const g3s_lrgb light, glow_values_t glow, const fix64 cloak_start_time, const fix total_cloaked_time, const fix Cloak_fadein_duration, const fix Cloak_fadeout_duration) |
||
254 | { |
||
255 | fix cloak_delta_time; |
||
256 | fix light_scale=F1_0; |
||
257 | int cloak_value=0; |
||
258 | int fading=0; //if true, fading, else cloaking |
||
259 | |||
260 | cloak_delta_time = GameTime64 - cloak_start_time; |
||
261 | |||
262 | if (cloak_delta_time < Cloak_fadein_duration/2) { |
||
263 | |||
264 | #if defined(DXX_BUILD_DESCENT_I) |
||
265 | light_scale = Cloak_fadein_duration/2 - cloak_delta_time; |
||
266 | #elif defined(DXX_BUILD_DESCENT_II) |
||
267 | light_scale = fixdiv(Cloak_fadein_duration/2 - cloak_delta_time,Cloak_fadein_duration/2); |
||
268 | #endif |
||
269 | fading = 1; |
||
270 | |||
271 | } |
||
272 | else if (cloak_delta_time < Cloak_fadein_duration) { |
||
273 | |||
274 | #if defined(DXX_BUILD_DESCENT_I) |
||
275 | cloak_value = f2i((cloak_delta_time - Cloak_fadein_duration/2) * CLOAKED_FADE_LEVEL); |
||
276 | #elif defined(DXX_BUILD_DESCENT_II) |
||
277 | cloak_value = f2i(fixdiv(cloak_delta_time - Cloak_fadein_duration/2,Cloak_fadein_duration/2) * CLOAKED_FADE_LEVEL); |
||
278 | #endif |
||
279 | |||
280 | } else if (GameTime64 < (cloak_start_time + total_cloaked_time) -Cloak_fadeout_duration) { |
||
281 | static int cloak_delta=0,cloak_dir=1; |
||
282 | static fix cloak_timer=0; |
||
283 | |||
284 | //note, if more than one cloaked object is visible at once, the |
||
285 | //pulse rate will change! |
||
286 | |||
287 | cloak_timer -= FrameTime; |
||
288 | while (cloak_timer < 0) { |
||
289 | |||
290 | cloak_timer += Cloak_fadeout_duration/12; |
||
291 | |||
292 | cloak_delta += cloak_dir; |
||
293 | |||
294 | if (cloak_delta==0 || cloak_delta==4) |
||
295 | cloak_dir = -cloak_dir; |
||
296 | } |
||
297 | |||
298 | cloak_value = CLOAKED_FADE_LEVEL - cloak_delta; |
||
299 | |||
300 | } else if (GameTime64 < (cloak_start_time + total_cloaked_time) -Cloak_fadeout_duration/2) { |
||
301 | |||
302 | #if defined(DXX_BUILD_DESCENT_I) |
||
303 | cloak_value = f2i((total_cloaked_time - Cloak_fadeout_duration/2 - cloak_delta_time) * CLOAKED_FADE_LEVEL); |
||
304 | #elif defined(DXX_BUILD_DESCENT_II) |
||
305 | cloak_value = f2i(fixdiv(total_cloaked_time - Cloak_fadeout_duration/2 - cloak_delta_time,Cloak_fadeout_duration/2) * CLOAKED_FADE_LEVEL); |
||
306 | #endif |
||
307 | |||
308 | } else { |
||
309 | |||
310 | #if defined(DXX_BUILD_DESCENT_I) |
||
311 | light_scale = Cloak_fadeout_duration/2 - (total_cloaked_time - cloak_delta_time); |
||
312 | #elif defined(DXX_BUILD_DESCENT_II) |
||
313 | light_scale = fixdiv(Cloak_fadeout_duration/2 - (total_cloaked_time - cloak_delta_time),Cloak_fadeout_duration/2); |
||
314 | #endif |
||
315 | fading = 1; |
||
316 | } |
||
317 | |||
318 | alternate_textures alt_textures; |
||
319 | #if defined(DXX_BUILD_DESCENT_II) |
||
320 | if (fading) |
||
321 | #endif |
||
322 | { |
||
323 | const unsigned ati = static_cast<unsigned>(obj.rtype.pobj_info.alt_textures) - 1; |
||
324 | if (ati < multi_player_textures.size()) |
||
325 | { |
||
326 | alt_textures = multi_player_textures[ati]; |
||
327 | } |
||
328 | } |
||
329 | |||
330 | if (fading) { |
||
331 | g3s_lrgb new_light; |
||
332 | |||
333 | new_light.r = fixmul(light.r,light_scale); |
||
334 | new_light.g = fixmul(light.g,light_scale); |
||
335 | new_light.b = fixmul(light.b,light_scale); |
||
336 | glow[0] = fixmul(glow[0],light_scale); |
||
337 | draw_polygon_model(canvas, obj.pos, |
||
338 | obj.orient, |
||
339 | obj.rtype.pobj_info.anim_angles, |
||
340 | obj.rtype.pobj_info.model_num, obj.rtype.pobj_info.subobj_flags, |
||
341 | new_light, |
||
342 | &glow, |
||
343 | alt_textures ); |
||
344 | } |
||
345 | else { |
||
346 | gr_settransblend(canvas, cloak_value, gr_blend::normal); |
||
347 | g3_set_special_render(draw_tmap_flat); //use special flat drawer |
||
348 | draw_polygon_model(canvas, obj.pos, |
||
349 | obj.orient, |
||
350 | obj.rtype.pobj_info.anim_angles, |
||
351 | obj.rtype.pobj_info.model_num, obj.rtype.pobj_info.subobj_flags, |
||
352 | light, |
||
353 | &glow, |
||
354 | alt_textures ); |
||
355 | g3_set_special_render(draw_tmap); |
||
356 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); |
||
357 | } |
||
358 | |||
359 | } |
||
360 | |||
361 | //draw an object which renders as a polygon model |
||
362 | static void draw_polygon_object(grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const vcobjptridx_t obj) |
||
363 | { |
||
364 | auto &BossUniqueState = LevelUniqueObjectState.BossState; |
||
365 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
366 | g3s_lrgb light; |
||
367 | glow_values_t engine_glow_value; |
||
368 | engine_glow_value[0] = 0; |
||
369 | #if defined(DXX_BUILD_DESCENT_II) |
||
370 | engine_glow_value[1] = -1; //element 0 is for engine glow, 1 for headlight |
||
371 | #endif |
||
372 | |||
373 | // If option set for bright players in netgame, brighten them! |
||
374 | light = unlikely(Netgame.BrightPlayers && (Game_mode & GM_MULTI) && obj->type == OBJ_PLAYER) |
||
375 | ? g3s_lrgb{F1_0 * 2, F1_0 * 2, F1_0 * 2} |
||
376 | : compute_object_light(LevelUniqueLightState, obj); |
||
377 | |||
378 | #if defined(DXX_BUILD_DESCENT_II) |
||
379 | //make robots brighter according to robot glow field |
||
380 | if (obj->type == OBJ_ROBOT) |
||
381 | { |
||
382 | const auto glow = Robot_info[get_robot_id(obj)].glow<<12; |
||
383 | light.r += glow; //convert 4:4 to 16:16 |
||
384 | light.g += glow; //convert 4:4 to 16:16 |
||
385 | light.b += glow; //convert 4:4 to 16:16 |
||
386 | } |
||
387 | |||
388 | if ((obj->type == OBJ_WEAPON && |
||
389 | get_weapon_id(obj) == weapon_id_type::FLARE_ID) || |
||
390 | obj->type == OBJ_MARKER |
||
391 | ) |
||
392 | { |
||
393 | light.r += F1_0*2; |
||
394 | light.g += F1_0*2; |
||
395 | light.b += F1_0*2; |
||
396 | } |
||
397 | #endif |
||
398 | |||
399 | push_interpolation_method imsave(1, true); |
||
400 | |||
401 | //set engine glow value |
||
402 | engine_glow_value[0] = f1_0/5; |
||
403 | if (obj->movement_type == MT_PHYSICS) { |
||
404 | |||
405 | if (obj->mtype.phys_info.flags & PF_USES_THRUST && obj->type==OBJ_PLAYER && get_player_id(obj)==Player_num) { |
||
406 | fix thrust_mag = vm_vec_mag_quick(obj->mtype.phys_info.thrust); |
||
407 | engine_glow_value[0] += (fixdiv(thrust_mag,Player_ship->max_thrust)*4)/5; |
||
408 | } |
||
409 | else { |
||
410 | fix speed = vm_vec_mag_quick(obj->mtype.phys_info.velocity); |
||
411 | #if defined(DXX_BUILD_DESCENT_I) |
||
412 | engine_glow_value[0] += (fixdiv(speed,MAX_VELOCITY)*4)/5; |
||
413 | #elif defined(DXX_BUILD_DESCENT_II) |
||
414 | engine_glow_value[0] += (fixdiv(speed,MAX_VELOCITY)*3)/5; |
||
415 | #endif |
||
416 | } |
||
417 | } |
||
418 | |||
419 | #if defined(DXX_BUILD_DESCENT_II) |
||
420 | //set value for player headlight |
||
421 | if (obj->type == OBJ_PLAYER) { |
||
422 | auto &player_flags = obj->ctype.player_info.powerup_flags; |
||
423 | if (player_flags & PLAYER_FLAGS_HEADLIGHT && !Endlevel_sequence) |
||
424 | if (player_flags & PLAYER_FLAGS_HEADLIGHT_ON) |
||
425 | engine_glow_value[1] = -2; //draw white! |
||
426 | else |
||
427 | engine_glow_value[1] = -1; //draw normal color (grey) |
||
428 | else |
||
429 | engine_glow_value[1] = -3; //don't draw |
||
430 | } |
||
431 | #endif |
||
432 | |||
433 | if (obj->rtype.pobj_info.tmap_override != -1) { |
||
434 | std::array<bitmap_index, 12> bm_ptrs; |
||
435 | |||
436 | //fill whole array, in case simple model needs more |
||
437 | bm_ptrs.fill(Textures[obj->rtype.pobj_info.tmap_override]); |
||
438 | draw_polygon_model(canvas, obj->pos, |
||
439 | obj->orient, |
||
440 | obj->rtype.pobj_info.anim_angles, |
||
441 | obj->rtype.pobj_info.model_num, |
||
442 | obj->rtype.pobj_info.subobj_flags, |
||
443 | light, |
||
444 | &engine_glow_value, |
||
445 | bm_ptrs); |
||
446 | } |
||
447 | else { |
||
448 | std::pair<fix64, fix> cloak_duration; |
||
449 | std::pair<fix, fix> cloak_fade; |
||
450 | if (obj->type==OBJ_PLAYER && (obj->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)) |
||
451 | { |
||
452 | auto &cloak_time = obj->ctype.player_info.cloak_time; |
||
453 | cloak_duration = {cloak_time, CLOAK_TIME_MAX}; |
||
454 | cloak_fade = {CLOAK_FADEIN_DURATION_PLAYER, CLOAK_FADEOUT_DURATION_PLAYER}; |
||
455 | } |
||
456 | else if ((obj->type == OBJ_ROBOT) && (obj->ctype.ai_info.CLOAKED)) { |
||
457 | if (Robot_info[get_robot_id(obj)].boss_flag) |
||
458 | cloak_duration = {BossUniqueState.Boss_cloak_start_time, Boss_cloak_duration}; |
||
459 | else |
||
460 | cloak_duration = {GameTime64-F1_0*10, F1_0 * 20}; |
||
461 | cloak_fade = {CLOAK_FADEIN_DURATION_ROBOT, CLOAK_FADEOUT_DURATION_ROBOT}; |
||
462 | } else { |
||
463 | alternate_textures alt_textures; |
||
464 | const unsigned ati = static_cast<unsigned>(obj->rtype.pobj_info.alt_textures) - 1; |
||
465 | if (ati < multi_player_textures.size()) |
||
466 | alt_textures = multi_player_textures[ati]; |
||
467 | |||
468 | #if defined(DXX_BUILD_DESCENT_II) |
||
469 | if (obj->type == OBJ_ROBOT) |
||
470 | { |
||
471 | // Snipers get bright when they fire. |
||
472 | if (obj->ctype.ai_info.ail.next_fire < F1_0/8) { |
||
473 | if (obj->ctype.ai_info.behavior == ai_behavior::AIB_SNIPE) |
||
474 | { |
||
475 | light.r = 2*light.r + F1_0; |
||
476 | light.g = 2*light.g + F1_0; |
||
477 | light.b = 2*light.b + F1_0; |
||
478 | } |
||
479 | } |
||
480 | } |
||
481 | #endif |
||
482 | |||
483 | const auto is_weapon_with_inner_model = (obj->type == OBJ_WEAPON && Weapon_info[get_weapon_id(obj)].model_num_inner > -1); |
||
484 | bool draw_simple_model; |
||
485 | if (is_weapon_with_inner_model) |
||
486 | { |
||
487 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_a); |
||
488 | draw_simple_model = static_cast<fix>(vm_vec_dist_quick(Viewer->pos, obj->pos)) < Simple_model_threshhold_scale * F1_0*2; |
||
489 | if (draw_simple_model) |
||
490 | draw_polygon_model(canvas, obj->pos, |
||
491 | obj->orient, |
||
492 | obj->rtype.pobj_info.anim_angles, |
||
493 | Weapon_info[get_weapon_id(obj)].model_num_inner, |
||
494 | obj->rtype.pobj_info.subobj_flags, |
||
495 | light, |
||
496 | &engine_glow_value, |
||
497 | alt_textures); |
||
498 | } |
||
499 | |||
500 | draw_polygon_model(canvas, obj->pos, |
||
501 | obj->orient, |
||
502 | obj->rtype.pobj_info.anim_angles,obj->rtype.pobj_info.model_num, |
||
503 | obj->rtype.pobj_info.subobj_flags, |
||
504 | light, |
||
505 | &engine_glow_value, |
||
506 | alt_textures); |
||
507 | |||
508 | if (is_weapon_with_inner_model) |
||
509 | { |
||
510 | #if !DXX_USE_OGL // in software rendering must draw inner model last |
||
511 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_a); |
||
512 | if (draw_simple_model) |
||
513 | draw_polygon_model(canvas, obj->pos, |
||
514 | obj->orient, |
||
515 | obj->rtype.pobj_info.anim_angles, |
||
516 | Weapon_info[obj->id].model_num_inner, |
||
517 | obj->rtype.pobj_info.subobj_flags, |
||
518 | light, |
||
519 | &engine_glow_value, |
||
520 | alt_textures); |
||
521 | #endif |
||
522 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); |
||
523 | } |
||
524 | return; |
||
525 | } |
||
526 | draw_cloaked_object(canvas, obj, light, engine_glow_value, cloak_duration.first, cloak_duration.second, cloak_fade.first, cloak_fade.second); |
||
527 | } |
||
528 | } |
||
529 | |||
530 | } |
||
531 | |||
532 | //------------------------------------------------------------------------------ |
||
533 | // These variables are used to keep a list of the 3 closest robots to the viewer. |
||
534 | // The code works like this: Every time render object is called with a polygon model, |
||
535 | // it finds the distance of that robot to the viewer. If this distance if within 10 |
||
536 | // segments of the viewer, it does the following: If there aren't already 3 robots in |
||
537 | // the closet-robots list, it just sticks that object into the list along with its distance. |
||
538 | // If the list already contains 3 robots, then it finds the robot in that list that is |
||
539 | // farthest from the viewer. If that object is farther than the object currently being |
||
540 | // rendered, then the new object takes over that far object's slot. *Then* after all |
||
541 | // objects are rendered, object_render_targets is called an it draws a target on top |
||
542 | // of all the objects. |
||
543 | |||
544 | //091494: #define MAX_CLOSE_ROBOTS 3 |
||
545 | //--unused-- static int Object_draw_lock_boxes = 0; |
||
546 | //091494: static int Object_num_close = 0; |
||
547 | //091494: static object * Object_close_ones[MAX_CLOSE_ROBOTS]; |
||
548 | //091494: static fix Object_close_distance[MAX_CLOSE_ROBOTS]; |
||
549 | |||
550 | //091494: set_close_objects(object *obj) |
||
551 | //091494: { |
||
552 | //091494: fix dist; |
||
553 | //091494: |
||
554 | //091494: if ( (obj->type != OBJ_ROBOT) || (Object_draw_lock_boxes==0) ) |
||
555 | //091494: return; |
||
556 | //091494: |
||
557 | //091494: // The following code keeps a list of the 10 closest robots to the |
||
558 | //091494: // viewer. See comments in front of this function for how this works. |
||
559 | //091494: dist = vm_vec_dist( &obj->pos, &Viewer->pos ); |
||
560 | //091494: if ( dist < i2f(20*10) ) { |
||
561 | //091494: if ( Object_num_close < MAX_CLOSE_ROBOTS ) { |
||
562 | //091494: Object_close_ones[Object_num_close] = obj; |
||
563 | //091494: Object_close_distance[Object_num_close] = dist; |
||
564 | //091494: Object_num_close++; |
||
565 | //091494: } else { |
||
566 | //091494: int i, farthest_robot; |
||
567 | //091494: fix farthest_distance; |
||
568 | //091494: // Find the farthest robot in the list |
||
569 | //091494: farthest_robot = 0; |
||
570 | //091494: farthest_distance = Object_close_distance[0]; |
||
571 | //091494: for (i=1; i<Object_num_close; i++ ) { |
||
572 | //091494: if ( Object_close_distance[i] > farthest_distance ) { |
||
573 | //091494: farthest_distance = Object_close_distance[i]; |
||
574 | //091494: farthest_robot = i; |
||
575 | //091494: } |
||
576 | //091494: } |
||
577 | //091494: // If this object is closer to the viewer than |
||
578 | //091494: // the farthest in the list, replace the farthest with this object. |
||
579 | //091494: if ( farthest_distance > dist ) { |
||
580 | //091494: Object_close_ones[farthest_robot] = obj; |
||
581 | //091494: Object_close_distance[farthest_robot] = dist; |
||
582 | //091494: } |
||
583 | //091494: } |
||
584 | //091494: } |
||
585 | //091494: } |
||
586 | |||
587 | namespace dcx { |
||
588 | objnum_t Player_fired_laser_this_frame=object_none; |
||
589 | |||
590 | static bool predicate_debris(const object_base &o) |
||
591 | { |
||
592 | return o.type == OBJ_DEBRIS; |
||
593 | } |
||
594 | |||
595 | static bool predicate_flare(const object_base &o) |
||
596 | { |
||
597 | return (o.type == OBJ_WEAPON) && (get_weapon_id(o) == weapon_id_type::FLARE_ID); |
||
598 | } |
||
599 | |||
600 | static bool predicate_nonflare_weapon(const object_base &o) |
||
601 | { |
||
602 | return (o.type == OBJ_WEAPON) && (get_weapon_id(o) != weapon_id_type::FLARE_ID); |
||
603 | } |
||
604 | |||
605 | } |
||
606 | |||
607 | |||
608 | |||
609 | namespace dsx { |
||
610 | |||
611 | static bool predicate_fireball(const object &o) |
||
612 | { |
||
613 | return o.type == OBJ_FIREBALL && o.ctype.expl_info.delete_objnum == object_none; |
||
614 | } |
||
615 | |||
616 | // ----------------------------------------------------------------------------- |
||
617 | //this routine checks to see if an robot rendered near the middle of |
||
618 | //the screen, and if so and the player had fired, "warns" the robot |
||
619 | static void set_robot_location_info(object &objp) |
||
620 | { |
||
621 | auto &Objects = LevelUniqueObjectState.Objects; |
||
622 | auto &vcobjptr = Objects.vcptr; |
||
623 | if (Player_fired_laser_this_frame != object_none) { |
||
624 | const auto &&temp = g3_rotate_point(objp.pos); |
||
625 | if (temp.p3_codes & CC_BEHIND) //robot behind the screen |
||
626 | return; |
||
627 | |||
628 | //the code below to check for object near the center of the screen |
||
629 | //completely ignores z, which may not be good |
||
630 | |||
631 | if ((abs(temp.p3_x) < F1_0*4) && (abs(temp.p3_y) < F1_0*4)) { |
||
632 | objp.ctype.ai_info.danger_laser_num = Player_fired_laser_this_frame; |
||
633 | objp.ctype.ai_info.danger_laser_signature = vcobjptr(Player_fired_laser_this_frame)->signature; |
||
634 | } |
||
635 | } |
||
636 | } |
||
637 | |||
638 | // ------------------------------------------------------------------------------------------------------------------ |
||
639 | void create_small_fireball_on_object(const vmobjptridx_t objp, fix size_scale, int sound_flag) |
||
640 | { |
||
641 | auto &Objects = LevelUniqueObjectState.Objects; |
||
642 | fix size; |
||
643 | vms_vector pos; |
||
644 | |||
645 | pos = objp->pos; |
||
646 | auto rand_vec = make_random_vector(); |
||
647 | |||
648 | vm_vec_scale(rand_vec, objp->size/2); |
||
649 | |||
650 | vm_vec_add2(pos, rand_vec); |
||
651 | |||
652 | #if defined(DXX_BUILD_DESCENT_I) |
||
653 | size = fixmul(size_scale, F1_0 + d_rand()*4); |
||
654 | #elif defined(DXX_BUILD_DESCENT_II) |
||
655 | size = fixmul(size_scale, F1_0/2 + d_rand()*4/2); |
||
656 | #endif |
||
657 | |||
658 | const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, pos, Segments.vmptridx(objp->segnum)); |
||
659 | if (segnum != segment_none) { |
||
660 | auto expl_obj = object_create_explosion(segnum, pos, size, VCLIP_SMALL_EXPLOSION); |
||
661 | if (!expl_obj) |
||
662 | return; |
||
663 | obj_attach(Objects, objp, expl_obj); |
||
664 | if (d_rand() < 8192) { |
||
665 | fix vol = F1_0/2; |
||
666 | if (objp->type == OBJ_ROBOT) |
||
667 | vol *= 2; |
||
668 | if (sound_flag) |
||
669 | digi_link_sound_to_object(SOUND_EXPLODING_WALL, objp, 0, vol, sound_stack::allow_stacking); |
||
670 | } |
||
671 | } |
||
672 | } |
||
673 | |||
674 | // -- mk, 02/05/95 -- #define VCLIP_INVULNERABILITY_EFFECT VCLIP_SMALL_EXPLOSION |
||
675 | // -- mk, 02/05/95 -- |
||
676 | // -- mk, 02/05/95 -- // ----------------------------------------------------------------------------- |
||
677 | // -- mk, 02/05/95 -- void do_player_invulnerability_effect(object *objp) |
||
678 | // -- mk, 02/05/95 -- { |
||
679 | // -- mk, 02/05/95 -- if (d_rand() < FrameTime*8) { |
||
680 | // -- mk, 02/05/95 -- create_vclip_on_object(objp, F1_0, VCLIP_INVULNERABILITY_EFFECT); |
||
681 | // -- mk, 02/05/95 -- } |
||
682 | // -- mk, 02/05/95 -- } |
||
683 | |||
684 | // ----------------------------------------------------------------------------- |
||
685 | // Render an object. Calls one of several routines based on type |
||
686 | void render_object(grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const vmobjptridx_t obj) |
||
687 | { |
||
688 | if (unlikely(obj == Viewer)) |
||
689 | return; |
||
690 | if (unlikely(obj->type==OBJ_NONE)) |
||
691 | { |
||
692 | Int3(); |
||
693 | return; |
||
694 | } |
||
695 | |||
696 | #if !DXX_USE_OGL |
||
697 | const auto mld_save = std::exchange(Max_linear_depth, Max_linear_depth_objects); |
||
698 | #endif |
||
699 | |||
700 | bool alpha = false; |
||
701 | switch (obj->render_type) |
||
702 | { |
||
703 | case RT_NONE: |
||
704 | break; //doesn't render, like the player |
||
705 | |||
706 | case RT_POLYOBJ: |
||
707 | #if defined(DXX_BUILD_DESCENT_II) |
||
708 | if ( PlayerCfg.AlphaBlendMarkers && obj->type == OBJ_MARKER ) // set nice transparency/blending for certrain objects |
||
709 | { |
||
710 | alpha = true; |
||
711 | gr_settransblend(canvas, 10, gr_blend::additive_a); |
||
712 | } |
||
713 | #endif |
||
714 | draw_polygon_object(canvas, LevelUniqueLightState, obj); |
||
715 | |||
716 | if (obj->type == OBJ_ROBOT) //"warn" robot if being shot at |
||
717 | set_robot_location_info(obj); |
||
718 | break; |
||
719 | |||
720 | case RT_MORPH: |
||
721 | draw_morph_object(canvas, LevelUniqueLightState, obj); |
||
722 | break; |
||
723 | |||
724 | case RT_FIREBALL: |
||
725 | if (PlayerCfg.AlphaBlendFireballs) // set nice transparency/blending for certrain objects |
||
726 | { |
||
727 | alpha = true; |
||
728 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_c); |
||
729 | } |
||
730 | |||
731 | draw_fireball(Vclip, canvas, obj); |
||
732 | break; |
||
733 | |||
734 | case RT_WEAPON_VCLIP: |
||
735 | if (PlayerCfg.AlphaBlendWeapons && (!is_proximity_bomb_or_any_smart_mine(get_weapon_id(obj)) |
||
736 | )) // set nice transparency/blending for certain objects |
||
737 | { |
||
738 | alpha = true; |
||
739 | gr_settransblend(canvas, 7, gr_blend::additive_a); |
||
740 | } |
||
741 | |||
742 | draw_weapon_vclip(Vclip, Weapon_info, canvas, obj); |
||
743 | break; |
||
744 | |||
745 | case RT_HOSTAGE: |
||
746 | draw_hostage(Vclip, canvas, LevelUniqueLightState, obj); |
||
747 | break; |
||
748 | |||
749 | case RT_POWERUP: |
||
750 | if (PlayerCfg.AlphaBlendPowerups) // set nice transparency/blending for certrain objects |
||
751 | switch ( get_powerup_id(obj) ) |
||
752 | { |
||
753 | case POW_EXTRA_LIFE: |
||
754 | case POW_ENERGY: |
||
755 | case POW_SHIELD_BOOST: |
||
756 | case POW_CLOAK: |
||
757 | case POW_INVULNERABILITY: |
||
758 | #if defined(DXX_BUILD_DESCENT_II) |
||
759 | case POW_HOARD_ORB: |
||
760 | #endif |
||
761 | alpha = true; |
||
762 | gr_settransblend(canvas, 7, gr_blend::additive_a); |
||
763 | break; |
||
764 | case POW_LASER: |
||
765 | case POW_KEY_BLUE: |
||
766 | case POW_KEY_RED: |
||
767 | case POW_KEY_GOLD: |
||
768 | case POW_MISSILE_1: |
||
769 | case POW_MISSILE_4: |
||
770 | case POW_QUAD_FIRE: |
||
771 | case POW_VULCAN_WEAPON: |
||
772 | case POW_SPREADFIRE_WEAPON: |
||
773 | case POW_PLASMA_WEAPON: |
||
774 | case POW_FUSION_WEAPON: |
||
775 | case POW_PROXIMITY_WEAPON: |
||
776 | case POW_HOMING_AMMO_1: |
||
777 | case POW_HOMING_AMMO_4: |
||
778 | case POW_SMARTBOMB_WEAPON: |
||
779 | case POW_MEGA_WEAPON: |
||
780 | case POW_VULCAN_AMMO: |
||
781 | case POW_TURBO: |
||
782 | case POW_MEGAWOW: |
||
783 | #if defined(DXX_BUILD_DESCENT_II) |
||
784 | case POW_FULL_MAP: |
||
785 | case POW_HEADLIGHT: |
||
786 | case POW_GAUSS_WEAPON: |
||
787 | case POW_HELIX_WEAPON: |
||
788 | case POW_PHOENIX_WEAPON: |
||
789 | case POW_OMEGA_WEAPON: |
||
790 | case POW_SUPER_LASER: |
||
791 | case POW_CONVERTER: |
||
792 | case POW_AMMO_RACK: |
||
793 | case POW_AFTERBURNER: |
||
794 | case POW_SMISSILE1_1: |
||
795 | case POW_SMISSILE1_4: |
||
796 | case POW_GUIDED_MISSILE_1: |
||
797 | case POW_GUIDED_MISSILE_4: |
||
798 | case POW_SMART_MINE: |
||
799 | case POW_MERCURY_MISSILE_1: |
||
800 | case POW_MERCURY_MISSILE_4: |
||
801 | case POW_EARTHSHAKER_MISSILE: |
||
802 | case POW_FLAG_BLUE: |
||
803 | case POW_FLAG_RED: |
||
804 | #endif |
||
805 | break; |
||
806 | } |
||
807 | |||
808 | draw_powerup(Vclip, canvas, obj); |
||
809 | break; |
||
810 | |||
811 | case RT_LASER: |
||
812 | if (PlayerCfg.AlphaBlendLasers) // set nice transparency/blending for certrain objects |
||
813 | { |
||
814 | alpha = true; |
||
815 | gr_settransblend(canvas, 7, gr_blend::additive_a); |
||
816 | } |
||
817 | |||
818 | Laser_render(canvas, obj); |
||
819 | break; |
||
820 | |||
821 | default: |
||
822 | Error("Unknown render_type <%d>",obj->render_type); |
||
823 | } |
||
824 | |||
825 | if (alpha) |
||
826 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); // revert any transparency/blending setting back to normal |
||
827 | |||
828 | if ( obj->render_type != RT_NONE && Newdemo_state == ND_STATE_RECORDING ) |
||
829 | newdemo_record_render_object(obj); |
||
830 | #if !DXX_USE_OGL |
||
831 | Max_linear_depth = mld_save; |
||
832 | #endif |
||
833 | } |
||
834 | |||
835 | void reset_player_object() |
||
836 | { |
||
837 | //Init physics |
||
838 | |||
839 | vm_vec_zero(ConsoleObject->mtype.phys_info.velocity); |
||
840 | vm_vec_zero(ConsoleObject->mtype.phys_info.thrust); |
||
841 | vm_vec_zero(ConsoleObject->mtype.phys_info.rotvel); |
||
842 | vm_vec_zero(ConsoleObject->mtype.phys_info.rotthrust); |
||
843 | ConsoleObject->mtype.phys_info.turnroll = 0; |
||
844 | ConsoleObject->mtype.phys_info.mass = Player_ship->mass; |
||
845 | ConsoleObject->mtype.phys_info.drag = Player_ship->drag; |
||
846 | ConsoleObject->mtype.phys_info.flags = PF_TURNROLL | PF_LEVELLING | PF_WIGGLE | PF_USES_THRUST; |
||
847 | |||
848 | //Init render info |
||
849 | |||
850 | ConsoleObject->render_type = RT_POLYOBJ; |
||
851 | ConsoleObject->rtype.pobj_info.model_num = Player_ship->model_num; //what model is this? |
||
852 | ConsoleObject->rtype.pobj_info.subobj_flags = 0; //zero the flags |
||
853 | ConsoleObject->rtype.pobj_info.tmap_override = -1; //no tmap override! |
||
854 | ConsoleObject->rtype.pobj_info.anim_angles = {}; |
||
855 | |||
856 | // Clear misc |
||
857 | |||
858 | ConsoleObject->flags = 0; |
||
859 | } |
||
860 | |||
861 | |||
862 | //make object0 the player, setting all relevant fields |
||
863 | void init_player_object() |
||
864 | { |
||
865 | auto &Objects = LevelUniqueObjectState.Objects; |
||
866 | auto &vmobjptr = Objects.vmptr; |
||
867 | const auto &&console = vmobjptr(ConsoleObject); |
||
868 | console->type = OBJ_PLAYER; |
||
869 | set_player_id(console, 0); //no sub-types for player |
||
870 | console->signature = object_signature_t{0}; |
||
871 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
872 | console->size = Polygon_models[Player_ship->model_num].rad; |
||
873 | console->control_type = CT_SLEW; //default is player slewing |
||
874 | console->movement_type = MT_PHYSICS; //change this sometime |
||
875 | console->lifeleft = IMMORTAL_TIME; |
||
876 | console->attached_obj = object_none; |
||
877 | reset_player_object(); |
||
878 | } |
||
879 | |||
880 | //sets up the free list & init player & whatever else |
||
881 | void init_objects() |
||
882 | { |
||
883 | auto &Objects = LevelUniqueObjectState.Objects; |
||
884 | auto &vmobjptr = Objects.vmptr; |
||
885 | for (objnum_t i = 0; i< MAX_OBJECTS; ++i) |
||
886 | { |
||
887 | LevelUniqueObjectState.free_obj_list[i] = i; |
||
888 | auto &obj = *vmobjptr(i); |
||
889 | DXX_POISON_VAR(obj, 0xfd); |
||
890 | obj.type = OBJ_NONE; |
||
891 | } |
||
892 | |||
893 | range_for (unique_segment &j, Segments) |
||
894 | j.objects = object_none; |
||
895 | |||
896 | Viewer = ConsoleObject = &Objects.front(); |
||
897 | |||
898 | init_player_object(); |
||
899 | obj_link_unchecked(Objects.vmptr, Objects.vmptridx(ConsoleObject), Segments.vmptridx(segment_first)); //put in the world in segment 0 |
||
900 | LevelUniqueObjectState.num_objects = 1; //just the player |
||
901 | Objects.set_count(1); |
||
902 | } |
||
903 | |||
904 | //after calling init_object(), the network code has grabbed specific |
||
905 | //object slots without allocating them. Go though the objects & build |
||
906 | //the free list, then set the apporpriate globals |
||
907 | void special_reset_objects(d_level_unique_object_state &LevelUniqueObjectState) |
||
908 | { |
||
909 | unsigned num_objects = MAX_OBJECTS; |
||
910 | |||
911 | auto &Objects = LevelUniqueObjectState.get_objects(); |
||
912 | Objects.set_count(1); |
||
913 | assert(Objects.front().type != OBJ_NONE); //0 should be used |
||
914 | |||
915 | DXX_POISON_VAR(LevelUniqueObjectState.free_obj_list, 0xfd); |
||
916 | for (objnum_t i = MAX_OBJECTS; i--;) |
||
917 | if (Objects.vcptr(i)->type == OBJ_NONE) |
||
918 | LevelUniqueObjectState.free_obj_list[--num_objects] = i; |
||
919 | else |
||
920 | if (i > Highest_object_index) |
||
921 | Objects.set_count(i + 1); |
||
922 | LevelUniqueObjectState.num_objects = num_objects; |
||
923 | } |
||
924 | |||
925 | //link the object into the list for its segment |
||
926 | void obj_link(fvmobjptr &vmobjptr, const vmobjptridx_t obj, const vmsegptridx_t segnum) |
||
927 | { |
||
928 | assert(obj->segnum == segment_none); |
||
929 | assert(obj->next == object_none); |
||
930 | assert(obj->prev == object_none); |
||
931 | obj_link_unchecked(vmobjptr, obj, segnum); |
||
932 | } |
||
933 | |||
934 | void obj_link_unchecked(fvmobjptr &vmobjptr, const vmobjptridx_t obj, const vmsegptridx_t segnum) |
||
935 | { |
||
936 | obj->segnum = segnum; |
||
937 | |||
938 | obj->next = segnum->objects; |
||
939 | obj->prev = object_none; |
||
940 | |||
941 | segnum->objects = obj; |
||
942 | |||
943 | if (obj->next != object_none) |
||
944 | vmobjptr(obj->next)->prev = obj; |
||
945 | } |
||
946 | |||
947 | void obj_unlink(fvmobjptr &vmobjptr, fvmsegptr &vmsegptr, object_base &obj) |
||
948 | { |
||
949 | const auto next = obj.next; |
||
950 | /* It is a bug elsewhere if vmsegptr ever fails here. However, it is |
||
951 | * expensive to check, so only force verification in debug builds. |
||
952 | * |
||
953 | * In debug builds, always compute it, for the side effect of |
||
954 | * validating the segment number. |
||
955 | * |
||
956 | * In release builds, compute it when it is needed. |
||
957 | */ |
||
958 | #ifndef NDEBUG |
||
959 | const auto &&segp = vmsegptr(obj.segnum); |
||
960 | #endif |
||
961 | ((obj.prev == object_none) |
||
962 | ? ( |
||
963 | #ifdef NDEBUG |
||
964 | vmsegptr(obj.segnum) |
||
965 | #else |
||
966 | segp |
||
967 | #endif |
||
968 | )->objects |
||
969 | : vmobjptr(obj.prev)->next) = next; |
||
970 | |||
971 | obj.segnum = segment_none; |
||
972 | |||
973 | if (next != object_none) |
||
974 | vmobjptr(next)->prev = obj.prev; |
||
975 | DXX_POISON_VAR(obj.next, 0xfa); |
||
976 | DXX_POISON_VAR(obj.prev, 0xfa); |
||
977 | } |
||
978 | |||
979 | //returns the number of a free object, updating Highest_object_index. |
||
980 | //Generally, obj_create() should be called to get an object, since it |
||
981 | //fills in important fields and does the linking. |
||
982 | //returns -1 if no free objects |
||
983 | imobjptridx_t obj_allocate(d_level_unique_object_state &LevelUniqueObjectState) |
||
984 | { |
||
985 | auto &Objects = LevelUniqueObjectState.Objects; |
||
986 | if (LevelUniqueObjectState.num_objects >= Objects.size()) |
||
987 | return object_none; |
||
988 | |||
989 | const auto objnum = LevelUniqueObjectState.free_obj_list[LevelUniqueObjectState.num_objects++]; |
||
990 | if (objnum >= Objects.get_count()) |
||
991 | { |
||
992 | Objects.set_count(objnum + 1); |
||
993 | } |
||
994 | const auto &&r = Objects.vmptridx(objnum); |
||
995 | assert(r->type == OBJ_NONE); |
||
996 | return r; |
||
997 | } |
||
998 | |||
999 | //frees up an object. Generally, obj_delete() should be called to get |
||
1000 | //rid of an object. This function deallocates the object entry after |
||
1001 | //the object has been unlinked |
||
1002 | static void obj_free(d_level_unique_object_state &LevelUniqueObjectState, const vmobjidx_t objnum) |
||
1003 | { |
||
1004 | const auto num_objects = -- LevelUniqueObjectState.num_objects; |
||
1005 | assert(num_objects < LevelUniqueObjectState.free_obj_list.size()); |
||
1006 | LevelUniqueObjectState.free_obj_list[num_objects] = objnum; |
||
1007 | auto &Objects = LevelUniqueObjectState.get_objects(); |
||
1008 | |||
1009 | objnum_t o = objnum; |
||
1010 | if (o == Highest_object_index) |
||
1011 | { |
||
1012 | for (;;) |
||
1013 | { |
||
1014 | --o; |
||
1015 | if (Objects.vcptr(o)->type != OBJ_NONE) |
||
1016 | break; |
||
1017 | if (o == 0) |
||
1018 | break; |
||
1019 | } |
||
1020 | Objects.set_count(o + 1); |
||
1021 | } |
||
1022 | } |
||
1023 | |||
1024 | //----------------------------------------------------------------------------- |
||
1025 | // Scan the object list, freeing down to num_used objects |
||
1026 | // Returns number of slots freed. |
||
1027 | static void free_object_slots(uint_fast32_t num_used) |
||
1028 | { |
||
1029 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1030 | auto &vmobjptr = Objects.vmptr; |
||
1031 | std::array<object *, MAX_OBJECTS> obj_list; |
||
1032 | unsigned num_already_free, num_to_free, olind = 0; |
||
1033 | |||
1034 | num_already_free = MAX_OBJECTS - Highest_object_index - 1; |
||
1035 | |||
1036 | if (MAX_OBJECTS - num_already_free < num_used) |
||
1037 | return; |
||
1038 | |||
1039 | range_for (const auto &&objp, vmobjptr) |
||
1040 | { |
||
1041 | if (objp->flags & OF_SHOULD_BE_DEAD) |
||
1042 | { |
||
1043 | num_already_free++; |
||
1044 | if (MAX_OBJECTS - num_already_free < num_used) |
||
1045 | return; |
||
1046 | } else |
||
1047 | switch (objp->type) |
||
1048 | { |
||
1049 | case OBJ_NONE: |
||
1050 | num_already_free++; |
||
1051 | if (MAX_OBJECTS - num_already_free < num_used) |
||
1052 | return; |
||
1053 | break; |
||
1054 | case OBJ_WALL: |
||
1055 | Int3(); // This is curious. What is an object that is a wall? |
||
1056 | break; |
||
1057 | case OBJ_FIREBALL: |
||
1058 | case OBJ_WEAPON: |
||
1059 | case OBJ_DEBRIS: |
||
1060 | obj_list[olind++] = objp; |
||
1061 | break; |
||
1062 | case OBJ_ROBOT: |
||
1063 | case OBJ_HOSTAGE: |
||
1064 | case OBJ_PLAYER: |
||
1065 | case OBJ_CNTRLCEN: |
||
1066 | case OBJ_CLUTTER: |
||
1067 | case OBJ_GHOST: |
||
1068 | case OBJ_LIGHT: |
||
1069 | case OBJ_CAMERA: |
||
1070 | case OBJ_POWERUP: |
||
1071 | case OBJ_COOP: |
||
1072 | case OBJ_MARKER: |
||
1073 | break; |
||
1074 | } |
||
1075 | |||
1076 | } |
||
1077 | |||
1078 | num_to_free = MAX_OBJECTS - num_used - num_already_free; |
||
1079 | |||
1080 | if (num_to_free > olind) { |
||
1081 | num_to_free = olind; |
||
1082 | } |
||
1083 | |||
1084 | // Capture before num_to_free modified |
||
1085 | const auto &&r = partial_const_range(obj_list, num_to_free); |
||
1086 | auto l = [&vmobjptr, &r, &num_to_free](const auto predicate) -> bool { |
||
1087 | range_for (const auto i, r) |
||
1088 | { |
||
1089 | auto &o = *vmobjptr(i); |
||
1090 | if (predicate(o)) |
||
1091 | { |
||
1092 | o.flags |= OF_SHOULD_BE_DEAD; |
||
1093 | if (!-- num_to_free) |
||
1094 | return true; |
||
1095 | } |
||
1096 | } |
||
1097 | return false; |
||
1098 | }; |
||
1099 | |||
1100 | if (l(predicate_debris)) |
||
1101 | return; |
||
1102 | |||
1103 | if (l(predicate_fireball)) |
||
1104 | return; |
||
1105 | |||
1106 | if (l(predicate_flare)) |
||
1107 | return; |
||
1108 | |||
1109 | if (l(predicate_nonflare_weapon)) |
||
1110 | return; |
||
1111 | } |
||
1112 | |||
1113 | //----------------------------------------------------------------------------- |
||
1114 | //initialize a new object. adds to the list for the given segment |
||
1115 | //note that segnum is really just a suggestion, since this routine actually |
||
1116 | //searches for the correct segment |
||
1117 | //returns the object number |
||
1118 | imobjptridx_t obj_create(const object_type_t type, const unsigned id, vmsegptridx_t segnum, const vms_vector &pos, const vms_matrix *const orient, const fix size, const unsigned ctype, const movement_type_t mtype, const render_type_t rtype) |
||
1119 | { |
||
1120 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
1121 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1122 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
1123 | // Some consistency checking. FIXME: Add more debug output here to probably trace all possible occurances back. |
||
1124 | Assert(ctype <= CT_CNTRLCEN); |
||
1125 | |||
1126 | if (type == OBJ_DEBRIS && LevelUniqueObjectState.Debris_object_count >= Max_debris_objects && !PERSISTENT_DEBRIS) |
||
1127 | return object_none; |
||
1128 | |||
1129 | auto &vcvertptr = Vertices.vcptr; |
||
1130 | if (get_seg_masks(vcvertptr, pos, segnum, 0).centermask != 0) |
||
1131 | { |
||
1132 | const auto &&p = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, pos, segnum); |
||
1133 | if (p == segment_none) { |
||
1134 | return object_none; //don't create this object |
||
1135 | } |
||
1136 | segnum = p; |
||
1137 | } |
||
1138 | |||
1139 | // Find next free object |
||
1140 | const auto &&obj = obj_allocate(LevelUniqueObjectState); |
||
1141 | |||
1142 | if (obj == object_none) //no free objects |
||
1143 | return object_none; |
||
1144 | |||
1145 | // Zero out object structure to keep weird bugs from happening |
||
1146 | // in uninitialized fields. |
||
1147 | const auto signature = obj->signature; |
||
1148 | /* Test the version in the object structure, not the local copy. |
||
1149 | * This produces a more useful diagnostic from Valgrind if the |
||
1150 | * test reports a problem. |
||
1151 | */ |
||
1152 | DXX_CHECK_VAR_IS_DEFINED(obj->signature); |
||
1153 | *obj = {}; |
||
1154 | // Tell Valgrind to warn on any uninitialized fields. |
||
1155 | DXX_POISON_VAR(*obj, 0xfd); |
||
1156 | |||
1157 | obj->signature = object_signature_t(signature.get() + 1); |
||
1158 | obj->type = type; |
||
1159 | obj->id = id; |
||
1160 | obj->pos = pos; |
||
1161 | obj->size = size; |
||
1162 | obj->flags = 0; |
||
1163 | //@@if (orient != NULL) |
||
1164 | //@@ obj->orient = *orient; |
||
1165 | |||
1166 | obj->orient = orient?*orient:vmd_identity_matrix; |
||
1167 | |||
1168 | obj->control_type = ctype; |
||
1169 | set_object_movement_type(*obj, mtype); |
||
1170 | obj->render_type = rtype; |
||
1171 | obj->contains_count = 0; |
||
1172 | obj->matcen_creator = 0; |
||
1173 | obj->lifeleft = IMMORTAL_TIME; //assume immortal |
||
1174 | obj->attached_obj = object_none; |
||
1175 | |||
1176 | if (obj->control_type == CT_POWERUP) |
||
1177 | { |
||
1178 | obj->ctype.powerup_info.count = 1; |
||
1179 | obj->ctype.powerup_info.flags = 0; |
||
1180 | obj->ctype.powerup_info.creation_time = GameTime64; |
||
1181 | } |
||
1182 | |||
1183 | // Init physics info for this object |
||
1184 | if (obj->movement_type == MT_PHYSICS) { |
||
1185 | obj->mtype.phys_info = {}; |
||
1186 | } |
||
1187 | |||
1188 | if (obj->render_type == RT_POLYOBJ) |
||
1189 | { |
||
1190 | obj->rtype.pobj_info.subobj_flags = 0; |
||
1191 | obj->rtype.pobj_info.tmap_override = -1; |
||
1192 | obj->rtype.pobj_info.alt_textures = 0; |
||
1193 | } |
||
1194 | |||
1195 | obj->shields = 20*F1_0; |
||
1196 | |||
1197 | { |
||
1198 | const auto &&p = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, pos, segnum); //find correct segment |
||
1199 | // Previously this was only an assert check. Now it is also |
||
1200 | // checked at runtime. |
||
1201 | segnum = p; |
||
1202 | } |
||
1203 | |||
1204 | obj_link_unchecked(Objects.vmptr, obj, segnum); |
||
1205 | |||
1206 | // Set (or not) persistent bit in phys_info. |
||
1207 | if (obj->type == OBJ_WEAPON) { |
||
1208 | Assert(obj->control_type == CT_WEAPON); |
||
1209 | obj->mtype.phys_info.flags |= (Weapon_info[get_weapon_id(obj)].persistent*PF_PERSISTENT); |
||
1210 | obj->ctype.laser_info.creation_time = GameTime64; |
||
1211 | obj->ctype.laser_info.clear_hitobj(); |
||
1212 | obj->ctype.laser_info.multiplier = F1_0; |
||
1213 | #if defined(DXX_BUILD_DESCENT_II) |
||
1214 | obj->ctype.laser_info.last_afterburner_time = 0; |
||
1215 | #endif |
||
1216 | } |
||
1217 | |||
1218 | if (obj->control_type == CT_EXPLOSION) |
||
1219 | obj->ctype.expl_info.next_attach = obj->ctype.expl_info.prev_attach = obj->ctype.expl_info.attach_parent = object_none; |
||
1220 | |||
1221 | if (obj->type == OBJ_DEBRIS) |
||
1222 | ++ LevelUniqueObjectState.Debris_object_count; |
||
1223 | |||
1224 | return obj; |
||
1225 | } |
||
1226 | |||
1227 | //create a copy of an object. returns new object number |
||
1228 | imobjptridx_t obj_create_copy(const object &srcobj, const vmsegptridx_t newsegnum) |
||
1229 | { |
||
1230 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1231 | // Find next free object |
||
1232 | const auto &&obj = obj_allocate(LevelUniqueObjectState); |
||
1233 | |||
1234 | if (obj == object_none) |
||
1235 | return object_none; |
||
1236 | |||
1237 | const auto signature = obj->signature; |
||
1238 | *obj = srcobj; |
||
1239 | |||
1240 | obj_link_unchecked(Objects.vmptr, obj, newsegnum); |
||
1241 | obj->signature = object_signature_t(signature.get() + 1); |
||
1242 | |||
1243 | //we probably should initialize sub-structures here |
||
1244 | |||
1245 | return obj; |
||
1246 | } |
||
1247 | |||
1248 | //remove object from the world |
||
1249 | void obj_delete(d_level_unique_object_state &LevelUniqueObjectState, segment_array &Segments, const vmobjptridx_t obj) |
||
1250 | { |
||
1251 | auto &Objects = LevelUniqueObjectState.get_objects(); |
||
1252 | Assert(obj->type != OBJ_NONE); |
||
1253 | Assert(obj != ConsoleObject); |
||
1254 | |||
1255 | #if defined(DXX_BUILD_DESCENT_II) |
||
1256 | if (obj->type==OBJ_WEAPON && get_weapon_id(obj)==weapon_id_type::GUIDEDMISS_ID && obj->ctype.laser_info.parent_type==OBJ_PLAYER) |
||
1257 | { |
||
1258 | const auto pnum = get_player_id(Objects.vcptr(obj->ctype.laser_info.parent_num)); |
||
1259 | const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(Objects.vmptridx, pnum); |
||
1260 | if (gimobj == obj) |
||
1261 | { |
||
1262 | LevelUniqueObjectState.Guided_missile.clear_player_active_guided_missile(pnum); |
||
1263 | if (pnum == Player_num) |
||
1264 | { |
||
1265 | if (!PlayerCfg.GuidedInBigWindow) |
||
1266 | do_cockpit_window_view(1, WBU_STATIC); |
||
1267 | if (Newdemo_state == ND_STATE_RECORDING) |
||
1268 | newdemo_record_guided_end(); |
||
1269 | } |
||
1270 | } |
||
1271 | } |
||
1272 | #endif |
||
1273 | |||
1274 | if (obj == Viewer) //deleting the viewer? |
||
1275 | Viewer = ConsoleObject; //..make the player the viewer |
||
1276 | |||
1277 | if (obj->flags & OF_ATTACHED) //detach this from object |
||
1278 | obj_detach_one(Objects, obj); |
||
1279 | |||
1280 | if (obj->attached_obj != object_none) //detach all objects from this |
||
1281 | obj_detach_all(Objects, obj); |
||
1282 | |||
1283 | if (obj->type == OBJ_DEBRIS) |
||
1284 | -- LevelUniqueObjectState.Debris_object_count; |
||
1285 | |||
1286 | if (obj->movement_type == MT_PHYSICS && (obj->mtype.phys_info.flags & PF_STICK)) |
||
1287 | LevelUniqueStuckObjectState.remove_stuck_object(obj); |
||
1288 | obj_unlink(Objects.vmptr, Segments.vmptr, obj); |
||
1289 | const auto signature = obj->signature; |
||
1290 | DXX_POISON_VAR(*obj, 0xfa); |
||
1291 | obj->type = OBJ_NONE; //unused! |
||
1292 | /* Preserve signature across the poison value. When the object slot |
||
1293 | * is reused, the allocator will need the old signature so that the |
||
1294 | * new one can be derived from it. No other sites should read it |
||
1295 | * until that happens. |
||
1296 | */ |
||
1297 | obj->signature = signature; |
||
1298 | obj_free(LevelUniqueObjectState, obj); |
||
1299 | } |
||
1300 | |||
1301 | #define DEATH_SEQUENCE_LENGTH (F1_0*5) |
||
1302 | #define DEATH_SEQUENCE_EXPLODE_TIME (F1_0*2) |
||
1303 | |||
1304 | object *Dead_player_camera = NULL; // Object index of object watching deader. |
||
1305 | static const object *Viewer_save; |
||
1306 | } |
||
1307 | namespace dcx { |
||
1308 | player_dead_state Player_dead_state = player_dead_state::no; // If !0, then player is dead, but game continues so he can watch. |
||
1309 | static int Player_flags_save; |
||
1310 | static fix Camera_to_player_dist_goal = F1_0*4; |
||
1311 | static uint8_t Control_type_save; |
||
1312 | static render_type_t Render_type_save; |
||
1313 | |||
1314 | unsigned laser_parent_is_matching_signature(const laser_parent &l, const object_base &o) |
||
1315 | { |
||
1316 | if (l.parent_type != o.type) |
||
1317 | return 0; |
||
1318 | return l.parent_signature == o.signature; |
||
1319 | } |
||
1320 | |||
1321 | } |
||
1322 | |||
1323 | namespace dsx { |
||
1324 | // ------------------------------------------------------------------------------------------------------------------ |
||
1325 | void dead_player_end(void) |
||
1326 | { |
||
1327 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1328 | auto &vmobjptridx = Objects.vmptridx; |
||
1329 | if (Player_dead_state == player_dead_state::no) |
||
1330 | return; |
||
1331 | |||
1332 | if (Newdemo_state == ND_STATE_RECORDING) |
||
1333 | newdemo_record_restore_cockpit(); |
||
1334 | |||
1335 | Player_dead_state = player_dead_state::no; |
||
1336 | obj_delete(LevelUniqueObjectState, Segments, vmobjptridx(Dead_player_camera)); |
||
1337 | Dead_player_camera = NULL; |
||
1338 | select_cockpit(PlayerCfg.CockpitMode[0]); |
||
1339 | Viewer = Viewer_save; |
||
1340 | ConsoleObject->type = OBJ_PLAYER; |
||
1341 | ConsoleObject->flags = Player_flags_save; |
||
1342 | |||
1343 | Assert((Control_type_save == CT_FLYING) || (Control_type_save == CT_SLEW)); |
||
1344 | |||
1345 | ConsoleObject->control_type = Control_type_save; |
||
1346 | ConsoleObject->render_type = Render_type_save; |
||
1347 | auto &player_info = ConsoleObject->ctype.player_info; |
||
1348 | player_info.powerup_flags &= ~PLAYER_FLAGS_INVULNERABLE; |
||
1349 | player_info.Player_eggs_dropped = false; |
||
1350 | } |
||
1351 | |||
1352 | // ------------------------------------------------------------------------------------------------------------------ |
||
1353 | // Camera is less than size of player away from |
||
1354 | static void set_camera_pos(vms_vector &camera_pos, const vcobjptridx_t objp) |
||
1355 | { |
||
1356 | int count = 0; |
||
1357 | fix camera_player_dist; |
||
1358 | fix far_scale; |
||
1359 | |||
1360 | camera_player_dist = vm_vec_dist_quick(camera_pos, objp->pos); |
||
1361 | |||
1362 | if (camera_player_dist < Camera_to_player_dist_goal) { |
||
1363 | // Camera is too close to player object, so move it away. |
||
1364 | fvi_query fq; |
||
1365 | fvi_info hit_data; |
||
1366 | |||
1367 | auto player_camera_vec = vm_vec_sub(camera_pos, objp->pos); |
||
1368 | if ((player_camera_vec.x == 0) && (player_camera_vec.y == 0) && (player_camera_vec.z == 0)) |
||
1369 | player_camera_vec.x += F1_0/16; |
||
1370 | |||
1371 | hit_data.hit_type = HIT_WALL; |
||
1372 | far_scale = F1_0; |
||
1373 | |||
1374 | while ((hit_data.hit_type != HIT_NONE) && (count++ < 6)) { |
||
1375 | vm_vec_normalize_quick(player_camera_vec); |
||
1376 | vm_vec_scale(player_camera_vec, Camera_to_player_dist_goal); |
||
1377 | |||
1378 | fq.p0 = &objp->pos; |
||
1379 | const auto closer_p1 = vm_vec_add(objp->pos, player_camera_vec); // This is the actual point we want to put the camera at. |
||
1380 | vm_vec_scale(player_camera_vec, far_scale); // ...but find a point 50% further away... |
||
1381 | const auto local_p1 = vm_vec_add(objp->pos, player_camera_vec); // ...so we won't have to do as many cuts. |
||
1382 | |||
1383 | fq.p1 = &local_p1; |
||
1384 | fq.startseg = objp->segnum; |
||
1385 | fq.rad = 0; |
||
1386 | fq.thisobjnum = objp; |
||
1387 | fq.ignore_obj_list.first = nullptr; |
||
1388 | fq.flags = 0; |
||
1389 | find_vector_intersection(fq, hit_data); |
||
1390 | |||
1391 | if (hit_data.hit_type == HIT_NONE) { |
||
1392 | camera_pos = closer_p1; |
||
1393 | } else { |
||
1394 | make_random_vector(player_camera_vec); |
||
1395 | far_scale = 3*F1_0/2; |
||
1396 | } |
||
1397 | } |
||
1398 | } |
||
1399 | } |
||
1400 | |||
1401 | // ------------------------------------------------------------------------------------------------------------------ |
||
1402 | window_event_result dead_player_frame() |
||
1403 | { |
||
1404 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1405 | auto &vcobjptridx = Objects.vcptridx; |
||
1406 | auto &vmobjptr = Objects.vmptr; |
||
1407 | auto &vmobjptridx = Objects.vmptridx; |
||
1408 | static fix time_dead = 0; |
||
1409 | |||
1410 | if (Player_dead_state != player_dead_state::no) |
||
1411 | { |
||
1412 | time_dead += FrameTime; |
||
1413 | |||
1414 | // If unable to create camera at time of death, create now. |
||
1415 | if (Dead_player_camera == Viewer_save) { |
||
1416 | const auto &player = get_local_plrobj(); |
||
1417 | const auto &&objnum = obj_create(OBJ_CAMERA, 0, vmsegptridx(player.segnum), player.pos, &player.orient, 0, CT_NONE, MT_NONE, RT_NONE); |
||
1418 | |||
1419 | if (objnum != object_none) |
||
1420 | Viewer = Dead_player_camera = objnum; |
||
1421 | else { |
||
1422 | Int3(); |
||
1423 | } |
||
1424 | } |
||
1425 | |||
1426 | ConsoleObject->mtype.phys_info.rotvel.x = max(0, DEATH_SEQUENCE_EXPLODE_TIME - time_dead)/4; |
||
1427 | ConsoleObject->mtype.phys_info.rotvel.y = max(0, DEATH_SEQUENCE_EXPLODE_TIME - time_dead)/2; |
||
1428 | ConsoleObject->mtype.phys_info.rotvel.z = max(0, DEATH_SEQUENCE_EXPLODE_TIME - time_dead)/3; |
||
1429 | |||
1430 | Camera_to_player_dist_goal = min(time_dead*8, F1_0*20) + ConsoleObject->size; |
||
1431 | |||
1432 | set_camera_pos(Dead_player_camera->pos, vcobjptridx(ConsoleObject)); |
||
1433 | |||
1434 | // the following line uncommented by WraithX, 4-12-00 |
||
1435 | if (time_dead < DEATH_SEQUENCE_EXPLODE_TIME + F1_0 * 2) |
||
1436 | { |
||
1437 | const auto fvec = vm_vec_sub(ConsoleObject->pos, Dead_player_camera->pos); |
||
1438 | vm_vector_2_matrix(Dead_player_camera->orient, fvec, nullptr, nullptr); |
||
1439 | Dead_player_camera->mtype.phys_info = ConsoleObject->mtype.phys_info; |
||
1440 | |||
1441 | // the following "if" added by WraithX to get rid of camera "wiggle" |
||
1442 | Dead_player_camera->mtype.phys_info.flags &= ~PF_WIGGLE; |
||
1443 | // end "if" added by WraithX, 4/13/00 |
||
1444 | |||
1445 | // the following line uncommented by WraithX, 4-12-00 |
||
1446 | } |
||
1447 | else |
||
1448 | { |
||
1449 | // the following line uncommented by WraithX, 4-11-00 |
||
1450 | Dead_player_camera->movement_type = MT_PHYSICS; |
||
1451 | //Dead_player_camera->mtype.phys_info.rotvel.y = F1_0/8; |
||
1452 | // the following line uncommented by WraithX, 4-12-00 |
||
1453 | } |
||
1454 | // end addition by WX |
||
1455 | |||
1456 | if (time_dead > DEATH_SEQUENCE_EXPLODE_TIME) { |
||
1457 | if (Player_dead_state != player_dead_state::exploded) |
||
1458 | { |
||
1459 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
1460 | const auto hostages_lost = std::exchange(player_info.mission.hostages_on_board, 0); |
||
1461 | |||
1462 | if (hostages_lost > 1) |
||
1463 | HUD_init_message(HM_DEFAULT, TXT_SHIP_DESTROYED_2, hostages_lost); |
||
1464 | else |
||
1465 | HUD_init_message_literal(HM_DEFAULT, hostages_lost == 1 ? TXT_SHIP_DESTROYED_1 : TXT_SHIP_DESTROYED_0); |
||
1466 | |||
1467 | Player_dead_state = player_dead_state::exploded; |
||
1468 | |||
1469 | const auto cobjp = vmobjptridx(ConsoleObject); |
||
1470 | drop_player_eggs(cobjp); |
||
1471 | player_info.Player_eggs_dropped = true; |
||
1472 | if (Game_mode & GM_MULTI) |
||
1473 | { |
||
1474 | multi_send_player_deres(deres_explode); |
||
1475 | } |
||
1476 | |||
1477 | explode_badass_player(cobjp); |
||
1478 | |||
1479 | //is this next line needed, given the badass call above? |
||
1480 | explode_object(cobjp,0); |
||
1481 | ConsoleObject->flags &= ~OF_SHOULD_BE_DEAD; //don't really kill player |
||
1482 | ConsoleObject->render_type = RT_NONE; //..just make him disappear |
||
1483 | ConsoleObject->type = OBJ_GHOST; //..and kill intersections |
||
1484 | #if defined(DXX_BUILD_DESCENT_II) |
||
1485 | player_info.powerup_flags &= ~PLAYER_FLAGS_HEADLIGHT_ON; |
||
1486 | #endif |
||
1487 | } |
||
1488 | } else { |
||
1489 | if (d_rand() < FrameTime*4) { |
||
1490 | if (Game_mode & GM_MULTI) |
||
1491 | multi_send_create_explosion(Player_num); |
||
1492 | create_small_fireball_on_object(vmobjptridx(ConsoleObject), F1_0, 1); |
||
1493 | } |
||
1494 | } |
||
1495 | |||
1496 | |||
1497 | if (GameViewUniqueState.Death_sequence_aborted) |
||
1498 | { |
||
1499 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
1500 | if (!player_info.Player_eggs_dropped) { |
||
1501 | player_info.Player_eggs_dropped = true; |
||
1502 | drop_player_eggs(vmobjptridx(ConsoleObject)); |
||
1503 | if (Game_mode & GM_MULTI) |
||
1504 | { |
||
1505 | multi_send_player_deres(deres_explode); |
||
1506 | } |
||
1507 | } |
||
1508 | |||
1509 | return DoPlayerDead(); //kill_player(); |
||
1510 | } |
||
1511 | } |
||
1512 | else |
||
1513 | time_dead = 0; |
||
1514 | |||
1515 | return window_event_result::handled; |
||
1516 | } |
||
1517 | |||
1518 | // ------------------------------------------------------------------------------------------------------------------ |
||
1519 | static void start_player_death_sequence(object &player) |
||
1520 | { |
||
1521 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1522 | #if defined(DXX_BUILD_DESCENT_II) |
||
1523 | auto &vmobjptr = Objects.vmptr; |
||
1524 | #endif |
||
1525 | auto &vmobjptridx = Objects.vmptridx; |
||
1526 | assert(&player == ConsoleObject); |
||
1527 | if (Player_dead_state != player_dead_state::no || |
||
1528 | Dead_player_camera != NULL || |
||
1529 | ((Game_mode & GM_MULTI) && (get_local_player().connected != CONNECT_PLAYING))) |
||
1530 | return; |
||
1531 | |||
1532 | //Assert(Dead_player_camera == NULL); |
||
1533 | |||
1534 | reset_rear_view(); |
||
1535 | |||
1536 | if (!(Game_mode & GM_MULTI)) |
||
1537 | HUD_clear_messages(); |
||
1538 | |||
1539 | GameViewUniqueState.Death_sequence_aborted = 0; |
||
1540 | |||
1541 | if (Game_mode & GM_MULTI) |
||
1542 | { |
||
1543 | #if defined(DXX_BUILD_DESCENT_II) |
||
1544 | // If Hoard, increase number of orbs by 1. Only if you haven't killed yourself. This prevents cheating |
||
1545 | if (game_mode_hoard()) |
||
1546 | { |
||
1547 | auto &player_info = player.ctype.player_info; |
||
1548 | auto &proximity = player_info.hoard.orbs; |
||
1549 | if (proximity < player_info.max_hoard_orbs) |
||
1550 | { |
||
1551 | const auto is_bad_kill = [&vmobjptr]{ |
||
1552 | auto &lplr = get_local_player(); |
||
1553 | auto &lplrobj = get_local_plrobj(); |
||
1554 | const auto killer_objnum = lplrobj.ctype.player_info.killer_objnum; |
||
1555 | if (killer_objnum == lplr.objnum) |
||
1556 | /* Self kill */ |
||
1557 | return true; |
||
1558 | if (killer_objnum == object_none) |
||
1559 | /* Non-player kill */ |
||
1560 | return true; |
||
1561 | const auto &&killer_objp = vmobjptr(killer_objnum); |
||
1562 | if (killer_objp->type != OBJ_PLAYER) |
||
1563 | return true; |
||
1564 | if (!(Game_mode & GM_TEAM)) |
||
1565 | return false; |
||
1566 | return get_team(Player_num) == get_team(get_player_id(killer_objp)); |
||
1567 | }; |
||
1568 | if (!is_bad_kill()) |
||
1569 | ++ proximity; |
||
1570 | } |
||
1571 | } |
||
1572 | #endif |
||
1573 | multi_send_kill(vmobjptridx(get_local_player().objnum)); |
||
1574 | } |
||
1575 | |||
1576 | PaletteRedAdd = 40; |
||
1577 | Player_dead_state = player_dead_state::yes; |
||
1578 | |||
1579 | vm_vec_zero(player.mtype.phys_info.rotthrust); |
||
1580 | vm_vec_zero(player.mtype.phys_info.thrust); |
||
1581 | |||
1582 | const auto &&objnum = obj_create(OBJ_CAMERA, 0, vmsegptridx(player.segnum), player.pos, &player.orient, 0, CT_NONE, MT_NONE, RT_NONE); |
||
1583 | Viewer_save = Viewer; |
||
1584 | if (objnum != object_none) |
||
1585 | Viewer = Dead_player_camera = objnum; |
||
1586 | else { |
||
1587 | Int3(); |
||
1588 | Dead_player_camera = ConsoleObject; |
||
1589 | } |
||
1590 | |||
1591 | select_cockpit(CM_LETTERBOX); |
||
1592 | if (Newdemo_state == ND_STATE_RECORDING) |
||
1593 | newdemo_record_letterbox(); |
||
1594 | |||
1595 | Player_flags_save = player.flags; |
||
1596 | Control_type_save = player.control_type; |
||
1597 | Render_type_save = player.render_type; |
||
1598 | |||
1599 | player.flags &= ~OF_SHOULD_BE_DEAD; |
||
1600 | // Players[Player_num].flags |= PLAYER_FLAGS_INVULNERABLE; |
||
1601 | player.control_type = CT_NONE; |
||
1602 | |||
1603 | PALETTE_FLASH_SET(0,0,0); |
||
1604 | } |
||
1605 | |||
1606 | // ------------------------------------------------------------------------------------------------------------------ |
||
1607 | static void obj_delete_all_that_should_be_dead() |
||
1608 | { |
||
1609 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1610 | auto &vmobjptridx = Objects.vmptridx; |
||
1611 | objnum_t local_dead_player_object=object_none; |
||
1612 | |||
1613 | // Move all objects |
||
1614 | range_for (const auto &&objp, vmobjptridx) |
||
1615 | { |
||
1616 | if ((objp->type!=OBJ_NONE) && (objp->flags&OF_SHOULD_BE_DEAD) ) { |
||
1617 | Assert(!(objp->type==OBJ_FIREBALL && objp->ctype.expl_info.delete_time!=-1)); |
||
1618 | if (objp->type==OBJ_PLAYER) { |
||
1619 | if ( get_player_id(objp) == Player_num ) { |
||
1620 | if (local_dead_player_object == object_none) { |
||
1621 | start_player_death_sequence(objp); |
||
1622 | local_dead_player_object = objp; |
||
1623 | } else |
||
1624 | Int3(); // Contact Mike: Illegal, killed player twice in this frame! |
||
1625 | // Ok to continue, won't start death sequence again! |
||
1626 | // kill_player(); |
||
1627 | } |
||
1628 | } else { |
||
1629 | obj_delete(LevelUniqueObjectState, Segments, objp); |
||
1630 | } |
||
1631 | } |
||
1632 | } |
||
1633 | } |
||
1634 | |||
1635 | //when an object has moved into a new segment, this function unlinks it |
||
1636 | //from its old segment, and links it into the new segment |
||
1637 | void obj_relink(fvmobjptr &vmobjptr, fvmsegptr &vmsegptr, const vmobjptridx_t objnum, const vmsegptridx_t newsegnum) |
||
1638 | { |
||
1639 | obj_unlink(vmobjptr, vmsegptr, objnum); |
||
1640 | obj_link_unchecked(vmobjptr, objnum, newsegnum); |
||
1641 | } |
||
1642 | |||
1643 | // for getting out of messed up linking situations (i.e. caused by demo playback) |
||
1644 | void obj_relink_all(void) |
||
1645 | { |
||
1646 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1647 | range_for (const auto &&segp, vmsegptr) |
||
1648 | { |
||
1649 | segp->objects = object_none; |
||
1650 | } |
||
1651 | |||
1652 | range_for (const auto &&obj, Objects.vmptridx) |
||
1653 | { |
||
1654 | if (obj->type != OBJ_NONE) |
||
1655 | { |
||
1656 | auto segnum = obj->segnum; |
||
1657 | if (segnum > Highest_segment_index) |
||
1658 | segnum = segment_first; |
||
1659 | obj_link_unchecked(Objects.vmptr, obj, Segments.vmptridx(segnum)); |
||
1660 | } |
||
1661 | } |
||
1662 | } |
||
1663 | |||
1664 | //process a continuously-spinning object |
||
1665 | static void spin_object(object_base &obj) |
||
1666 | { |
||
1667 | vms_angvec rotangs; |
||
1668 | assert(obj.movement_type == MT_SPINNING); |
||
1669 | |||
1670 | const fix frametime = FrameTime; |
||
1671 | rotangs.p = fixmul(obj.mtype.spin_rate.x, frametime); |
||
1672 | rotangs.h = fixmul(obj.mtype.spin_rate.y, frametime); |
||
1673 | rotangs.b = fixmul(obj.mtype.spin_rate.z, frametime); |
||
1674 | |||
1675 | const auto &&rotmat = vm_angles_2_matrix(rotangs); |
||
1676 | obj.orient = vm_matrix_x_matrix(obj.orient, rotmat); |
||
1677 | check_and_fix_matrix(obj.orient); |
||
1678 | } |
||
1679 | |||
1680 | #if defined(DXX_BUILD_DESCENT_II) |
||
1681 | imobjidx_t d_guided_missile_indices::get_player_active_guided_missile(const unsigned pnum) const |
||
1682 | { |
||
1683 | return operator[](pnum); |
||
1684 | } |
||
1685 | |||
1686 | /* Place debug checks out of line so that they are shared among the |
||
1687 | * template instantiations. |
||
1688 | */ |
||
1689 | bool d_guided_missile_indices::debug_check_current_object(const object_base &obj) |
||
1690 | { |
||
1691 | assert(obj.type == OBJ_WEAPON); |
||
1692 | const auto gmid = get_weapon_id(obj); |
||
1693 | if (obj.type != OBJ_WEAPON) |
||
1694 | return false; |
||
1695 | assert(gmid == weapon_id_type::GUIDEDMISS_ID); |
||
1696 | if (gmid != weapon_id_type::GUIDEDMISS_ID) |
||
1697 | return false; |
||
1698 | return true; |
||
1699 | } |
||
1700 | |||
1701 | template <typename R, typename F> |
||
1702 | R d_guided_missile_indices::get_player_active_guided_missile_tmpl(F &fobjptr, const unsigned pnum) const |
||
1703 | { |
||
1704 | const auto gmidx = get_player_active_guided_missile(pnum); |
||
1705 | if (gmidx == object_none) |
||
1706 | return object_none; |
||
1707 | auto &&gmobj = fobjptr(gmidx); |
||
1708 | if (!debug_check_current_object(gmobj)) |
||
1709 | return object_none; |
||
1710 | return gmobj; |
||
1711 | } |
||
1712 | |||
1713 | imobjptr_t d_guided_missile_indices::get_player_active_guided_missile(fvmobjptr &vmobjptr, const unsigned pnum) const |
||
1714 | { |
||
1715 | return this->template get_player_active_guided_missile_tmpl<imobjptr_t>(vmobjptr, pnum); |
||
1716 | } |
||
1717 | |||
1718 | imobjptridx_t d_guided_missile_indices::get_player_active_guided_missile(fvmobjptridx &vmobjptridx, const unsigned pnum) const |
||
1719 | { |
||
1720 | return this->template get_player_active_guided_missile_tmpl<imobjptridx_t>(vmobjptridx, pnum); |
||
1721 | } |
||
1722 | |||
1723 | void d_guided_missile_indices::set_player_active_guided_missile(const vmobjidx_t obji, const unsigned pnum) |
||
1724 | { |
||
1725 | auto &i = operator[](pnum); |
||
1726 | i = obji; |
||
1727 | } |
||
1728 | |||
1729 | void d_guided_missile_indices::clear_player_active_guided_missile(const unsigned pnum) |
||
1730 | { |
||
1731 | auto &i = operator[](pnum); |
||
1732 | i = object_none; |
||
1733 | } |
||
1734 | |||
1735 | int Drop_afterburner_blob_flag; //ugly hack |
||
1736 | //see if wall is volatile, and if so, cause damage to player |
||
1737 | //returns true if player is in lava |
||
1738 | #endif |
||
1739 | |||
1740 | //-------------------------------------------------------------------- |
||
1741 | //move an object for the current frame |
||
1742 | static window_event_result object_move_one(const vmobjptridx_t obj) |
||
1743 | { |
||
1744 | #if defined(DXX_BUILD_DESCENT_II) |
||
1745 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
1746 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
1747 | #endif |
||
1748 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1749 | auto &vmobjptr = Objects.vmptr; |
||
1750 | const auto previous_segment = obj->segnum; |
||
1751 | auto result = window_event_result::handled; |
||
1752 | |||
1753 | const auto obj_previous_position = obj->pos; // Save the current position |
||
1754 | |||
1755 | if ((obj->type==OBJ_PLAYER) && (Player_num==get_player_id(obj))) { |
||
1756 | const auto &&segp = vmsegptr(obj->segnum); |
||
1757 | #if defined(DXX_BUILD_DESCENT_II) |
||
1758 | if (game_mode_capture_flag()) |
||
1759 | fuelcen_check_for_goal(obj, segp); |
||
1760 | else if (game_mode_hoard()) |
||
1761 | fuelcen_check_for_hoard_goal(obj, segp); |
||
1762 | #endif |
||
1763 | |||
1764 | auto &player_info = obj->ctype.player_info; |
||
1765 | auto &energy = player_info.energy; |
||
1766 | const fix fuel = fuelcen_give_fuel(segp, INITIAL_ENERGY - energy); |
||
1767 | if (fuel > 0 ) { |
||
1768 | energy += fuel; |
||
1769 | } |
||
1770 | #if defined(DXX_BUILD_DESCENT_II) |
||
1771 | auto &pl_shields = get_local_plrobj().shields; |
||
1772 | const fix shields = repaircen_give_shields(segp, INITIAL_SHIELDS - pl_shields); |
||
1773 | if (shields > 0) { |
||
1774 | pl_shields += shields; |
||
1775 | } |
||
1776 | #endif |
||
1777 | } |
||
1778 | |||
1779 | { |
||
1780 | auto lifeleft = obj->lifeleft; |
||
1781 | if (lifeleft != IMMORTAL_TIME) //if not immortal... |
||
1782 | { |
||
1783 | lifeleft -= FrameTime; //...inevitable countdown towards death |
||
1784 | #if defined(DXX_BUILD_DESCENT_II) |
||
1785 | if (obj->type == OBJ_MARKER) |
||
1786 | { |
||
1787 | if (lifeleft < F1_0*1000) |
||
1788 | lifeleft += F1_0; // Make sure this object doesn't go out. |
||
1789 | } |
||
1790 | #endif |
||
1791 | obj->lifeleft = lifeleft; |
||
1792 | } |
||
1793 | } |
||
1794 | #if defined(DXX_BUILD_DESCENT_II) |
||
1795 | Drop_afterburner_blob_flag = 0; |
||
1796 | #endif |
||
1797 | |||
1798 | switch (obj->control_type) { |
||
1799 | |||
1800 | case CT_NONE: break; |
||
1801 | |||
1802 | case CT_FLYING: |
||
1803 | |||
1804 | read_flying_controls( obj ); |
||
1805 | |||
1806 | break; |
||
1807 | |||
1808 | case CT_REPAIRCEN: |
||
1809 | Int3(); |
||
1810 | // -- hey! these are no longer supported!! -- do_repair_sequence(obj); |
||
1811 | break; |
||
1812 | |||
1813 | case CT_POWERUP: |
||
1814 | do_powerup_frame(Vclip, obj); |
||
1815 | break; |
||
1816 | |||
1817 | case CT_MORPH: //morph implies AI |
||
1818 | do_morph_frame(obj); |
||
1819 | //NOTE: FALLS INTO AI HERE!!!! |
||
1820 | DXX_BOOST_FALLTHROUGH; |
||
1821 | |||
1822 | case CT_AI: |
||
1823 | //NOTE LINK TO CT_MORPH ABOVE!!! |
||
1824 | if (Game_suspended & SUSP_ROBOTS) return window_event_result::ignored; |
||
1825 | do_ai_frame(obj); |
||
1826 | break; |
||
1827 | |||
1828 | case CT_WEAPON: Laser_do_weapon_sequence(obj); break; |
||
1829 | case CT_EXPLOSION: do_explosion_sequence(obj); break; |
||
1830 | |||
1831 | case CT_SLEW: |
||
1832 | #ifdef RELEASE |
||
1833 | obj->control_type = CT_NONE; |
||
1834 | con_printf(CON_URGENT, DXX_STRINGIZE_FL(__FILE__, __LINE__, "BUG: object %hu has control type CT_SLEW, sig/type/id = %i/%i/%i"), static_cast<objnum_t>(obj), obj->signature.get(), obj->type, obj->id); |
||
1835 | #else |
||
1836 | if ( keyd_pressed[KEY_PAD5] ) slew_stop(); |
||
1837 | if ( keyd_pressed[KEY_NUMLOCK] ) { |
||
1838 | slew_reset_orient(); |
||
1839 | } |
||
1840 | slew_frame(0 ); // Does velocity addition for us. |
||
1841 | #endif |
||
1842 | break; |
||
1843 | |||
1844 | // case CT_FLYTHROUGH: |
||
1845 | // do_flythrough(obj,0); // HACK:do_flythrough should operate on an object!!!! |
||
1846 | // //check_object_seg(obj); |
||
1847 | // return; // DON'T DO THE REST OF OBJECT STUFF SINCE THIS IS A SPECIAL CASE!!! |
||
1848 | // break; |
||
1849 | |||
1850 | case CT_DEBRIS: do_debris_frame(obj); break; |
||
1851 | |||
1852 | case CT_LIGHT: break; //doesn't do anything |
||
1853 | |||
1854 | case CT_REMOTE: break; //doesn't do anything |
||
1855 | |||
1856 | case CT_CNTRLCEN: do_controlcen_frame(obj); break; |
||
1857 | |||
1858 | default: |
||
1859 | |||
1860 | Error("Unknown control type %d in object %hu, sig/type/id = %i/%i/%i",obj->control_type, static_cast<objnum_t>(obj), obj->signature.get(), obj->type, obj->id); |
||
1861 | |||
1862 | break; |
||
1863 | |||
1864 | } |
||
1865 | |||
1866 | if (obj->lifeleft < 0 ) { // We died of old age |
||
1867 | obj->flags |= OF_SHOULD_BE_DEAD; |
||
1868 | if ( obj->type==OBJ_WEAPON && Weapon_info[get_weapon_id(obj)].damage_radius ) |
||
1869 | explode_badass_weapon(obj, obj->pos); |
||
1870 | #if defined(DXX_BUILD_DESCENT_II) |
||
1871 | else if ( obj->type==OBJ_ROBOT) //make robots explode |
||
1872 | explode_object(obj,0); |
||
1873 | #endif |
||
1874 | } |
||
1875 | |||
1876 | if (obj->type == OBJ_NONE || obj->flags&OF_SHOULD_BE_DEAD) |
||
1877 | return window_event_result::ignored; // object has been deleted |
||
1878 | |||
1879 | bool prepare_seglist = false; |
||
1880 | phys_visited_seglist phys_visited_segs; |
||
1881 | switch (obj->movement_type) { |
||
1882 | |||
1883 | case MT_NONE: break; //this doesn't move |
||
1884 | |||
1885 | case MT_PHYSICS: //move by physics |
||
1886 | result = do_physics_sim(obj, obj_previous_position, obj->type == OBJ_PLAYER ? (prepare_seglist = true, phys_visited_segs.nsegs = 0, &phys_visited_segs) : nullptr); |
||
1887 | break; |
||
1888 | |||
1889 | case MT_SPINNING: spin_object(obj); break; |
||
1890 | |||
1891 | } |
||
1892 | |||
1893 | #if defined(DXX_BUILD_DESCENT_II) |
||
1894 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
1895 | auto &vcwallptr = Walls.vcptr; |
||
1896 | #endif |
||
1897 | // If player and moved to another segment, see if hit any triggers. |
||
1898 | // also check in player under a lavafall |
||
1899 | if (prepare_seglist) |
||
1900 | { |
||
1901 | if (previous_segment != obj->segnum && phys_visited_segs.nsegs > 1) |
||
1902 | { |
||
1903 | auto seg0 = vmsegptridx(phys_visited_segs.seglist[0]); |
||
1904 | #if defined(DXX_BUILD_DESCENT_II) |
||
1905 | int old_level = Current_level_num; |
||
1906 | #endif |
||
1907 | range_for (const auto i, partial_const_range(phys_visited_segs.seglist, 1u, phys_visited_segs.nsegs)) |
||
1908 | { |
||
1909 | const auto &&seg1 = seg0.absolute_sibling(i); |
||
1910 | const auto connect_side = find_connect_side(seg1, seg0); |
||
1911 | if (connect_side != side_none) |
||
1912 | { |
||
1913 | result = check_trigger(seg0, connect_side, get_local_plrobj(), obj, 0); |
||
1914 | #if defined(DXX_BUILD_DESCENT_II) |
||
1915 | //maybe we've gone on to the next level. if so, bail! |
||
1916 | if (Current_level_num != old_level) |
||
1917 | return window_event_result::ignored; |
||
1918 | #endif |
||
1919 | } |
||
1920 | seg0 = seg1; |
||
1921 | } |
||
1922 | } |
||
1923 | #if defined(DXX_BUILD_DESCENT_II) |
||
1924 | { |
||
1925 | bool under_lavafall = false; |
||
1926 | |||
1927 | auto &playing = obj->ctype.player_info.lavafall_hiss_playing; |
||
1928 | const auto &&segp = vcsegptr(obj->segnum); |
||
1929 | auto &vcvertptr = Vertices.vcptr; |
||
1930 | if (const auto sidemask = get_seg_masks(vcvertptr, obj->pos, segp, obj->size).sidemask) |
||
1931 | { |
||
1932 | range_for (const unsigned sidenum, xrange(MAX_SIDES_PER_SEGMENT)) |
||
1933 | { |
||
1934 | if (!(sidemask & (1 << sidenum))) |
||
1935 | continue; |
||
1936 | const auto wall_num = segp->shared_segment::sides[sidenum].wall_num; |
||
1937 | if (wall_num != wall_none && vcwallptr(wall_num)->type == WALL_ILLUSION) |
||
1938 | { |
||
1939 | const auto type = check_volatile_wall(obj, segp->unique_segment::sides[sidenum]); |
||
1940 | if (type != volatile_wall_result::none) |
||
1941 | { |
||
1942 | under_lavafall = 1; |
||
1943 | if (!playing) |
||
1944 | { |
||
1945 | playing = 1; |
||
1946 | const auto sound = (type == volatile_wall_result::lava) ? SOUND_LAVAFALL_HISS : SOUND_SHIP_IN_WATERFALL; |
||
1947 | digi_link_sound_to_object3(sound, obj, 1, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)}, -1, -1); |
||
1948 | break; |
||
1949 | } |
||
1950 | } |
||
1951 | } |
||
1952 | } |
||
1953 | } |
||
1954 | |||
1955 | if (!under_lavafall && playing) |
||
1956 | { |
||
1957 | playing = 0; |
||
1958 | digi_kill_sound_linked_to_object( obj); |
||
1959 | } |
||
1960 | } |
||
1961 | #endif |
||
1962 | } |
||
1963 | |||
1964 | #if defined(DXX_BUILD_DESCENT_II) |
||
1965 | //see if guided missile has flown through exit trigger |
||
1966 | if (obj == LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(Player_num)) |
||
1967 | { |
||
1968 | if (previous_segment != obj->segnum) { |
||
1969 | const auto &&psegp = vcsegptr(previous_segment); |
||
1970 | const auto &&connect_side = find_connect_side(vcsegptridx(obj->segnum), psegp); |
||
1971 | if (connect_side != side_none) |
||
1972 | { |
||
1973 | const auto wall_num = psegp->shared_segment::sides[connect_side].wall_num; |
||
1974 | if ( wall_num != wall_none ) { |
||
1975 | auto trigger_num = vcwallptr(wall_num)->trigger; |
||
1976 | if (trigger_num != trigger_none) |
||
1977 | { |
||
1978 | auto &Triggers = LevelUniqueWallSubsystemState.Triggers; |
||
1979 | auto &vctrgptr = Triggers.vcptr; |
||
1980 | const auto &&t = vctrgptr(trigger_num); |
||
1981 | if (t->type == trigger_action::normal_exit) |
||
1982 | obj->lifeleft = 0; |
||
1983 | } |
||
1984 | } |
||
1985 | } |
||
1986 | } |
||
1987 | } |
||
1988 | |||
1989 | if (Drop_afterburner_blob_flag) { |
||
1990 | Assert(obj==ConsoleObject); |
||
1991 | drop_afterburner_blobs(obj, 2, i2f(5)/2, -1); // -1 means use default lifetime |
||
1992 | if (Game_mode & GM_MULTI) |
||
1993 | multi_send_drop_blobs(Player_num); |
||
1994 | Drop_afterburner_blob_flag = 0; |
||
1995 | } |
||
1996 | |||
1997 | if ((obj->type == OBJ_WEAPON) && (Weapon_info[get_weapon_id(obj)].afterburner_size)) { |
||
1998 | fix vel = vm_vec_mag_quick(obj->mtype.phys_info.velocity); |
||
1999 | fix delay, lifetime; |
||
2000 | |||
2001 | if (vel > F1_0*200) |
||
2002 | delay = F1_0/16; |
||
2003 | else if (vel > F1_0*40) |
||
2004 | delay = fixdiv(F1_0*13,vel); |
||
2005 | else |
||
2006 | delay = F1_0/4; |
||
2007 | |||
2008 | lifetime = (delay * 3)/2; |
||
2009 | if (!(Game_mode & GM_MULTI)) { |
||
2010 | delay /= 2; |
||
2011 | lifetime *= 2; |
||
2012 | } |
||
2013 | |||
2014 | assert(obj->control_type == CT_WEAPON); |
||
2015 | if ((obj->ctype.laser_info.last_afterburner_time + delay < GameTime64) || (obj->ctype.laser_info.last_afterburner_time > GameTime64)) { |
||
2016 | drop_afterburner_blobs(obj, 1, i2f(Weapon_info[get_weapon_id(obj)].afterburner_size)/16, lifetime); |
||
2017 | obj->ctype.laser_info.last_afterburner_time = GameTime64; |
||
2018 | } |
||
2019 | } |
||
2020 | |||
2021 | #endif |
||
2022 | return result; |
||
2023 | } |
||
2024 | |||
2025 | //-------------------------------------------------------------------- |
||
2026 | //move all objects for the current frame |
||
2027 | static window_event_result object_move_all() |
||
2028 | { |
||
2029 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2030 | auto &vmobjptridx = Objects.vmptridx; |
||
2031 | auto result = window_event_result::ignored; |
||
2032 | |||
2033 | if (Highest_object_index > MAX_USED_OBJECTS) |
||
2034 | free_object_slots(MAX_USED_OBJECTS); // Free all possible object slots. |
||
2035 | |||
2036 | obj_delete_all_that_should_be_dead(); |
||
2037 | |||
2038 | if (PlayerCfg.AutoLeveling) |
||
2039 | ConsoleObject->mtype.phys_info.flags |= PF_LEVELLING; |
||
2040 | else |
||
2041 | ConsoleObject->mtype.phys_info.flags &= ~PF_LEVELLING; |
||
2042 | |||
2043 | // Move all objects |
||
2044 | range_for (const auto &&objp, vmobjptridx) |
||
2045 | { |
||
2046 | if ( (objp->type != OBJ_NONE) && (!(objp->flags&OF_SHOULD_BE_DEAD)) ) { |
||
2047 | result = std::max(object_move_one( objp ), result); |
||
2048 | } |
||
2049 | } |
||
2050 | |||
2051 | // check_duplicate_objects(); |
||
2052 | // remove_incorrect_objects(); |
||
2053 | |||
2054 | return result; |
||
2055 | } |
||
2056 | |||
2057 | window_event_result game_move_all_objects() |
||
2058 | { |
||
2059 | LevelUniqueObjectState.last_console_player_position = ConsoleObject->pos; |
||
2060 | return object_move_all(); |
||
2061 | } |
||
2062 | |||
2063 | window_event_result endlevel_move_all_objects() |
||
2064 | { |
||
2065 | return object_move_all(); |
||
2066 | } |
||
2067 | |||
2068 | //--unused-- // ----------------------------------------------------------- |
||
2069 | //--unused-- // Moved here from eobject.c on 02/09/94 by MK. |
||
2070 | //--unused-- int find_last_obj(int i) |
||
2071 | //--unused-- { |
||
2072 | //--unused-- for (i=MAX_OBJECTS;--i>=0;) |
||
2073 | //--unused-- if (Objects[i].type != OBJ_NONE) break; |
||
2074 | //--unused-- |
||
2075 | //--unused-- return i; |
||
2076 | //--unused-- |
||
2077 | //--unused-- } |
||
2078 | |||
2079 | |||
2080 | //make object array non-sparse |
||
2081 | void compress_objects(void) |
||
2082 | { |
||
2083 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2084 | auto &vmobjptr = Objects.vmptr; |
||
2085 | auto &vmobjptridx = Objects.vmptridx; |
||
2086 | //last_i = find_last_obj(MAX_OBJECTS); |
||
2087 | |||
2088 | // Note: It's proper to do < (rather than <=) Highest_object_index here because we |
||
2089 | // are just removing gaps, and the last object can't be a gap. |
||
2090 | for (objnum_t start_i=0;start_i<Highest_object_index;start_i++) |
||
2091 | { |
||
2092 | const auto &&start_objp = vmobjptridx(start_i); |
||
2093 | if (start_objp->type == OBJ_NONE) { |
||
2094 | auto highest = Highest_object_index; |
||
2095 | const auto &&h = vmobjptr(static_cast<objnum_t>(highest)); |
||
2096 | auto segnum_copy = h->segnum; |
||
2097 | |||
2098 | obj_unlink(Objects.vmptr, Segments.vmptr, h); |
||
2099 | |||
2100 | *start_objp = *h; |
||
2101 | |||
2102 | #if DXX_USE_EDITOR |
||
2103 | if (Cur_object_index == Highest_object_index) |
||
2104 | Cur_object_index = start_i; |
||
2105 | #endif |
||
2106 | |||
2107 | h->type = OBJ_NONE; |
||
2108 | |||
2109 | obj_link(Objects.vmptr, start_objp, vmsegptridx(segnum_copy)); |
||
2110 | |||
2111 | while (vmobjptr(static_cast<objnum_t>(--highest))->type == OBJ_NONE) |
||
2112 | { |
||
2113 | } |
||
2114 | Objects.set_count(highest + 1); |
||
2115 | |||
2116 | //last_i = find_last_obj(last_i); |
||
2117 | |||
2118 | } |
||
2119 | } |
||
2120 | reset_objects(LevelUniqueObjectState, LevelUniqueObjectState.num_objects); |
||
2121 | } |
||
2122 | |||
2123 | //called after load. Takes number of objects, and objects should be |
||
2124 | //compressed. resets free list, marks unused objects as unused |
||
2125 | void reset_objects(d_level_unique_object_state &LevelUniqueObjectState, const unsigned n_objs) |
||
2126 | { |
||
2127 | LevelUniqueObjectState.Debris_object_count = 0; |
||
2128 | LevelUniqueObjectState.num_objects = n_objs; |
||
2129 | assert(LevelUniqueObjectState.num_objects > 0); |
||
2130 | auto &Objects = LevelUniqueObjectState.get_objects(); |
||
2131 | assert(LevelUniqueObjectState.num_objects < Objects.size()); |
||
2132 | Objects.set_count(n_objs); |
||
2133 | |||
2134 | for (objnum_t i = n_objs; i < MAX_OBJECTS; ++i) |
||
2135 | { |
||
2136 | LevelUniqueObjectState.free_obj_list[i] = i; |
||
2137 | auto &obj = *Objects.vmptr(i); |
||
2138 | DXX_POISON_VAR(obj, 0xfd); |
||
2139 | obj.type = OBJ_NONE; |
||
2140 | obj.signature = object_signature_t{0}; |
||
2141 | } |
||
2142 | } |
||
2143 | |||
2144 | //Tries to find a segment for an object, using find_point_seg() |
||
2145 | imsegptridx_t find_object_seg(const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, const object_base &obj) |
||
2146 | { |
||
2147 | auto &Segments = LevelUniqueSegmentState.get_segments(); |
||
2148 | return find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj.pos, Segments.vmptridx(obj.segnum)); |
||
2149 | } |
||
2150 | |||
2151 | |||
2152 | //If an object is in a segment, set its segnum field and make sure it's |
||
2153 | //properly linked. If not in any segment, returns 0, else 1. |
||
2154 | //callers should generally use find_vector_intersection() |
||
2155 | int update_object_seg(fvmobjptr &vmobjptr, const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, const vmobjptridx_t obj) |
||
2156 | { |
||
2157 | const auto &&newseg = find_object_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj); |
||
2158 | if (newseg == segment_none) |
||
2159 | return 0; |
||
2160 | |||
2161 | if ( newseg != obj->segnum ) |
||
2162 | obj_relink(vmobjptr, LevelUniqueSegmentState.get_segments().vmptr, obj, newseg); |
||
2163 | |||
2164 | return 1; |
||
2165 | } |
||
2166 | |||
2167 | unsigned laser_parent_is_player(fvcobjptr &vcobjptr, const laser_parent &l, const object_base &o) |
||
2168 | { |
||
2169 | /* Player objects are never recycled, so skip the signature check. |
||
2170 | */ |
||
2171 | if (l.parent_type != OBJ_PLAYER) |
||
2172 | return 0; |
||
2173 | /* As a special case, let the player be recognized even if he died |
||
2174 | * before the weapon hit the target. |
||
2175 | */ |
||
2176 | if (o.type != OBJ_PLAYER && o.type != OBJ_GHOST) |
||
2177 | return 0; |
||
2178 | auto &parent_object = *vcobjptr(l.parent_num); |
||
2179 | return (&parent_object == &o); |
||
2180 | } |
||
2181 | |||
2182 | unsigned laser_parent_is_object(fvcobjptr &vcobjptr, const laser_parent &l, const object_base &o) |
||
2183 | { |
||
2184 | auto &parent_object = *vcobjptr(l.parent_num); |
||
2185 | if (&parent_object != &o) |
||
2186 | return 0; |
||
2187 | return laser_parent_is_matching_signature(l, o); |
||
2188 | } |
||
2189 | |||
2190 | unsigned laser_parent_is_object(const laser_parent &l, const vcobjptridx_t o) |
||
2191 | { |
||
2192 | if (l.parent_num != o.get_unchecked_index()) |
||
2193 | return 0; |
||
2194 | return laser_parent_is_matching_signature(l, *o); |
||
2195 | } |
||
2196 | |||
2197 | unsigned laser_parent_object_exists(fvcobjptr &vcobjptr, const laser_parent &l) |
||
2198 | { |
||
2199 | return laser_parent_is_matching_signature(l, *vcobjptr(l.parent_num)); |
||
2200 | } |
||
2201 | |||
2202 | void set_powerup_id(const d_powerup_info_array &Powerup_info, const d_vclip_array &Vclip, object_base &o, powerup_type_t id) |
||
2203 | { |
||
2204 | o.id = id; |
||
2205 | o.size = Powerup_info[id].size; |
||
2206 | const auto vclip_num = Powerup_info[id].vclip_num; |
||
2207 | o.rtype.vclip_info.vclip_num = vclip_num; |
||
2208 | o.rtype.vclip_info.frametime = Vclip[vclip_num].frame_time; |
||
2209 | } |
||
2210 | |||
2211 | //go through all objects and make sure they have the correct segment numbers |
||
2212 | void fix_object_segs() |
||
2213 | { |
||
2214 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
2215 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2216 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
2217 | auto &vmobjptr = Objects.vmptr; |
||
2218 | auto &vmobjptridx = Objects.vmptridx; |
||
2219 | auto &vcvertptr = Vertices.vcptr; |
||
2220 | range_for (const auto &&o, vmobjptridx) |
||
2221 | { |
||
2222 | if (o->type != OBJ_NONE) |
||
2223 | { |
||
2224 | const auto oldsegnum = o->segnum; |
||
2225 | if (update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, o) == 0) |
||
2226 | { |
||
2227 | const auto pos = o->pos; |
||
2228 | const auto segnum = o->segnum; |
||
2229 | compute_segment_center(vcvertptr, o->pos, vcsegptr(segnum)); |
||
2230 | con_printf(CON_URGENT, "Object %hu claims segment %hu, but has position {%i,%i,%i}; moving to %hu:{%i,%i,%i}", o.get_unchecked_index(), oldsegnum, pos.x, pos.y, pos.z, segnum, o->pos.x, o->pos.y, o->pos.z); |
||
2231 | } |
||
2232 | } |
||
2233 | } |
||
2234 | } |
||
2235 | |||
2236 | |||
2237 | //--unused-- void object_use_new_object_list( object * new_list ) |
||
2238 | //--unused-- { |
||
2239 | //--unused-- int i, segnum; |
||
2240 | //--unused-- object *obj; |
||
2241 | //--unused-- |
||
2242 | //--unused-- // First, unlink all the old objects for the segments array |
||
2243 | //--unused-- for (segnum=0; segnum <= Highest_segment_index; segnum++) { |
||
2244 | //--unused-- Segments[segnum].objects = -1; |
||
2245 | //--unused-- } |
||
2246 | //--unused-- // Then, erase all the objects |
||
2247 | //--unused-- reset_objects(1); |
||
2248 | //--unused-- |
||
2249 | //--unused-- // Fill in the object array |
||
2250 | //--unused-- memcpy( Objects, new_list, sizeof(object)*MAX_OBJECTS ); |
||
2251 | //--unused-- |
||
2252 | //--unused-- Highest_object_index=-1; |
||
2253 | //--unused-- |
||
2254 | //--unused-- // Relink 'em |
||
2255 | //--unused-- for (i=0; i<MAX_OBJECTS; i++ ) { |
||
2256 | //--unused-- obj = &Objects[i]; |
||
2257 | //--unused-- if ( obj->type != OBJ_NONE ) { |
||
2258 | //--unused-- num_objects++; |
||
2259 | //--unused-- Highest_object_index = i; |
||
2260 | //--unused-- segnum = obj->segnum; |
||
2261 | //--unused-- obj->next = obj->prev = obj->segnum = -1; |
||
2262 | //--unused-- obj_link(i,segnum); |
||
2263 | //--unused-- } else { |
||
2264 | //--unused-- obj->next = obj->prev = obj->segnum = -1; |
||
2265 | //--unused-- } |
||
2266 | //--unused-- } |
||
2267 | //--unused-- |
||
2268 | //--unused-- } |
||
2269 | |||
2270 | #if defined(DXX_BUILD_DESCENT_I) |
||
2271 | #define object_is_clearable_weapon(W,a,b) object_is_clearable_weapon(a,b) |
||
2272 | #endif |
||
2273 | static unsigned object_is_clearable_weapon(const weapon_info_array &Weapon_info, const object_base obj, const unsigned clear_all) |
||
2274 | { |
||
2275 | if (!(obj.type == OBJ_WEAPON)) |
||
2276 | return 0; |
||
2277 | const auto weapon_id = get_weapon_id(obj); |
||
2278 | #if defined(DXX_BUILD_DESCENT_II) |
||
2279 | if (Weapon_info[weapon_id].flags & WIF_PLACABLE) |
||
2280 | return 0; |
||
2281 | #endif |
||
2282 | if (clear_all) |
||
2283 | return clear_all; |
||
2284 | return !is_proximity_bomb_or_player_smart_mine(weapon_id); |
||
2285 | } |
||
2286 | |||
2287 | //delete objects, such as weapons & explosions, that shouldn't stay between levels |
||
2288 | // Changed by MK on 10/15/94, don't remove proximity bombs. |
||
2289 | //if clear_all is set, clear even proximity bombs |
||
2290 | void clear_transient_objects(int clear_all) |
||
2291 | { |
||
2292 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2293 | auto &vmobjptridx = Objects.vmptridx; |
||
2294 | range_for (const auto &&obj, vmobjptridx) |
||
2295 | { |
||
2296 | if (object_is_clearable_weapon(Weapon_info, obj, clear_all) || |
||
2297 | obj->type == OBJ_FIREBALL || |
||
2298 | obj->type == OBJ_DEBRIS || |
||
2299 | (obj->type!=OBJ_NONE && obj->flags & OF_EXPLODING)) { |
||
2300 | obj_delete(LevelUniqueObjectState, Segments, obj); |
||
2301 | } |
||
2302 | } |
||
2303 | } |
||
2304 | |||
2305 | //attaches an object, such as a fireball, to another object, such as a robot |
||
2306 | void obj_attach(object_array &Objects, const vmobjptridx_t parent, const vmobjptridx_t sub) |
||
2307 | { |
||
2308 | Assert(sub->type == OBJ_FIREBALL); |
||
2309 | Assert(sub->control_type == CT_EXPLOSION); |
||
2310 | |||
2311 | Assert(sub->ctype.expl_info.next_attach==object_none); |
||
2312 | Assert(sub->ctype.expl_info.prev_attach==object_none); |
||
2313 | |||
2314 | assert(parent->attached_obj == object_none || Objects.vcptr(parent->attached_obj)->ctype.expl_info.prev_attach == object_none); |
||
2315 | |||
2316 | sub->ctype.expl_info.next_attach = parent->attached_obj; |
||
2317 | |||
2318 | if (sub->ctype.expl_info.next_attach != object_none) |
||
2319 | Objects.vmptr(sub->ctype.expl_info.next_attach)->ctype.expl_info.prev_attach = sub; |
||
2320 | |||
2321 | parent->attached_obj = sub; |
||
2322 | |||
2323 | sub->ctype.expl_info.attach_parent = parent; |
||
2324 | sub->flags |= OF_ATTACHED; |
||
2325 | |||
2326 | Assert(sub->ctype.expl_info.next_attach != sub); |
||
2327 | Assert(sub->ctype.expl_info.prev_attach != sub); |
||
2328 | } |
||
2329 | |||
2330 | //dettaches one object |
||
2331 | void obj_detach_one(object_array &Objects, object &sub) |
||
2332 | { |
||
2333 | Assert(sub.flags & OF_ATTACHED); |
||
2334 | Assert(sub.ctype.expl_info.attach_parent != object_none); |
||
2335 | |||
2336 | const auto &&parent_objp = Objects.vcptr(sub.ctype.expl_info.attach_parent); |
||
2337 | if (parent_objp->type == OBJ_NONE || parent_objp->attached_obj == object_none) |
||
2338 | { |
||
2339 | sub.flags &= ~OF_ATTACHED; |
||
2340 | return; |
||
2341 | } |
||
2342 | |||
2343 | if (sub.ctype.expl_info.next_attach != object_none) |
||
2344 | { |
||
2345 | auto &a = Objects.vmptr(sub.ctype.expl_info.next_attach)->ctype.expl_info.prev_attach; |
||
2346 | assert(Objects.vcptr(a) == &sub); |
||
2347 | a = sub.ctype.expl_info.prev_attach; |
||
2348 | } |
||
2349 | |||
2350 | const auto use_prev_attach = (sub.ctype.expl_info.prev_attach != object_none); |
||
2351 | auto &o = *Objects.vmptr(use_prev_attach ? std::exchange(sub.ctype.expl_info.prev_attach, object_none) : sub.ctype.expl_info.attach_parent); |
||
2352 | auto &update_attach = use_prev_attach ? o.ctype.expl_info.next_attach : o.attached_obj; |
||
2353 | assert(Objects.vcptr(update_attach) == &sub); |
||
2354 | update_attach = sub.ctype.expl_info.next_attach; |
||
2355 | |||
2356 | sub.ctype.expl_info.next_attach = object_none; |
||
2357 | sub.flags &= ~OF_ATTACHED; |
||
2358 | |||
2359 | } |
||
2360 | |||
2361 | //dettaches all objects from this object |
||
2362 | static void obj_detach_all(object_array &Objects, object_base &parent) |
||
2363 | { |
||
2364 | while (parent.attached_obj != object_none) |
||
2365 | obj_detach_one(Objects, Objects.vmptr(parent.attached_obj)); |
||
2366 | } |
||
2367 | |||
2368 | #if defined(DXX_BUILD_DESCENT_II) |
||
2369 | //creates a marker object in the world. returns the object number |
||
2370 | imobjptridx_t drop_marker_object(const vms_vector &pos, const vmsegptridx_t segnum, const vms_matrix &orient, const game_marker_index marker_num) |
||
2371 | { |
||
2372 | Assert(Marker_model_num != -1); |
||
2373 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
2374 | const movement_type_t movement_type = |
||
2375 | ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP) && Netgame.Allow_marker_view) |
||
2376 | ? MT_NONE |
||
2377 | : MT_SPINNING; |
||
2378 | const auto &&obj = obj_create(OBJ_MARKER, static_cast<unsigned>(marker_num), segnum, pos, &orient, Polygon_models[Marker_model_num].rad, CT_NONE, movement_type, RT_POLYOBJ); |
||
2379 | if (obj != object_none) { |
||
2380 | auto &o = *obj; |
||
2381 | o.rtype.pobj_info.model_num = Marker_model_num; |
||
2382 | |||
2383 | if (movement_type == MT_SPINNING) |
||
2384 | { |
||
2385 | constexpr fix scale = F1_0 / 2; |
||
2386 | const auto oi = obj.get_unchecked_index(); |
||
2387 | auto &spin_vec = o.mtype.spin_rate; |
||
2388 | spin_vec = {}; |
||
2389 | if (oi & 1) |
||
2390 | vm_vec_scale_add2(spin_vec, o.orient.fvec, (oi & 8) ? scale : -scale); |
||
2391 | if (oi & 2) |
||
2392 | vm_vec_scale_add2(spin_vec, o.orient.uvec, (oi & 16) ? scale : -scale); |
||
2393 | if (oi & 4) |
||
2394 | vm_vec_scale_add2(spin_vec, o.orient.rvec, (oi & 32) ? scale : -scale); |
||
2395 | } |
||
2396 | |||
2397 | // MK, 10/16/95: Using lifeleft to make it flash, thus able to trim lightlevel from all objects. |
||
2398 | o.lifeleft = IMMORTAL_TIME - 1; |
||
2399 | } |
||
2400 | return obj; |
||
2401 | } |
||
2402 | |||
2403 | // *viewer is a viewer, probably a missile. |
||
2404 | // wake up all robots that were rendered last frame subject to some constraints. |
||
2405 | void wake_up_rendered_objects(const object &viewer, window_rendered_data &window) |
||
2406 | { |
||
2407 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2408 | auto &vmobjptr = Objects.vmptr; |
||
2409 | // Make sure that we are processing current data. |
||
2410 | if (timer_query() != window.time) { |
||
2411 | return; |
||
2412 | } |
||
2413 | |||
2414 | Ai_last_missile_camera = &viewer; |
||
2415 | |||
2416 | range_for (const auto objnum, window.rendered_robots) |
||
2417 | { |
||
2418 | int fcval = d_tick_count & 3; |
||
2419 | if ((objnum & 3) == fcval) { |
||
2420 | const auto &&objp = vmobjptr(objnum); |
||
2421 | |||
2422 | if (objp->type == OBJ_ROBOT) { |
||
2423 | if (vm_vec_dist_quick(viewer.pos, objp->pos) < F1_0*100) |
||
2424 | { |
||
2425 | ai_local *ailp = &objp->ctype.ai_info.ail; |
||
2426 | if (ailp->player_awareness_type == player_awareness_type_t::PA_NONE) { |
||
2427 | objp->ctype.ai_info.SUB_FLAGS |= SUB_FLAGS_CAMERA_AWAKE; |
||
2428 | ailp->player_awareness_type = player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION; |
||
2429 | ailp->player_awareness_time = F1_0*3; |
||
2430 | ailp->previous_visibility = player_visibility_state::visible_and_in_field_of_view; |
||
2431 | } |
||
2432 | } |
||
2433 | } |
||
2434 | } |
||
2435 | } |
||
2436 | } |
||
2437 | #endif |
||
2438 | |||
2439 | // Swap endianess of given object_rw if swap == 1 |
||
2440 | void object_rw_swap(object_rw *obj, int swap) |
||
2441 | { |
||
2442 | if (!swap) |
||
2443 | return; |
||
2444 | |||
2445 | obj->signature = SWAPINT(obj->signature); |
||
2446 | obj->next = SWAPSHORT(obj->next); |
||
2447 | obj->prev = SWAPSHORT(obj->prev); |
||
2448 | obj->segnum = SWAPSHORT(obj->segnum); |
||
2449 | obj->attached_obj = SWAPSHORT(obj->attached_obj); |
||
2450 | obj->pos.x = SWAPINT(obj->pos.x); |
||
2451 | obj->pos.y = SWAPINT(obj->pos.y); |
||
2452 | obj->pos.z = SWAPINT(obj->pos.z); |
||
2453 | obj->orient.rvec.x = SWAPINT(obj->orient.rvec.x); |
||
2454 | obj->orient.rvec.y = SWAPINT(obj->orient.rvec.y); |
||
2455 | obj->orient.rvec.z = SWAPINT(obj->orient.rvec.z); |
||
2456 | obj->orient.fvec.x = SWAPINT(obj->orient.fvec.x); |
||
2457 | obj->orient.fvec.y = SWAPINT(obj->orient.fvec.y); |
||
2458 | obj->orient.fvec.z = SWAPINT(obj->orient.fvec.z); |
||
2459 | obj->orient.uvec.x = SWAPINT(obj->orient.uvec.x); |
||
2460 | obj->orient.uvec.y = SWAPINT(obj->orient.uvec.y); |
||
2461 | obj->orient.uvec.z = SWAPINT(obj->orient.uvec.z); |
||
2462 | obj->size = SWAPINT(obj->size); |
||
2463 | obj->shields = SWAPINT(obj->shields); |
||
2464 | obj->last_pos.x = SWAPINT(obj->last_pos.x); |
||
2465 | obj->last_pos.y = SWAPINT(obj->last_pos.y); |
||
2466 | obj->last_pos.z = SWAPINT(obj->last_pos.z); |
||
2467 | obj->lifeleft = SWAPINT(obj->lifeleft); |
||
2468 | |||
2469 | switch (obj->movement_type) |
||
2470 | { |
||
2471 | case MT_PHYSICS: |
||
2472 | obj->mtype.phys_info.velocity.x = SWAPINT(obj->mtype.phys_info.velocity.x); |
||
2473 | obj->mtype.phys_info.velocity.y = SWAPINT(obj->mtype.phys_info.velocity.y); |
||
2474 | obj->mtype.phys_info.velocity.z = SWAPINT(obj->mtype.phys_info.velocity.z); |
||
2475 | obj->mtype.phys_info.thrust.x = SWAPINT(obj->mtype.phys_info.thrust.x); |
||
2476 | obj->mtype.phys_info.thrust.y = SWAPINT(obj->mtype.phys_info.thrust.y); |
||
2477 | obj->mtype.phys_info.thrust.z = SWAPINT(obj->mtype.phys_info.thrust.z); |
||
2478 | obj->mtype.phys_info.mass = SWAPINT(obj->mtype.phys_info.mass); |
||
2479 | obj->mtype.phys_info.drag = SWAPINT(obj->mtype.phys_info.drag); |
||
2480 | obj->mtype.phys_info.rotvel.x = SWAPINT(obj->mtype.phys_info.rotvel.x); |
||
2481 | obj->mtype.phys_info.rotvel.y = SWAPINT(obj->mtype.phys_info.rotvel.y); |
||
2482 | obj->mtype.phys_info.rotvel.z = SWAPINT(obj->mtype.phys_info.rotvel.z); |
||
2483 | obj->mtype.phys_info.rotthrust.x = SWAPINT(obj->mtype.phys_info.rotthrust.x); |
||
2484 | obj->mtype.phys_info.rotthrust.y = SWAPINT(obj->mtype.phys_info.rotthrust.y); |
||
2485 | obj->mtype.phys_info.rotthrust.z = SWAPINT(obj->mtype.phys_info.rotthrust.z); |
||
2486 | obj->mtype.phys_info.turnroll = SWAPINT(obj->mtype.phys_info.turnroll); |
||
2487 | obj->mtype.phys_info.flags = SWAPSHORT(obj->mtype.phys_info.flags); |
||
2488 | break; |
||
2489 | |||
2490 | case MT_SPINNING: |
||
2491 | obj->mtype.spin_rate.x = SWAPINT(obj->mtype.spin_rate.x); |
||
2492 | obj->mtype.spin_rate.y = SWAPINT(obj->mtype.spin_rate.y); |
||
2493 | obj->mtype.spin_rate.z = SWAPINT(obj->mtype.spin_rate.z); |
||
2494 | break; |
||
2495 | } |
||
2496 | |||
2497 | switch (obj->control_type) |
||
2498 | { |
||
2499 | case CT_WEAPON: |
||
2500 | obj->ctype.laser_info.parent_type = SWAPSHORT(obj->ctype.laser_info.parent_type); |
||
2501 | obj->ctype.laser_info.parent_num = SWAPSHORT(obj->ctype.laser_info.parent_num); |
||
2502 | obj->ctype.laser_info.parent_signature = SWAPINT(obj->ctype.laser_info.parent_signature); |
||
2503 | obj->ctype.laser_info.creation_time = SWAPINT(obj->ctype.laser_info.creation_time); |
||
2504 | obj->ctype.laser_info.last_hitobj = SWAPSHORT(obj->ctype.laser_info.last_hitobj); |
||
2505 | obj->ctype.laser_info.track_goal = SWAPSHORT(obj->ctype.laser_info.track_goal); |
||
2506 | obj->ctype.laser_info.multiplier = SWAPINT(obj->ctype.laser_info.multiplier); |
||
2507 | break; |
||
2508 | |||
2509 | case CT_EXPLOSION: |
||
2510 | obj->ctype.expl_info.spawn_time = SWAPINT(obj->ctype.expl_info.spawn_time); |
||
2511 | obj->ctype.expl_info.delete_time = SWAPINT(obj->ctype.expl_info.delete_time); |
||
2512 | obj->ctype.expl_info.delete_objnum = SWAPSHORT(obj->ctype.expl_info.delete_objnum); |
||
2513 | obj->ctype.expl_info.attach_parent = SWAPSHORT(obj->ctype.expl_info.attach_parent); |
||
2514 | obj->ctype.expl_info.prev_attach = SWAPSHORT(obj->ctype.expl_info.prev_attach); |
||
2515 | obj->ctype.expl_info.next_attach = SWAPSHORT(obj->ctype.expl_info.next_attach); |
||
2516 | break; |
||
2517 | |||
2518 | case CT_AI: |
||
2519 | obj->ctype.ai_info.hide_segment = SWAPSHORT(obj->ctype.ai_info.hide_segment); |
||
2520 | obj->ctype.ai_info.hide_index = SWAPSHORT(obj->ctype.ai_info.hide_index); |
||
2521 | obj->ctype.ai_info.path_length = SWAPSHORT(obj->ctype.ai_info.path_length); |
||
2522 | #if defined(DXX_BUILD_DESCENT_I) |
||
2523 | obj->ctype.ai_info.cur_path_index = SWAPSHORT(obj->ctype.ai_info.cur_path_index); |
||
2524 | #elif defined(DXX_BUILD_DESCENT_II) |
||
2525 | obj->ctype.ai_info.dying_start_time = SWAPINT(obj->ctype.ai_info.dying_start_time); |
||
2526 | #endif |
||
2527 | obj->ctype.ai_info.danger_laser_num = SWAPSHORT(obj->ctype.ai_info.danger_laser_num); |
||
2528 | obj->ctype.ai_info.danger_laser_signature = SWAPINT(obj->ctype.ai_info.danger_laser_signature); |
||
2529 | break; |
||
2530 | |||
2531 | case CT_LIGHT: |
||
2532 | obj->ctype.light_info.intensity = SWAPINT(obj->ctype.light_info.intensity); |
||
2533 | break; |
||
2534 | |||
2535 | case CT_POWERUP: |
||
2536 | obj->ctype.powerup_info.count = SWAPINT(obj->ctype.powerup_info.count); |
||
2537 | #if defined(DXX_BUILD_DESCENT_II) |
||
2538 | obj->ctype.powerup_info.creation_time = SWAPINT(obj->ctype.powerup_info.creation_time); |
||
2539 | obj->ctype.powerup_info.flags = SWAPINT(obj->ctype.powerup_info.flags); |
||
2540 | #endif |
||
2541 | break; |
||
2542 | } |
||
2543 | |||
2544 | switch (obj->render_type) |
||
2545 | { |
||
2546 | case RT_MORPH: |
||
2547 | case RT_POLYOBJ: |
||
2548 | case RT_NONE: // HACK below |
||
2549 | { |
||
2550 | if (obj->render_type == RT_NONE && obj->type != OBJ_GHOST) // HACK: when a player is dead or not connected yet, clients still expect to get polyobj data - even if render_type == RT_NONE at this time. |
||
2551 | break; |
||
2552 | obj->rtype.pobj_info.model_num = SWAPINT(obj->rtype.pobj_info.model_num); |
||
2553 | for (uint_fast32_t i=0;i<MAX_SUBMODELS;i++) |
||
2554 | { |
||
2555 | obj->rtype.pobj_info.anim_angles[i].p = SWAPINT(obj->rtype.pobj_info.anim_angles[i].p); |
||
2556 | obj->rtype.pobj_info.anim_angles[i].b = SWAPINT(obj->rtype.pobj_info.anim_angles[i].b); |
||
2557 | obj->rtype.pobj_info.anim_angles[i].h = SWAPINT(obj->rtype.pobj_info.anim_angles[i].h); |
||
2558 | } |
||
2559 | obj->rtype.pobj_info.subobj_flags = SWAPINT(obj->rtype.pobj_info.subobj_flags); |
||
2560 | obj->rtype.pobj_info.tmap_override = SWAPINT(obj->rtype.pobj_info.tmap_override); |
||
2561 | obj->rtype.pobj_info.alt_textures = SWAPINT(obj->rtype.pobj_info.alt_textures); |
||
2562 | break; |
||
2563 | } |
||
2564 | |||
2565 | case RT_WEAPON_VCLIP: |
||
2566 | case RT_HOSTAGE: |
||
2567 | case RT_POWERUP: |
||
2568 | case RT_FIREBALL: |
||
2569 | obj->rtype.vclip_info.vclip_num = SWAPINT(obj->rtype.vclip_info.vclip_num); |
||
2570 | obj->rtype.vclip_info.frametime = SWAPINT(obj->rtype.vclip_info.frametime); |
||
2571 | break; |
||
2572 | |||
2573 | case RT_LASER: |
||
2574 | break; |
||
2575 | |||
2576 | } |
||
2577 | } |
||
2578 | |||
2579 | } |
||
2580 | |||
2581 | namespace dcx { |
||
2582 | |||
2583 | void (check_warn_object_type)(const object_base &o, object_type_t t, const char *file, unsigned line) |
||
2584 | { |
||
2585 | if (o.type != t) |
||
2586 | con_printf(CON_URGENT, "%s:%u: BUG: object %p has type %u, expected %u", file, line, &o, o.type, t); |
||
2587 | } |
||
2588 | |||
2589 | } |