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