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