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  * Multiplayer robot code
23  *
24  */
25 #include <type_traits>
26 #include "multiinternal.h"
27 
28 #include <string.h>
29 #include <stdlib.h>
30 #include <stdexcept>
31 
32 #include "vecmat.h"
33 #include "object.h"
34 #include "multibot.h"
35 #include "game.h"
36 #include "net_udp.h"
37 #include "laser.h"
38 #include "dxxerror.h"
39 #include "timer.h"
40 #include "player.h"
41 #include "ai.h"
42 #include "fireball.h"
43 #include "aistruct.h"
44 #include "gameseg.h"
45 #include "robot.h"
46 #include "powerup.h"
47 #include "gauges.h"
48 #include "fuelcen.h"
49 #include "morph.h"
50 #include "digi.h"
51 #include "sounds.h"
52 #include "effects.h"
53 #include "automap.h"
54 #include "physics.h"
55 #include "byteutil.h"
56 #include "escort.h"
57 
58 #include "compiler-range_for.h"
59 #include "d_levelstate.h"
60 #include "partial_range.h"
61 
62 namespace dsx {
63 static int multi_add_controlled_robot(vmobjptridx_t objnum, int agitation);
64 }
65 static void multi_send_robot_position_sub(const vmobjptridx_t objnum, int now);
66 static void multi_send_release_robot(vmobjptridx_t objnum);
67 static void multi_delete_controlled_robot(const vmobjptridx_t objnum);
68 
69 //
70 // Code for controlling robots in multiplayer games
71 //
72 
73 #define STANDARD_EXPL_DELAY (F1_0/4)
74 #if defined(DXX_BUILD_DESCENT_I)
75 #define MIN_CONTROL_TIME	F1_0*2
76 #define ROBOT_TIMEOUT		F1_0*3
77 
78 #define MAX_TO_DELETE	67
79 #elif defined(DXX_BUILD_DESCENT_II)
80 #define MIN_CONTROL_TIME	F1_0*1
81 #define ROBOT_TIMEOUT		F1_0*2
82 #endif
83 
84 #define MIN_TO_ADD	60
85 
86 std::array<objnum_t, MAX_ROBOTS_CONTROLLED> robot_controlled;
87 std::array<int, MAX_ROBOTS_CONTROLLED> robot_agitation,
88 	robot_send_pending,
89 	robot_fired;
90 std::array<fix64, MAX_ROBOTS_CONTROLLED> robot_controlled_time,
91 	robot_last_send_time,
92 	robot_last_message_time;
93 ubyte robot_fire_buf[MAX_ROBOTS_CONTROLLED][18+3];
94 
95 #define MULTI_ROBOT_PRIORITY(objnum, pnum) (((objnum % 4) + pnum) % N_players)
96 
97 //#define MULTI_ROBOT_PRIORITY(objnum, pnum) multi_robot_priority(objnum, pnum)
98 //int multi_robot_priority(int objnum, int pnum)
99 //{
100 //	return( ((objnum % 4) + pnum) % N_players);
101 //}
102 
multi_can_move_robot(const vmobjptridx_t objnum,int agitation)103 int multi_can_move_robot(const vmobjptridx_t objnum, int agitation)
104 {
105 	auto &BossUniqueState = LevelUniqueObjectState.BossState;
106 	// Determine whether or not I am allowed to move this robot.
107 	// Claim robot if necessary.
108 
109 	if (Player_dead_state == player_dead_state::exploded)
110 		return 0;
111 
112 	if (objnum->type != OBJ_ROBOT)
113 	{
114 #ifndef NDEBUG
115 		Int3();
116 #endif
117 		return 0;
118 	}
119 
120 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
121 	if (Robot_info[get_robot_id(objnum)].boss_flag && BossUniqueState.Boss_dying)
122 		return 0;
123 	else if (objnum->ctype.ai_info.REMOTE_OWNER == Player_num) // Already my robot!
124 	{
125 		int slot_num = objnum->ctype.ai_info.REMOTE_SLOT_NUM;
126 
127       if ((slot_num < 0) || (slot_num >= MAX_ROBOTS_CONTROLLED))
128 		 {
129 			return 0;
130 		 }
131 
132 		if (robot_fired[slot_num]) {
133 			return 0;
134 		}
135 		else {
136 			robot_agitation[slot_num] = agitation;
137 			robot_last_message_time[slot_num] = GameTime64;
138 			return 1;
139 		}
140 	}
141 	else if ((objnum->ctype.ai_info.REMOTE_OWNER != -1) || (agitation < MIN_TO_ADD))
142 	{
143 		if (agitation == ROBOT_FIRE_AGITATION) // Special case for firing at non-player
144 		{
145 			return 1; // Try to fire at player even tho we're not in control!
146 		}
147 		else
148 			return 0;
149 	}
150 	else
151 		return multi_add_controlled_robot(objnum, agitation);
152 }
153 
multi_check_robot_timeout()154 void multi_check_robot_timeout()
155 {
156 	auto &Objects = LevelUniqueObjectState.Objects;
157 	auto &vmobjptridx = Objects.vmptridx;
158 	static fix64 lastcheck = 0;
159 	int i;
160 
161 	if (GameTime64 > lastcheck + F1_0 || lastcheck > GameTime64)
162 	{
163 		lastcheck = GameTime64;
164 		for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++)
165 		{
166 			if (robot_controlled[i] != object_none && robot_last_send_time[i] + ROBOT_TIMEOUT < GameTime64)
167 			{
168 				const auto &&robot_objp = vmobjptridx(robot_controlled[i]);
169 				if (robot_objp->ctype.ai_info.REMOTE_OWNER != Player_num)
170 				{
171 					robot_controlled[i] = object_none;
172 					Int3(); // Non-terminal but Rob is interesting, step over please...
173 					return;
174 				}
175 				if (robot_send_pending[i])
176 					multi_send_robot_position(robot_objp, 1);
177 				multi_send_release_robot(robot_objp);
178 //				multi_delete_controlled_robot(robot_controlled[i]);
179 //				robot_controlled[i] = -1;
180 			}
181 		}
182 	}
183 }
184 
multi_strip_robots(const int playernum)185 void multi_strip_robots(const int playernum)
186 {
187 	auto &Objects = LevelUniqueObjectState.Objects;
188 	auto &vmobjptr = Objects.vmptr;
189 	auto &vmobjptridx = Objects.vmptridx;
190 	// Grab all robots away from a player
191 	// (player died or exited the game)
192 	if (Game_mode & GM_MULTI_ROBOTS) {
193 
194 		if (playernum == Player_num)
195 		{
196 			range_for (const auto r, robot_controlled)
197 				if (r != object_none)
198 					multi_delete_controlled_robot(vmobjptridx(r));
199 		}
200 
201 		/* clang-3.7 crashes with a segmentation fault if asked to
202 		 * implicitly convert 1u to std::size_t in partial_range inline
203 		 * chain.  Add a conversion up front, which seems to avoid the
204 		 * bug.  The value must not be cast on non-clang targets because
205 		 * some non-clang targets issue a `-Wuseless-cast` diagnostic
206 		 * for the cast.  Fortunately, clang does not understand
207 		 * `-Wuseless-cast`, so the cast can be used on all clang
208 		 * targets, even ones where it is useless.
209 		 *
210 		 * This crash was first seen in x86_64-pc-linux-gnu-clang-3.7,
211 		 * then later reported by kreator in `Apple LLVM version 7.3.0`
212 		 * on `x86_64-apple-darwin15.6.0`.
213 		 *
214 		 * Comment from kreator regarding the clang crash bug:
215 		 *	This has been reported with Apple, bug report 28247284
216 		 */
217 #ifdef __clang__
218 #define DXX_partial_range_vobjptr_skip_distance	static_cast<std::size_t>
219 #else
220 #define DXX_partial_range_vobjptr_skip_distance
221 #endif
222 		range_for (const auto &&objp, partial_range(vmobjptr, DXX_partial_range_vobjptr_skip_distance(1u), vmobjptr.count()))
223 #undef DXX_partial_range_vobjptr_skip_distance
224 		{
225 			auto &obj = *objp;
226 			if (obj.type == OBJ_ROBOT && obj.ctype.ai_info.REMOTE_OWNER == playernum)
227 			{
228 				assert(obj.control_source == object::control_type::ai || obj.control_source == object::control_type::None || obj.control_source == object::control_type::morph);
229 				obj.ctype.ai_info.REMOTE_OWNER = -1;
230 				if (playernum == Player_num)
231 					obj.ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD;
232 				else
233 					obj.ctype.ai_info.REMOTE_SLOT_NUM = 0;
234 	  		}
235 		}
236 	}
237 	// Note -- only call this with playernum == Player_num if all other players
238 	// already know that we are clearing house.  This does not send a release
239 	// message for each controlled robot!!
240 
241 }
242 
243 namespace dsx {
multi_add_controlled_robot(const vmobjptridx_t objnum,int agitation)244 int multi_add_controlled_robot(const vmobjptridx_t objnum, int agitation)
245 {
246 	auto &Objects = LevelUniqueObjectState.Objects;
247 	auto &vcobjptr = Objects.vcptr;
248 	auto &vmobjptridx = Objects.vmptridx;
249 	int i;
250 	int lowest_agitation = INT32_MAX; // MAX POSITIVE INT
251 	int lowest_agitated_bot = -1;
252 	int first_free_robot = -1;
253 
254 	// Try to add a new robot to the controlled list, return 1 if added, 0 if not.
255 
256 #if defined(DXX_BUILD_DESCENT_II)
257 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
258    if (Robot_info[get_robot_id(objnum)].boss_flag) // this is a boss, so make sure he gets a slot
259 		agitation=(agitation*3)+Player_num;
260 #endif
261 	if (objnum->ctype.ai_info.REMOTE_SLOT_NUM > 0)
262 	{
263 		objnum->ctype.ai_info.REMOTE_SLOT_NUM -= 1;
264 		return 0;
265 	}
266 
267 	for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++)
268 	{
269 		if (robot_controlled[i] == object_none || vcobjptr(robot_controlled[i])->type != OBJ_ROBOT) {
270 			first_free_robot = i;
271 			break;
272 		}
273 
274 		if (robot_last_message_time[i] + ROBOT_TIMEOUT < GameTime64) {
275 			const auto &&robot_objp = vmobjptridx(robot_controlled[i]);
276 			if (robot_send_pending[i])
277 				multi_send_robot_position(robot_objp, 1);
278 			multi_send_release_robot(robot_objp);
279 			first_free_robot = i;
280 			break;
281 		}
282 
283 		if (robot_agitation[i] < lowest_agitation && robot_controlled_time[i] + MIN_CONTROL_TIME < GameTime64)
284 		{
285 			lowest_agitation = robot_agitation[i];
286 			lowest_agitated_bot = i;
287 		}
288 	}
289 
290 	if (first_free_robot != -1)  // Room left for new robots
291 		i = first_free_robot;
292 
293 	else if ((agitation > lowest_agitation)
294 #if defined(DXX_BUILD_DESCENT_I)
295 			 && (lowest_agitation <= MAX_TO_DELETE)
296 #endif
297 	) // Replace some old robot with a more agitated one
298 	{
299 		const auto &&robot_objp = vmobjptridx(robot_controlled[lowest_agitated_bot]);
300 		if (robot_send_pending[lowest_agitated_bot])
301 			multi_send_robot_position(robot_objp, 1);
302 		multi_send_release_robot(robot_objp);
303 
304 		i = lowest_agitated_bot;
305 	}
306 	else {
307 		return(0); // Sorry, can't squeeze him in!
308 	}
309 
310 	multi_send_claim_robot(objnum);
311 	robot_controlled[i] = objnum;
312 	robot_agitation[i] = agitation;
313 	objnum->ctype.ai_info.REMOTE_OWNER = Player_num;
314 	objnum->ctype.ai_info.REMOTE_SLOT_NUM = i;
315 	robot_controlled_time[i] = GameTime64;
316 	robot_last_send_time[i] = robot_last_message_time[i] = GameTime64;
317 	return(1);
318 }
319 }
320 
multi_delete_controlled_robot(const vmobjptridx_t objnum)321 void multi_delete_controlled_robot(const vmobjptridx_t objnum)
322 {
323 	int i;
324 
325 	// Delete robot object number objnum from list of controlled robots because it is dead
326 
327 	for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++)
328 		if (robot_controlled[i] == objnum)
329 			break;
330 
331 	if (i == MAX_ROBOTS_CONTROLLED)
332 		return;
333 
334 	if (objnum->ctype.ai_info.REMOTE_SLOT_NUM != i)
335 	 {
336 	  Int3();  // can't release this bot!
337 	  return;
338 	 }
339 
340 	objnum->ctype.ai_info.REMOTE_OWNER = -1;
341 	objnum->ctype.ai_info.REMOTE_SLOT_NUM = 0;
342 	robot_controlled[i] = object_none;
343 	robot_send_pending[i] = 0;
344 	robot_fired[i] = 0;
345 }
346 
347 struct multi_claim_robot
348 {
349 	uint8_t pnum;
350 	int8_t owner;
351 	uint16_t robjnum;
352 };
353 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_ROBOT_CLAIM, multi_claim_robot, b, (b.pnum, b.owner, b.robjnum));
354 
multi_send_claim_robot(const vmobjptridx_t objnum)355 void multi_send_claim_robot(const vmobjptridx_t objnum)
356 {
357 	if (objnum->type != OBJ_ROBOT)
358 		throw std::runtime_error("claiming non-robot"); // See rob
359 	// The AI tells us we should take control of this robot.
360 	auto r = objnum_local_to_remote(objnum);
361 	multi_serialize_write(2, multi_claim_robot{static_cast<uint8_t>(Player_num), r.owner, r.objnum});
362 }
363 
multi_send_release_robot(const vmobjptridx_t objnum)364 void multi_send_release_robot(const vmobjptridx_t objnum)
365 {
366 	multi_command<MULTI_ROBOT_RELEASE> multibuf;
367 	if (objnum->type != OBJ_ROBOT)
368 	{
369 		Int3(); // See rob
370 		return;
371 	}
372 
373 	multi_delete_controlled_robot(objnum);
374 
375 	multibuf[1] = Player_num;
376 	const auto &&[obj_owner, remote_objnum] = objnum_local_to_remote(objnum);
377 	multibuf[4] = obj_owner;
378 	PUT_INTEL_SHORT(&multibuf[2], remote_objnum);
379 
380 	multi_send_data(multibuf, 2);
381 }
382 
383 #define MIN_ROBOT_COM_GAP F1_0/12
384 
multi_send_robot_frame(int sent)385 int multi_send_robot_frame(int sent)
386 {
387 	auto &Objects = LevelUniqueObjectState.Objects;
388 	auto &vmobjptridx = Objects.vmptridx;
389 	static int last_sent = 0;
390 
391 	int i;
392 	int rval = 0;
393 
394 	for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++)
395 	{
396 		int sending = (last_sent+1+i)%MAX_ROBOTS_CONTROLLED;
397 		if (robot_controlled[sending] != object_none && (robot_send_pending[sending] > sent || robot_fired[sending] > sent))
398 		{
399 			if (robot_send_pending[sending])
400 			{
401 				multi_send_robot_position_sub(vmobjptridx(robot_controlled[sending]), (std::exchange(robot_send_pending[sending], 0) > 1));
402 			}
403 
404 			if (robot_fired[sending])
405 			{
406 				robot_fired[sending] = 0;
407 				multi_send_data<MULTI_ROBOT_FIRE>(robot_fire_buf[sending], 18, 1);
408 			}
409 
410 			if (!(Game_mode & GM_NETWORK))
411 				sent += 1;
412 
413 			last_sent = sending;
414 			rval++;
415 		}
416 	}
417 	Assert((last_sent >= 0) && (last_sent <= MAX_ROBOTS_CONTROLLED));
418 	return(rval);
419 }
420 
421 #if defined(DXX_BUILD_DESCENT_II)
422 namespace dsx {
423 /*
424  * The thief bot moves around even when not controlled by a player. Due to its erratic and random behaviour, it's movement will diverge heavily between players and cause it to teleport when a player takes over.
425  * To counter this, let host update positions when no one controls it OR the client which does.
426  * Seperated this function to allow the positions being updated more frequently then multi_send_robot_frame (see dispatch_table::do_protocol_frame()).
427  */
multi_send_thief_frame()428 void multi_send_thief_frame()
429 {
430 	auto &Objects = LevelUniqueObjectState.Objects;
431 	auto &vmobjptridx = Objects.vmptridx;
432 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
433         if (!(Game_mode & GM_MULTI_ROBOTS))
434                 return;
435 
436         range_for (const auto &&objp, vmobjptridx)
437         {
438 		if (objp->type == OBJ_ROBOT)
439                 {
440 			if (robot_is_thief(Robot_info[get_robot_id(objp)]))
441                         {
442 				if ((multi_i_am_master() && objp->ctype.ai_info.REMOTE_OWNER == -1) || objp->ctype.ai_info.REMOTE_OWNER == Player_num)
443                                 {
444                                         multi_send_robot_position_sub(objp,1);
445                                 }
446                                 return;
447                         }
448                 }
449         }
450 }
451 
452 }
453 #endif
454 
multi_send_robot_position_sub(const vmobjptridx_t objnum,int now)455 void multi_send_robot_position_sub(const vmobjptridx_t objnum, int now)
456 {
457 	multi_command<MULTI_ROBOT_POSITION> multibuf;
458 	int loc = 0;
459 
460 	loc += 1;
461 	multibuf[loc] = Player_num;                                            loc += 1;
462 	const auto &&[obj_owner, remote_objnum] = objnum_local_to_remote(objnum);
463 	multibuf[loc+2] = obj_owner;
464 	PUT_INTEL_SHORT(&multibuf[loc], remote_objnum);                                      loc += 3; // 5
465 
466 	quaternionpos qpp{};
467 	create_quaternionpos(qpp, objnum);
468 	PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.w);			loc += 2;
469 	PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.x);			loc += 2;
470 	PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.y);			loc += 2;
471 	PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.z);			loc += 2;
472 	PUT_INTEL_INT(&multibuf[loc], qpp.pos.x);				loc += 4;
473 	PUT_INTEL_INT(&multibuf[loc], qpp.pos.y);				loc += 4;
474 	PUT_INTEL_INT(&multibuf[loc], qpp.pos.z);				loc += 4;
475 	PUT_INTEL_SHORT(&multibuf[loc], qpp.segment);			loc += 2;
476 	PUT_INTEL_INT(&multibuf[loc], qpp.vel.x);				loc += 4;
477 	PUT_INTEL_INT(&multibuf[loc], qpp.vel.y);				loc += 4;
478 	PUT_INTEL_INT(&multibuf[loc], qpp.vel.z);				loc += 4;
479 	PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.x);			loc += 4;
480 	PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.y);			loc += 4;
481 	PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.z);			loc += 4; // 46 + 5 = 51
482 
483 	multi_send_data<MULTI_ROBOT_POSITION>(multibuf, now?1:0);
484 }
485 
multi_send_robot_position(object & obj,int force)486 void multi_send_robot_position(object &obj, int force)
487 {
488 	// Send robot position to other player(s).  Includes a byte
489 	// value describing whether or not they fired a weapon
490 
491 	if (!(Game_mode & GM_MULTI))
492 		return;
493 
494 	if (obj.type != OBJ_ROBOT)
495 	{
496 		Int3(); // See rob
497 		return;
498 	}
499 
500 	if (obj.ctype.ai_info.REMOTE_OWNER != Player_num)
501 		return;
502 
503 //	Objects[objnum].phys_info.drag = Robot_info[Objects[objnum].id].drag; // Set drag to normal
504 
505 	const auto slot = obj.ctype.ai_info.REMOTE_SLOT_NUM;
506 	robot_last_send_time[slot] = GameTime64;
507 	robot_send_pending[slot] = 1+force;
508 	return;
509 }
510 
multi_send_robot_fire(const vmobjptridx_t obj,int gun_num,const vms_vector & fire)511 void multi_send_robot_fire(const vmobjptridx_t obj, int gun_num, const vms_vector &fire)
512 {
513 	multi_command<MULTI_ROBOT_FIRE> multibuf;
514         // Send robot fire event
515         int loc = 0;
516 
517                                                                         loc += 1;
518         multibuf[loc] = Player_num;                                     loc += 1;
519 	const auto &&[obj_owner, remote_objnum] = objnum_local_to_remote(obj);
520 	multibuf[loc+2] = obj_owner;
521 	PUT_INTEL_SHORT(&multibuf[loc], remote_objnum);
522                                                                         loc += 3;
523         multibuf[loc] = gun_num;                                        loc += 1;
524         if constexpr (words_bigendian)
525         {
526                 vms_vector swapped_vec;
527 		swapped_vec.x = INTEL_INT(static_cast<int>(fire.x));
528 		swapped_vec.y = INTEL_INT(static_cast<int>(fire.y));
529 		swapped_vec.z = INTEL_INT(static_cast<int>(fire.z));
530                 memcpy(&multibuf[loc], &swapped_vec, sizeof(vms_vector));
531         }
532         else
533         {
534                 memcpy(&multibuf[loc], &fire, sizeof(vms_vector));
535         }
536                                                                         loc += sizeof(vms_vector); // 12
537                                                                         // --------------------------
538                                                                         //      Total = 18
539 
540         if (obj->ctype.ai_info.REMOTE_OWNER == Player_num)
541         {
542                 int slot = obj->ctype.ai_info.REMOTE_SLOT_NUM;
543                 if (slot<0 || slot>=MAX_ROBOTS_CONTROLLED)
544                 {
545                         return;
546                 }
547                 if (robot_fired[slot] != 0)
548                 {
549                         // Int3(); // ROB!
550                         return;
551                 }
552                 memcpy(robot_fire_buf[slot], multibuf.data(), loc);
553                 robot_fired[slot] = 1;
554         }
555         else
556                 multi_send_data(multibuf, 1); // Not our robot, send ASAP
557 }
558 
559 struct multi_explode_robot
560 {
561 	int16_t robj_killer, robj_killed;
562 	int8_t owner_killer, owner_killed;
563 };
564 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_ROBOT_EXPLODE, multi_explode_robot, b, (b.robj_killer, b.robj_killed, b.owner_killer, b.owner_killed));
565 
multi_send_robot_explode(const imobjptridx_t objnum,objnum_t killer)566 void multi_send_robot_explode(const imobjptridx_t objnum, objnum_t killer)
567 {
568 	// Send robot explosion event to the other players
569 	const auto k = objnum_local_to_remote(killer);
570 	multi_explode_robot e;
571 	e.robj_killer = k.objnum;
572 	e.owner_killer = k.owner;
573 	const auto d = objnum_local_to_remote(objnum);
574 	e.robj_killed = d.objnum;
575 	e.owner_killed = d.owner;
576 	multi_serialize_write(2, e);
577 
578 	multi_delete_controlled_robot(objnum);
579 }
580 
multi_send_create_robot(const station_number station,const objnum_t objnum,const int type)581 void multi_send_create_robot(const station_number station, const objnum_t objnum, const int type)
582 {
583 	multi_command<MULTI_CREATE_ROBOT> multibuf;
584 	// Send create robot information
585 
586 	int loc = 0;
587 
588 	loc += 1;
589 	multibuf[loc] = Player_num;								loc += 1;
590 	static_assert(sizeof(underlying_value(station)) == 1);
591 	multibuf[loc] = underlying_value(station);                         loc += 1;
592 	PUT_INTEL_SHORT(&multibuf[loc], objnum);                  loc += 2;
593 	multibuf[loc] = type;									loc += 1;
594 
595 	map_objnum_local_to_local(objnum);
596 
597 	multi_send_data(multibuf, 2);
598 }
599 
600 namespace {
601 
602 struct boss_teleport
603 {
604 	objnum_t objnum;
605 	segnum_t where;
606 };
607 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_TELEPORT, boss_teleport, b, (b.objnum, b.where));
608 
609 struct boss_cloak
610 {
611 	objnum_t objnum;
612 };
613 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_CLOAK, boss_cloak, b, (b.objnum));
614 
615 struct boss_start_gate
616 {
617 	objnum_t objnum;
618 };
619 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_START_GATE, boss_start_gate, b, (b.objnum));
620 
621 struct boss_stop_gate
622 {
623 	objnum_t objnum;
624 };
625 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_STOP_GATE, boss_stop_gate, b, (b.objnum));
626 
627 struct boss_create_robot
628 {
629 	objnum_t objnum;
630 	objnum_t objrobot;
631 	segnum_t where;
632 	uint8_t robot_type;
633 };
634 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_CREATE_ROBOT, boss_create_robot, b, (b.objnum, b.objrobot, b.where, b.robot_type));
635 
636 #if defined(DXX_BUILD_DESCENT_II)
637 struct update_buddy_state
638 {
639 	uint8_t Looking_for_marker;
640 	escort_goal_t Escort_special_goal;
641 	int Last_buddy_key;
642 };
643 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_UPDATE_BUDDY_STATE, update_buddy_state, b, (b.Looking_for_marker, b.Escort_special_goal, b.Last_buddy_key));
644 #endif
645 
646 }
647 
648 template <typename T, typename... Args>
multi_send_boss_action(objnum_t bossobjnum,Args &&...args)649 static inline void multi_send_boss_action(objnum_t bossobjnum, Args&&... args)
650 {
651 	multi_serialize_write(2, T{bossobjnum, std::forward<Args>(args)...});
652 }
653 
654 namespace dsx {
multi_send_boss_teleport(const vmobjptridx_t bossobj,const vcsegidx_t where)655 void multi_send_boss_teleport(const vmobjptridx_t bossobj, const vcsegidx_t where)
656 {
657 	// Boss is up for grabs after teleporting
658 	Assert((bossobj->ctype.ai_info.REMOTE_SLOT_NUM >= 0) && (bossobj->ctype.ai_info.REMOTE_SLOT_NUM < MAX_ROBOTS_CONTROLLED));
659 	multi_delete_controlled_robot(bossobj);
660 #if defined(DXX_BUILD_DESCENT_I)
661 	bossobj->ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD; // Hands-off period!
662 #endif
663 	multi_send_boss_action<boss_teleport>(bossobj, where);
664 }
665 }
666 
multi_send_boss_cloak(objnum_t bossobjnum)667 void multi_send_boss_cloak(objnum_t bossobjnum)
668 {
669 	multi_send_boss_action<boss_cloak>(bossobjnum);
670 }
671 
multi_send_boss_start_gate(objnum_t bossobjnum)672 void multi_send_boss_start_gate(objnum_t bossobjnum)
673 {
674 	multi_send_boss_action<boss_start_gate>(bossobjnum);
675 }
676 
multi_send_boss_stop_gate(objnum_t bossobjnum)677 void multi_send_boss_stop_gate(objnum_t bossobjnum)
678 {
679 	multi_send_boss_action<boss_stop_gate>(bossobjnum);
680 }
681 
multi_send_boss_create_robot(vmobjidx_t bossobjnum,const vmobjptridx_t objrobot)682 void multi_send_boss_create_robot(vmobjidx_t bossobjnum, const vmobjptridx_t objrobot)
683 {
684 	map_objnum_local_to_local(objrobot);
685 	multi_send_boss_action<boss_create_robot>(bossobjnum, objrobot, objrobot->segnum, get_robot_id(objrobot));
686 }
687 
688 #define MAX_ROBOT_POWERUPS 4
689 
690 namespace dsx {
691 
multi_send_create_robot_powerups(const object_base & del_obj)692 static void multi_send_create_robot_powerups(const object_base &del_obj)
693 {
694 	multi_command<MULTI_CREATE_ROBOT_POWERUPS> multibuf;
695 	// Send create robot information
696 
697 	int loc = 0;
698 
699 	loc += 1;
700 	multibuf[loc] = Player_num;									loc += 1;
701 	multibuf[loc] = del_obj.contains_count;					loc += 1;
702 	multibuf[loc] = del_obj.contains_type; 					loc += 1;
703 	multibuf[loc] = del_obj.contains_id;						loc += 1;
704 	PUT_INTEL_SHORT(&multibuf[loc], del_obj.segnum);		        loc += 2;
705 	if constexpr (words_bigendian)
706 	{
707 		vms_vector swapped_vec;
708 		swapped_vec.x = INTEL_INT(static_cast<int>(del_obj.pos.x));
709 		swapped_vec.y = INTEL_INT(static_cast<int>(del_obj.pos.y));
710 		swapped_vec.z = INTEL_INT(static_cast<int>(del_obj.pos.z));
711 		memcpy(&multibuf[loc], &swapped_vec, sizeof(vms_vector));
712 		loc += 12;
713 	}
714 	else
715 	{
716 		memcpy(&multibuf[loc], &del_obj.pos, sizeof(vms_vector));
717 		loc += 12;
718 	}
719 
720 	memset(&multibuf[loc], -1, MAX_ROBOT_POWERUPS*sizeof(short));
721 #if defined(DXX_BUILD_DESCENT_II)
722    if (del_obj.contains_count != Net_create_loc)
723 	  Int3();  //Get Jason, a bad thing happened
724 #endif
725 
726 	if ((Net_create_loc > MAX_ROBOT_POWERUPS) || (Net_create_loc < 1))
727 	{
728 		Int3(); // See Rob
729 	}
730 	range_for (const auto i, partial_const_range(Net_create_objnums, Net_create_loc))
731 	{
732 		PUT_INTEL_SHORT(&multibuf[loc], i);
733 		loc += 2;
734 		map_objnum_local_to_local(i);
735 	}
736 
737 	Net_create_loc = 0;
738 
739 	multi_send_data(multibuf, 2);
740 }
741 }
742 
multi_do_claim_robot(const playernum_t pnum,const ubyte * buf)743 void multi_do_claim_robot(const playernum_t pnum, const ubyte *buf)
744 {
745 	auto &Objects = LevelUniqueObjectState.Objects;
746 	auto &vmobjptridx = Objects.vmptridx;
747 	multi_claim_robot b;
748 	multi_serialize_read(buf, b);
749 	auto botnum = objnum_remote_to_local(b.robjnum, b.owner);
750 	if (botnum > Highest_object_index)
751 	{
752 		return;
753 	}
754 
755 	const auto &&botp = vmobjptridx(botnum);
756 	if (botp->type != OBJ_ROBOT)
757 	{
758 		return;
759 	}
760 
761 	if (botp->ctype.ai_info.REMOTE_OWNER != -1)
762 	{
763 		if (MULTI_ROBOT_PRIORITY(b.robjnum, pnum) <= MULTI_ROBOT_PRIORITY(b.robjnum, botp->ctype.ai_info.REMOTE_OWNER))
764 			return;
765 	}
766 
767 	// Perform the requested change
768 
769 	if (botp->ctype.ai_info.REMOTE_OWNER == Player_num)
770 	{
771 		multi_delete_controlled_robot(botp);
772 	}
773 
774 	botp->ctype.ai_info.REMOTE_OWNER = pnum;
775 	botp->ctype.ai_info.REMOTE_SLOT_NUM = 0;
776 }
777 
multi_do_release_robot(const playernum_t pnum,const ubyte * buf)778 void multi_do_release_robot(const playernum_t pnum, const ubyte *buf)
779 {
780 	auto &Objects = LevelUniqueObjectState.Objects;
781 	auto &vmobjptr = Objects.vmptr;
782 	short remote_botnum;
783 
784 	remote_botnum = GET_INTEL_SHORT(buf + 2);
785 	auto botnum = objnum_remote_to_local(remote_botnum, buf[4]);
786 
787 	if (botnum > Highest_object_index)
788 	{
789 		return;
790 	}
791 
792 	const auto &&botp = vmobjptr(botnum);
793 	if (botp->type != OBJ_ROBOT)
794 	{
795 		return;
796 	}
797 
798 	if (botp->ctype.ai_info.REMOTE_OWNER != pnum)
799 	{
800 		return;
801 	}
802 
803 	// Perform the requested change
804 
805 	botp->ctype.ai_info.REMOTE_OWNER = -1;
806 	botp->ctype.ai_info.REMOTE_SLOT_NUM = 0;
807 }
808 
multi_do_robot_position(const playernum_t pnum,const ubyte * buf)809 void multi_do_robot_position(const playernum_t pnum, const ubyte *buf)
810 {
811 	auto &Objects = LevelUniqueObjectState.Objects;
812 	auto &vmobjptridx = Objects.vmptridx;
813 	// Process robot movement sent by another player
814 
815 	short remote_botnum;
816 	int loc = 1;
817 
818 	;										loc += 1;
819 
820 	remote_botnum = GET_INTEL_SHORT(buf + loc);
821 	auto botnum = objnum_remote_to_local(remote_botnum, buf[loc+2]); loc += 3;
822 
823 	if (botnum > Highest_object_index)
824 	{
825 		return;
826 	}
827 
828 	const auto robot = vmobjptridx(botnum);
829 
830 	if ((robot->type != OBJ_ROBOT) || (robot->flags & OF_EXPLODING)) {
831 		return;
832 	}
833 
834 	if (robot->ctype.ai_info.REMOTE_OWNER != pnum)
835 	{
836 		if (robot->ctype.ai_info.REMOTE_OWNER == -1)
837 		{
838 			// Robot claim packet must have gotten lost, let this player claim it.
839 			if (robot->ctype.ai_info.REMOTE_SLOT_NUM >= MAX_ROBOTS_CONTROLLED) { // == HANDS_OFF_PERIOD should do the same trick
840 				robot->ctype.ai_info.REMOTE_OWNER = pnum;
841 				robot->ctype.ai_info.REMOTE_SLOT_NUM = 0;
842 			}
843 			else
844 				robot->ctype.ai_info.REMOTE_SLOT_NUM++;
845 		}
846 		else
847 		{
848 			return;
849 		}
850 	}
851 
852 	set_thrust_from_velocity(robot); // Try to smooth out movement
853 //	Objects[botnum].phys_info.drag = Robot_info[Objects[botnum].id].drag >> 4; // Set drag to low
854 
855 	quaternionpos qpp{};
856 	qpp.orient.w = GET_INTEL_SHORT(&buf[loc]);					loc += 2;
857 	qpp.orient.x = GET_INTEL_SHORT(&buf[loc]);					loc += 2;
858 	qpp.orient.y = GET_INTEL_SHORT(&buf[loc]);					loc += 2;
859 	qpp.orient.z = GET_INTEL_SHORT(&buf[loc]);					loc += 2;
860 	qpp.pos.x = GET_INTEL_INT(&buf[loc]);						loc += 4;
861 	qpp.pos.y = GET_INTEL_INT(&buf[loc]);						loc += 4;
862 	qpp.pos.z = GET_INTEL_INT(&buf[loc]);						loc += 4;
863 	qpp.segment = GET_INTEL_SHORT(&buf[loc]);					loc += 2;
864 	qpp.vel.x = GET_INTEL_INT(&buf[loc]);						loc += 4;
865 	qpp.vel.y = GET_INTEL_INT(&buf[loc]);						loc += 4;
866 	qpp.vel.z = GET_INTEL_INT(&buf[loc]);						loc += 4;
867 	qpp.rotvel.x = GET_INTEL_INT(&buf[loc]);					loc += 4;
868 	qpp.rotvel.y = GET_INTEL_INT(&buf[loc]);					loc += 4;
869 	qpp.rotvel.z = GET_INTEL_INT(&buf[loc]);					loc += 4;
870 	extract_quaternionpos(robot, qpp);
871 }
872 
calc_gun_point(const object_base & obj,unsigned gun_num)873 static inline vms_vector calc_gun_point(const object_base &obj, unsigned gun_num)
874 {
875 	vms_vector v;
876 	return calc_gun_point(v, obj, gun_num), v;
877 }
878 
879 namespace dsx {
multi_do_robot_fire(const uint8_t * const buf)880 void multi_do_robot_fire(const uint8_t *const buf)
881 {
882 	auto &Objects = LevelUniqueObjectState.Objects;
883 	auto &vmobjptridx = Objects.vmptridx;
884 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
885 	// Send robot fire event
886 	int loc = 1;
887 	short remote_botnum;
888 	int gun_num;
889 	vms_vector fire;
890                                                                                         loc += 1; // pnum
891 	remote_botnum = GET_INTEL_SHORT(buf + loc);
892 	auto botnum = objnum_remote_to_local(remote_botnum, buf[loc+2]);                loc += 3;
893 	gun_num = static_cast<int8_t>(buf[loc]);                                                      loc += 1;
894 	memcpy(&fire, buf+loc, sizeof(vms_vector));
895 	fire.x = INTEL_INT(fire.x);
896 	fire.y = INTEL_INT(fire.y);
897 	fire.z = INTEL_INT(fire.z);
898 
899 	if (botnum > Highest_object_index)
900 		return;
901 
902 	auto botp = vmobjptridx(botnum);
903 	if (botp->type != OBJ_ROBOT || botp->flags & OF_EXPLODING)
904 		return;
905 
906 	using pt_weapon = std::pair<vms_vector, weapon_id_type>;
907 	const pt_weapon pw =
908 	// Do the firing
909 		(gun_num == -1
910 #if defined(DXX_BUILD_DESCENT_II)
911 		|| gun_num==-2
912 #endif
913 		)
914 		? pt_weapon(vm_vec_add(botp->pos, fire),
915 #if defined(DXX_BUILD_DESCENT_II)
916 			gun_num != -1 ? weapon_id_type::SUPERPROX_ID :
917 #endif
918 			weapon_id_type::PROXIMITY_ID)
919 		: pt_weapon(calc_gun_point(botp, gun_num), get_robot_weapon(Robot_info[get_robot_id(botp)], 1));
920 	Laser_create_new_easy(fire, pw.first, botp, pw.second, 1);
921 }
922 }
923 
924 namespace dsx {
multi_explode_robot_sub(const vmobjptridx_t robot)925 int multi_explode_robot_sub(const vmobjptridx_t robot)
926 {
927 	auto &BossUniqueState = LevelUniqueObjectState.BossState;
928 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
929 	if (robot->type != OBJ_ROBOT) { // Object is robot?
930 		return 0;
931 	}
932 
933 	if (robot->flags & OF_EXPLODING) { // Object not already exploding
934 		return 0;
935 	}
936 
937 	// Data seems valid, explode the sucker
938 
939 	if (Network_send_objects && multi::dispatch->objnum_is_past(robot))
940 	{
941 		Network_send_objnum = -1;
942 	}
943 
944 	// Drop non-random KEY powerups locally only!
945 	if ((robot->contains_count > 0) && (robot->contains_type == OBJ_POWERUP) && (Game_mode & GM_MULTI_COOP) && (robot->contains_id >= POW_KEY_BLUE) && (robot->contains_id <= POW_KEY_GOLD))
946 	{
947 		object_create_robot_egg(robot);
948 	}
949 	else if (robot->ctype.ai_info.REMOTE_OWNER == Player_num)
950 	{
951 		multi_drop_robot_powerups(robot);
952 		multi_delete_controlled_robot(robot);
953 	}
954 	else if (robot->ctype.ai_info.REMOTE_OWNER == -1 && multi_i_am_master())
955 	{
956 		multi_drop_robot_powerups(robot);
957 	}
958 	if (robot_is_thief(Robot_info[get_robot_id(robot)]))
959 		drop_stolen_items(robot);
960 
961 	if (Robot_info[get_robot_id(robot)].boss_flag) {
962 		if (!BossUniqueState.Boss_dying)
963 			start_boss_death_sequence(robot);
964 		else
965 			return (0);
966 	}
967 #if defined(DXX_BUILD_DESCENT_II)
968 	else if (Robot_info[get_robot_id(robot)].death_roll) {
969 		start_robot_death_sequence(robot);
970 	}
971 #endif
972 	else
973 	{
974 #if defined(DXX_BUILD_DESCENT_II)
975 		const auto robot_id = get_robot_id(robot);
976 		if (robot_id == SPECIAL_REACTOR_ROBOT)
977 			special_reactor_stuff();
978 		if (Robot_info[robot_id].kamikaze)
979 			explode_object(robot,1);	//	Kamikaze, explode right away, IN YOUR FACE!
980 		else
981 #endif
982 			explode_object(robot,STANDARD_EXPL_DELAY);
983    }
984 
985 	return 1;
986 }
987 }
988 
multi_do_robot_explode(const uint8_t * const buf)989 void multi_do_robot_explode(const uint8_t *const buf)
990 {
991 	auto &Objects = LevelUniqueObjectState.Objects;
992 	auto &vmobjptridx = Objects.vmptridx;
993 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
994 	multi_explode_robot b;
995 	multi_serialize_read(buf, b);
996 	auto killer = objnum_remote_to_local(b.robj_killer, b.owner_killer);
997 	auto botnum = objnum_remote_to_local(b.robj_killed, b.owner_killed);
998 	// Explode robot controlled by other player
999 	if (botnum > Highest_object_index)
1000 	{
1001 		return;
1002 	}
1003 
1004 	const auto robot = vmobjptridx(botnum);
1005 	const auto rval = multi_explode_robot_sub(robot);
1006 	if (!rval)
1007 		return;
1008 
1009 	++ Players[0u].num_kills_level;
1010 	++ Players[0u].num_kills_total;
1011 	if (killer == get_local_player().objnum)
1012 		add_points_to_score(ConsoleObject->ctype.player_info, Robot_info[get_robot_id(robot)].score_value, Game_mode);
1013 }
1014 
1015 namespace dsx {
1016 
multi_do_create_robot(const d_vclip_array & Vclip,const playernum_t pnum,const ubyte * buf)1017 void multi_do_create_robot(const d_vclip_array &Vclip, const playernum_t pnum, const ubyte *buf)
1018 {
1019 	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1020 	auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
1021 	auto &Station = LevelUniqueFuelcenterState.Station;
1022 	auto &Vertices = LevelSharedVertexState.get_vertices();
1023 	const auto untrusted_fuelcen_num = buf[2];
1024 	int type = buf[5];
1025 
1026 	objnum_t objnum;
1027 	objnum = GET_INTEL_SHORT(buf + 3);
1028 
1029 	if (!LevelUniqueFuelcenterState.Station.valid_index(untrusted_fuelcen_num))
1030 		return;
1031 	if (untrusted_fuelcen_num >= LevelUniqueFuelcenterState.Num_fuelcenters || pnum >= N_players)
1032 	{
1033 		Int3(); // Bogus data
1034 		return;
1035 	}
1036 
1037 	auto &robotcen = Station[(station_number{untrusted_fuelcen_num})];
1038 
1039 	// Play effect and sound
1040 
1041 	// Set robot center flags, in case we become the master for the next one
1042 
1043 	robotcen.Flag = 0;
1044 	robotcen.Capacity -= EnergyToCreateOneRobot;
1045 	robotcen.Timer = 0;
1046 
1047 	const auto &&robotcen_segp = vmsegptridx(robotcen.segnum);
1048 	auto &vcvertptr = Vertices.vcptr;
1049 	const auto &&cur_object_loc = compute_segment_center(vcvertptr, robotcen_segp);
1050 	if (const auto &&obj = object_create_explosion(robotcen_segp, cur_object_loc, i2f(10), VCLIP_MORPHING_ROBOT))
1051 		extract_orient_from_segment(vcvertptr, obj->orient, robotcen_segp);
1052 	if (Vclip[VCLIP_MORPHING_ROBOT].sound_num > -1)
1053 		digi_link_sound_to_pos(Vclip[VCLIP_MORPHING_ROBOT].sound_num, robotcen_segp, 0, cur_object_loc, 0, F1_0);
1054 
1055 	const auto &&obj = create_morph_robot(robotcen_segp, cur_object_loc, type);
1056 	if (obj == object_none)
1057 		return; // Cannot create object!
1058 
1059 	obj->matcen_creator = untrusted_fuelcen_num | 0x80;
1060 //	extract_orient_from_segment(&obj->orient, &Segments[robotcen->segnum]);
1061 	const auto direction = vm_vec_sub(ConsoleObject->pos, obj->pos );
1062 	vm_vector_2_matrix( obj->orient, direction, &obj->orient.uvec, nullptr);
1063 	morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, obj);
1064 
1065 	map_objnum_local_to_remote(obj, objnum, pnum);
1066 
1067 	Assert(obj->ctype.ai_info.REMOTE_OWNER == -1);
1068 }
1069 
1070 #if defined(DXX_BUILD_DESCENT_II)
multi_send_escort_goal(const d_unique_buddy_state & BuddyState)1071 void multi_send_escort_goal(const d_unique_buddy_state &BuddyState)
1072 {
1073 	update_buddy_state b;
1074 	b.Looking_for_marker = static_cast<uint8_t>(BuddyState.Looking_for_marker);
1075 	b.Escort_special_goal = BuddyState.Escort_special_goal;
1076 	b.Last_buddy_key = BuddyState.Last_buddy_key;
1077 	multi_serialize_write(2, b);
1078 }
1079 
multi_recv_escort_goal(d_unique_buddy_state & BuddyState,const uint8_t * const buf)1080 void multi_recv_escort_goal(d_unique_buddy_state &BuddyState, const uint8_t *const buf)
1081 {
1082 	update_buddy_state b;
1083 	multi_serialize_read(buf, b);
1084 	const auto Looking_for_marker = b.Looking_for_marker;
1085 	BuddyState.Looking_for_marker = MarkerState.imobjidx.valid_index(Looking_for_marker)
1086 		? static_cast<game_marker_index>(Looking_for_marker)
1087 		: game_marker_index::None;
1088 	BuddyState.Escort_special_goal = b.Escort_special_goal;
1089 	BuddyState.Last_buddy_key = b.Last_buddy_key;
1090 	BuddyState.Buddy_messages_suppressed = 0;
1091 	BuddyState.Last_buddy_message_time = GameTime64 - 2 * F1_0;
1092 	BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
1093 }
1094 #endif
1095 
multi_do_boss_teleport(const d_vclip_array & Vclip,const playernum_t pnum,const ubyte * buf)1096 void multi_do_boss_teleport(const d_vclip_array &Vclip, const playernum_t pnum, const ubyte *buf)
1097 {
1098 	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1099 	auto &BossUniqueState = LevelUniqueObjectState.BossState;
1100 	auto &Objects = LevelUniqueObjectState.Objects;
1101 	auto &Vertices = LevelSharedVertexState.get_vertices();
1102 	auto &vcobjptr = Objects.vcptr;
1103 	auto &vmobjptr = Objects.vmptr;
1104 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1105 	boss_teleport b;
1106 	multi_serialize_read(buf, b);
1107 	const auto &&guarded_boss_obj = Objects.vmptridx.check_untrusted(b.objnum);
1108 	if (!guarded_boss_obj)
1109 		return;
1110 	const auto &&boss_obj = *guarded_boss_obj;
1111 	if ((boss_obj->type != OBJ_ROBOT) || !(Robot_info[get_robot_id(boss_obj)].boss_flag))
1112 	{
1113 		Int3(); // Got boss actions for a robot who's not a boss?
1114 		return;
1115 	}
1116 	const auto &&guarded_teleport_segnum = vmsegptridx.check_untrusted(b.where);
1117 	if (!guarded_teleport_segnum)
1118 		return;
1119 	const auto &&teleport_segnum = *guarded_teleport_segnum;
1120 	auto &vcvertptr = Vertices.vcptr;
1121 	compute_segment_center(vcvertptr, boss_obj->pos, teleport_segnum);
1122 	obj_relink(vmobjptr, vmsegptr, boss_obj, teleport_segnum);
1123 	BossUniqueState.Last_teleport_time = GameTime64;
1124 
1125 	const auto boss_dir = vm_vec_sub(vcobjptr(vcplayerptr(pnum)->objnum)->pos, boss_obj->pos);
1126 	vm_vector_2_matrix(boss_obj->orient, boss_dir, nullptr, nullptr);
1127 
1128 	digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, teleport_segnum, 0, boss_obj->pos, 0 , F1_0);
1129 	digi_kill_sound_linked_to_object( boss_obj);
1130 	boss_link_see_sound(boss_obj);
1131 	ai_local		*ailp = &boss_obj->ctype.ai_info.ail;
1132 	ailp->next_fire = 0;
1133 
1134 	if (boss_obj->ctype.ai_info.REMOTE_OWNER == Player_num)
1135 	{
1136 		multi_delete_controlled_robot(boss_obj);
1137 	}
1138 
1139 	boss_obj->ctype.ai_info.REMOTE_OWNER = -1; // Boss is up for grabs again!
1140 	boss_obj->ctype.ai_info.REMOTE_SLOT_NUM = 0; // Available immediately!
1141 }
1142 
multi_do_boss_cloak(const ubyte * buf)1143 void multi_do_boss_cloak(const ubyte *buf)
1144 {
1145 	auto &BossUniqueState = LevelUniqueObjectState.BossState;
1146 	auto &Objects = LevelUniqueObjectState.Objects;
1147 	auto &vmobjptridx = Objects.vmptridx;
1148 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1149 	boss_cloak b;
1150 	multi_serialize_read(buf, b);
1151 	const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum);
1152 	if (!guarded_boss_obj)
1153 		return;
1154 	const auto &&boss_obj = *guarded_boss_obj;
1155 	if (boss_obj->type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag)
1156 	{
1157 		Int3(); // Got boss actions for a robot who's not a boss?
1158 		return;
1159 	}
1160 	BossUniqueState.Boss_hit_this_frame = 0;
1161 #if defined(DXX_BUILD_DESCENT_II)
1162 	BossUniqueState.Boss_hit_time = -F1_0*10;
1163 #endif
1164 	BossUniqueState.Boss_cloak_start_time = GameTime64;
1165 	boss_obj->ctype.ai_info.CLOAKED = 1;
1166 }
1167 }
1168 
multi_do_boss_start_gate(const ubyte * buf)1169 void multi_do_boss_start_gate(const ubyte *buf)
1170 {
1171 	auto &Objects = LevelUniqueObjectState.Objects;
1172 	auto &vmobjptridx = Objects.vmptridx;
1173 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1174 	boss_start_gate b;
1175 	multi_serialize_read(buf, b);
1176 	const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum);
1177 	if (!guarded_boss_obj)
1178 		return;
1179 	const object_base &boss_obj = *guarded_boss_obj;
1180 	if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag)
1181 	{
1182 		Int3(); // Got boss actions for a robot who's not a boss?
1183 		return;
1184 	}
1185 	restart_effect(ECLIP_NUM_BOSS);
1186 }
1187 
multi_do_boss_stop_gate(const ubyte * buf)1188 void multi_do_boss_stop_gate(const ubyte *buf)
1189 {
1190 	auto &Objects = LevelUniqueObjectState.Objects;
1191 	auto &vmobjptridx = Objects.vmptridx;
1192 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1193 	boss_start_gate b;
1194 	multi_serialize_read(buf, b);
1195 	const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum);
1196 	if (!guarded_boss_obj)
1197 		return;
1198 	const object_base &boss_obj = *guarded_boss_obj;
1199 	if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag)
1200 	{
1201 		Int3(); // Got boss actions for a robot who's not a boss?
1202 		return;
1203 	}
1204 	stop_effect(ECLIP_NUM_BOSS);
1205 }
1206 
multi_do_boss_create_robot(const playernum_t pnum,const ubyte * buf)1207 void multi_do_boss_create_robot(const playernum_t pnum, const ubyte *buf)
1208 {
1209 	auto &Objects = LevelUniqueObjectState.Objects;
1210 	auto &vmobjptridx = Objects.vmptridx;
1211 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1212 	boss_create_robot b;
1213 	multi_serialize_read(buf, b);
1214 	const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum);
1215 	if (!guarded_boss_obj)
1216 		return;
1217 	const object_base &boss_obj = *guarded_boss_obj;
1218 	if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag)
1219 	{
1220 		Int3(); // Got boss actions for a robot who's not a boss?
1221 		return;
1222 	}
1223 	// Do some validity checking
1224 	if (b.objrobot >= MAX_OBJECTS)
1225 	{
1226 		Int3(); // See Rob, bad data in boss gate action message
1227 		return;
1228 	}
1229 	// Gate one in!
1230 	const auto &&robot = gate_in_robot(b.robot_type, vmsegptridx(b.where));
1231 	if (robot != object_none)
1232 		map_objnum_local_to_remote(robot, b.objrobot, pnum);
1233 }
1234 
multi_do_create_robot_powerups(const playernum_t pnum,const ubyte * buf)1235 void multi_do_create_robot_powerups(const playernum_t pnum, const ubyte *buf)
1236 {
1237 	auto &Objects = LevelUniqueObjectState.Objects;
1238 	auto &vmobjptr = Objects.vmptr;
1239 	// Code to drop remote-controlled robot powerups
1240 
1241 	int loc = 1;
1242 	;					loc += 1;
1243 	uint8_t contains_count = buf[loc];			loc += 1;
1244 	uint8_t contains_type = buf[loc];			loc += 1;
1245 	uint8_t contains_id = buf[loc]; 			loc += 1;
1246 	segnum_t segnum = GET_INTEL_SHORT(buf + loc);            loc += 2;
1247 	vms_vector pos;
1248 	memcpy(&pos, &buf[loc], sizeof(pos));      loc += 12;
1249 
1250 	vms_vector velocity{};
1251 	pos.x = INTEL_INT(pos.x);
1252 	pos.y = INTEL_INT(pos.y);
1253 	pos.z = INTEL_INT(pos.z);
1254 
1255 	Assert(pnum < N_players);
1256 	Assert (pnum!=Player_num); // What? How'd we send ourselves this?
1257 
1258 	Net_create_loc = 0;
1259 	d_srand(1245L);
1260 
1261 	const auto &&egg_objnum = object_create_robot_egg(contains_type, contains_id, contains_count, velocity, pos, vmsegptridx(segnum));
1262 
1263 	if (egg_objnum == object_none)
1264 		return; // Object buffer full
1265 
1266 //	Assert(egg_objnum > -1);
1267 	Assert((Net_create_loc > 0) && (Net_create_loc <= MAX_ROBOT_POWERUPS));
1268 
1269 	range_for (const auto i, partial_const_range(Net_create_objnums, Net_create_loc))
1270 	{
1271 		short s;
1272 
1273 		s = GET_INTEL_SHORT(buf + loc);
1274 		if ( s != -1)
1275 			map_objnum_local_to_remote(i, s, pnum);
1276 		else
1277 			vmobjptr(i)->flags |= OF_SHOULD_BE_DEAD; // Delete objects other guy didn't create one of
1278 		loc += 2;
1279 	}
1280 }
1281 
multi_drop_robot_powerups(const vmobjptr_t del_obj)1282 void multi_drop_robot_powerups(const vmobjptr_t del_obj)
1283 {
1284 	// Code to handle dropped robot powerups in network mode ONLY!
1285 
1286 	objnum_t egg_objnum = object_none;
1287 
1288 	if (del_obj->type != OBJ_ROBOT)
1289 	{
1290 		Int3(); // dropping powerups for non-robot, Rob's fault
1291 		return;
1292 	}
1293 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1294 	auto &robptr = Robot_info[get_robot_id(del_obj)];
1295 
1296 	Net_create_loc = 0;
1297 
1298 	if (del_obj->contains_count > 0) {
1299 		//	If dropping a weapon that the player has, drop energy instead, unless it's vulcan, in which case drop vulcan ammo.
1300 		if (del_obj->contains_type == OBJ_POWERUP) {
1301 			maybe_replace_powerup_with_energy(del_obj);
1302 			if (!multi_powerup_is_allowed(del_obj->contains_id, Netgame.AllowedItems))
1303 				del_obj->contains_id=POW_SHIELD_BOOST;
1304 
1305 			// No key drops in non-coop games!
1306 			if (!(Game_mode & GM_MULTI_COOP)) {
1307 				if ((del_obj->contains_id >= POW_KEY_BLUE) && (del_obj->contains_id <= POW_KEY_GOLD))
1308 					del_obj->contains_count = 0;
1309 			}
1310 		}
1311 		d_srand(1245L);
1312 		if (del_obj->contains_count > 0)
1313 			egg_objnum = object_create_robot_egg(del_obj);
1314 	}
1315 
1316 	else if (del_obj->ctype.ai_info.REMOTE_OWNER == -1) // No random goodies for robots we weren't in control of
1317 		return;
1318 
1319 	else if (robptr.contains_count) {
1320 		d_srand(static_cast<fix>(timer_query()));
1321 		if (((d_rand() * 16) >> 15) < robptr.contains_prob) {
1322 			del_obj->contains_count = ((d_rand() * robptr.contains_count) >> 15) + 1;
1323 			del_obj->contains_type = robptr.contains_type;
1324 			del_obj->contains_id = robptr.contains_id;
1325 			if (del_obj->contains_type == OBJ_POWERUP)
1326 			 {
1327 				maybe_replace_powerup_with_energy(del_obj);
1328 				if (!multi_powerup_is_allowed(del_obj->contains_id, Netgame.AllowedItems))
1329 					del_obj->contains_id=POW_SHIELD_BOOST;
1330 			 }
1331 
1332 			d_srand(1245L);
1333 			if (del_obj->contains_count > 0)
1334 				egg_objnum = object_create_robot_egg(del_obj);
1335 		}
1336 	}
1337 
1338 	if (egg_objnum != object_none) {
1339 		// Transmit the object creation to the other players
1340 		multi_send_create_robot_powerups(del_obj);
1341 	}
1342 }
1343 
1344 //	-----------------------------------------------------------------------------
1345 //	Robot *robot got whacked by player player_num and requests permission to do something about it.
1346 //	Note: This function will be called regardless of whether Game_mode is a multiplayer mode, so it
1347 //	should quick-out if not in a multiplayer mode.  On the other hand, it only gets called when a
1348 //	player or player weapon whacks a robot, so it happens rarely.
1349 namespace dsx {
multi_robot_request_change(const vmobjptridx_t robot,int player_num)1350 void multi_robot_request_change(const vmobjptridx_t robot, int player_num)
1351 {
1352 	if (!(Game_mode & GM_MULTI_ROBOTS))
1353 		return;
1354 #if defined(DXX_BUILD_DESCENT_I)
1355 	if (robot->ctype.ai_info.REMOTE_OWNER != Player_num)
1356 		return;
1357 #endif
1358 
1359 	const auto slot = robot->ctype.ai_info.REMOTE_SLOT_NUM;
1360 
1361 	if (slot == HANDS_OFF_PERIOD)
1362 	{
1363 		con_printf(CON_DEBUG, "Suppressing debugger trap for hands off robot %hu with player %i", static_cast<vmobjptridx_t::integral_type>(robot), player_num);
1364 		return;
1365 	}
1366 	if ((slot < 0) || (slot >= MAX_ROBOTS_CONTROLLED)) {
1367 		Int3();
1368 		return;
1369 	}
1370 	if (robot_controlled[slot] == object_none)
1371 		return;
1372 	const auto &&rcrobot = robot.absolute_sibling(robot_controlled[slot]);
1373 	const auto remote_objnum = objnum_local_to_remote(robot).objnum;
1374 	if (remote_objnum >= MAX_OBJECTS)
1375 		return;
1376 
1377 	if ( (robot_agitation[slot] < 70) || (MULTI_ROBOT_PRIORITY(remote_objnum, player_num) > MULTI_ROBOT_PRIORITY(remote_objnum, Player_num)) || (d_rand() > 0x4400))
1378 	{
1379 		if (robot_send_pending[slot])
1380 			multi_send_robot_position(rcrobot, -1);
1381 		multi_send_release_robot(rcrobot);
1382 		robot->ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD;  // Hands-off period
1383 	}
1384 }
1385 
1386 }
1387