Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

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