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