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 code for network play.
  23.  *
  24.  */
  25.  
  26. #include <bitset>
  27. #include <stdexcept>
  28. #include <random>
  29. #include <stdio.h>
  30. #include <stdlib.h>
  31. #include <string.h>
  32. #include <time.h>
  33. #include <ctype.h>
  34. #include <inttypes.h>
  35.  
  36. #include "u_mem.h"
  37. #include "strutil.h"
  38. #include "game.h"
  39. #include "multi.h"
  40. #include "multiinternal.h"
  41. #include "object.h"
  42. #include "player.h"
  43. #include "laser.h"
  44. #include "fuelcen.h"
  45. #include "scores.h"
  46. #include "gauges.h"
  47. #include "gameseg.h"
  48. #include "weapon.h"
  49. #include "collide.h"
  50. #include "dxxerror.h"
  51. #include "fireball.h"
  52. #include "newmenu.h"
  53. #include "console.h"
  54. #include "wall.h"
  55. #include "cntrlcen.h"
  56. #include "powerup.h"
  57. #include "polyobj.h"
  58. #include "bm.h"
  59. #include "endlevel.h"
  60. #include "key.h"
  61. #include "playsave.h"
  62. #include "timer.h"
  63. #include "digi.h"
  64. #include "sounds.h"
  65. #include "kconfig.h"
  66. #include "hudmsg.h"
  67. #include "newdemo.h"
  68. #include "text.h"
  69. #include "kmatrix.h"
  70. #include "multibot.h"
  71. #include "gameseq.h"
  72. #include "physics.h"
  73. #include "config.h"
  74. #include "ai.h"
  75. #include "switch.h"
  76. #include "textures.h"
  77. #include "sounds.h"
  78. #include "args.h"
  79. #include "effects.h"
  80. #include "iff.h"
  81. #include "state.h"
  82. #include "automap.h"
  83. #include "event.h"
  84. #if DXX_USE_UDP
  85. #include "net_udp.h"
  86. #endif
  87. #include "d_enumerate.h"
  88. #include "d_range.h"
  89.  
  90. #include "partial_range.h"
  91. #include <utility>
  92.  
  93. constexpr std::integral_constant<int8_t, -1> owner_none{};
  94.  
  95. namespace dsx {
  96. static void multi_reset_object_texture(object_base &objp);
  97. static void multi_new_bounty_target(playernum_t pnum);
  98. static void multi_process_data(playernum_t pnum, const ubyte *dat, uint_fast32_t type);
  99. static void multi_update_objects_for_non_cooperative();
  100. static void multi_restore_game(unsigned slot, unsigned id);
  101. static void multi_save_game(unsigned slot, unsigned id, const d_game_unique_state::savegame_description &desc);
  102. }
  103. static void multi_add_lifetime_killed();
  104. static void multi_send_heartbeat();
  105. #if defined(DXX_BUILD_DESCENT_II)
  106. namespace dsx {
  107. static std::size_t find_goal_texture(ubyte t);
  108. static const tmap_info &find_required_goal_texture(uint8_t t);
  109. static void multi_do_capture_bonus(const playernum_t pnum);
  110. static void multi_do_orb_bonus(const playernum_t pnum, const ubyte *buf);
  111. static void multi_send_drop_flag(vmobjptridx_t objnum,int seed);
  112. }
  113. #endif
  114. static void multi_send_ranking(uint8_t);
  115. static void multi_send_gmode_update();
  116. namespace dcx {
  117. static void multi_send_quit();
  118. DEFINE_SERIAL_UDT_TO_MESSAGE(shortpos, s, (s.bytemat, s.xo, s.yo, s.zo, s.segment, s.velx, s.vely, s.velz));
  119. }
  120. static playernum_t multi_who_is_master();
  121. static void multi_show_player_list();
  122. static void multi_send_message();
  123.  
  124. #if !(!defined(RELEASE) && defined(DXX_BUILD_DESCENT_II))
  125. static void multi_add_lifetime_kills(int count);
  126. #endif
  127.  
  128. //
  129. // Global variables
  130. //
  131.  
  132. namespace dcx {
  133.  
  134. int multi_protocol=0; // set and determinate used protocol
  135. static int imulti_new_game; // to prep stuff for level only when starting new game
  136.  
  137. //do we draw the kill list on the HUD?
  138. int Show_kill_list = 1;
  139. int Show_reticle_name = 1;
  140. fix Show_kill_list_timer = 0;
  141.  
  142. }
  143.  
  144. #if defined(DXX_BUILD_DESCENT_II)
  145. namespace dsx {
  146. hoard_highest_record hoard_highest_record_stats;
  147.  
  148. char Multi_is_guided=0;
  149. }
  150. #endif
  151.  
  152. namespace dcx {
  153.  
  154. playernum_t Bounty_target;
  155.  
  156.  
  157. std::array<msgsend_state_t, MAX_PLAYERS> multi_sending_message;
  158. int multi_defining_message = 0;
  159. static int multi_message_index;
  160.  
  161. static std::array<std::array<objnum_t, MAX_OBJECTS>, MAX_PLAYERS> remote_to_local;  // Remote object number for each local object
  162. static std::array<uint16_t, MAX_OBJECTS> local_to_remote;
  163. std::array<sbyte, MAX_OBJECTS> object_owner;   // Who created each object in my universe, -1 = loaded at start
  164.  
  165. unsigned   Net_create_loc;       // pointer into previous array
  166. std::array<objnum_t, MAX_NET_CREATE_OBJECTS>   Net_create_objnums; // For tracking object creation that will be sent to remote
  167. int   Network_status = 0;
  168. ntstring<MAX_MESSAGE_LEN - 1> Network_message;
  169. int   Network_message_reciever=-1;
  170. static std::array<unsigned, MAX_PLAYERS>   sorted_kills;
  171. std::array<std::array<uint16_t, MAX_PLAYERS>, MAX_PLAYERS> kill_matrix;
  172. std::array<int16_t, 2> team_kills;
  173. int   multi_quit_game = 0;
  174.  
  175. }
  176.  
  177. namespace dsx {
  178.  
  179. const GMNames_array GMNames = {{
  180.         "Anarchy",
  181.         "Team Anarchy",
  182.         "Robo Anarchy",
  183.         "Cooperative",
  184. #if defined(DXX_BUILD_DESCENT_I)
  185.         "Unknown",
  186.         "Unknown",
  187.         "Unknown",
  188. #elif defined(DXX_BUILD_DESCENT_II)
  189.         "Capture the Flag",
  190.         "Hoard",
  191.         "Team Hoard",
  192. #endif
  193.         "Bounty"
  194. }};
  195. const std::array<char[8], MULTI_GAME_TYPE_COUNT> GMNamesShrt = {{
  196.         "ANRCHY",
  197.         "TEAM",
  198.         "ROBO",
  199.         "COOP",
  200. #if defined(DXX_BUILD_DESCENT_I)
  201.         "UNKNOWN",
  202.         "UNKNOWN",
  203.         "UNKNOWN",
  204. #elif defined(DXX_BUILD_DESCENT_II)
  205.         "FLAG",
  206.         "HOARD",
  207.         "TMHOARD",
  208. #endif
  209.         "BOUNTY"
  210. }};
  211.  
  212. }
  213.  
  214. namespace dcx {
  215.  
  216. // For rejoin object syncing (used here and all protocols - globally)
  217.  
  218. int     Network_send_objects = 0;  // Are we in the process of sending objects to a player?
  219. int     Network_send_object_mode = 0; // What type of objects are we sending, static or dynamic?
  220. int     Network_send_objnum = -1;   // What object are we sending next?
  221. int     Network_rejoined = 0;       // Did WE rejoin this game?
  222. int     Network_sending_extras=0;
  223. int     VerifyPlayerJoined=-1;      // Player (num) to enter game before any ingame/extra stuff is being sent
  224. int     Player_joining_extras=-1;  // This is so we know who to send 'latecomer' packets to.
  225. int     Network_player_added = 0;   // Is this a new player or a returning player?
  226.  
  227. ushort          my_segments_checksum = 0;
  228.  
  229.  
  230. std::array<std::array<bitmap_index, N_PLAYER_SHIP_TEXTURES>, MAX_PLAYERS> multi_player_textures;
  231.  
  232. // Globals for protocol-bound Refuse-functions
  233. char RefuseThisPlayer=0,WaitForRefuseAnswer=0,RefuseTeam,RefusePlayerName[12];
  234. fix64 RefuseTimeLimit=0;
  235.  
  236. constexpr int message_length[] = {
  237. #define define_message_length(NAME,SIZE)        (SIZE),
  238.         for_each_multiplayer_command(define_message_length)
  239. };
  240.  
  241. }
  242.  
  243. namespace dsx {
  244.  
  245. netgame_info Netgame;
  246. multi_level_inv MultiLevelInv;
  247.  
  248. }
  249.  
  250. namespace dcx {
  251. const std::array<char[16], 10> RankStrings{{
  252.         "(unpatched)",
  253.         "Cadet",
  254.         "Ensign",
  255.         "Lieutenant",
  256.         "Lt.Commander",
  257.         "Commander",
  258.         "Captain",
  259.         "Vice Admiral",
  260.         "Admiral",
  261.         "Demigod"
  262. }};
  263. }
  264.  
  265. namespace dsx {
  266. const multi_allow_powerup_text_array multi_allow_powerup_text = {{
  267. #define define_netflag_string(NAME,STR) STR,
  268.         for_each_netflag_value(define_netflag_string)
  269. }};
  270. }
  271.  
  272. int GetMyNetRanking()
  273. {
  274.         int rank, eff;
  275.  
  276.         if (PlayerCfg.NetlifeKills + PlayerCfg.NetlifeKilled <= 0)
  277.                 return (1);
  278.  
  279.         rank=static_cast<int>((static_cast<float>(PlayerCfg.NetlifeKills)/3000.0)*8.0);
  280.  
  281.         eff = static_cast<int>(
  282.                 (
  283.                         static_cast<float>(PlayerCfg.NetlifeKills) / (
  284.                                 static_cast<float>(PlayerCfg.NetlifeKilled) + static_cast<float>(PlayerCfg.NetlifeKills)
  285.                         )
  286.                 ) * 100.0
  287.         );
  288.  
  289.         if (rank>8)
  290.                 rank=8;
  291.  
  292.         if (eff<0)
  293.                 eff=0;
  294.  
  295.         if (eff<60)
  296.                 rank-=((59-eff)/10);
  297.  
  298.         if (rank<0)
  299.                 rank=0;
  300.         if (rank>8)
  301.                 rank=8;
  302.  
  303.         return (rank+1);
  304. }
  305.  
  306. void ClipRank (ubyte *rank)
  307. {
  308.         // This function insures no crashes when dealing with D2 1.0
  309.         if (*rank > 9)
  310.                 *rank = 0;
  311. }
  312.  
  313. //
  314. //  Functions that replace what used to be macros
  315. //
  316.  
  317. objnum_t objnum_remote_to_local(uint16_t remote_objnum, int8_t owner)
  318. {
  319.         if (owner == owner_none)
  320.                 return(remote_objnum);
  321.         // Map a remote object number from owner to a local object number
  322.         if ((owner >= N_players) || (owner < -1)) {
  323.                 Int3(); // Illegal!
  324.                 return(remote_objnum);
  325.         }
  326.  
  327.         if (remote_objnum >= MAX_OBJECTS)
  328.                 return(object_none);
  329.  
  330.         auto result = remote_to_local[owner][remote_objnum];
  331.         return(result);
  332. }
  333.  
  334. owned_remote_objnum objnum_local_to_remote(objnum_t local_objnum)
  335. {
  336.         auto &Objects = LevelUniqueObjectState.Objects;
  337.         // Map a local object number to a remote + owner
  338.         if (local_objnum > Highest_object_index)
  339.         {
  340.                 return {owner_none, 0xffff};
  341.         }
  342.         auto owner = object_owner[local_objnum];
  343.         if (owner == owner_none)
  344.                 return {owner, local_objnum};
  345.         auto result = local_to_remote[local_objnum];
  346.         const char *emsg;
  347.         if (
  348.                 ((owner >= N_players || owner < -1) && (emsg = "illegal object owner", true)) ||
  349.                 (result >= MAX_OBJECTS && (emsg = "illegal object remote number", true))        // See Rob, object has no remote number!
  350.         )
  351.                 throw std::runtime_error(emsg);
  352.         return {owner, result};
  353. }
  354.  
  355. uint16_t objnum_local_to_remote(objnum_t local_objnum, int8_t *owner)
  356. {
  357.         auto r = objnum_local_to_remote(local_objnum);
  358.         *owner = r.owner;
  359.         return r.objnum;
  360. }
  361.  
  362. void map_objnum_local_to_remote(const int local_objnum, const int remote_objnum, const int owner)
  363. {
  364.         // Add a mapping from a network remote object number to a local one
  365.  
  366.         Assert(local_objnum > -1);
  367.         Assert(local_objnum < MAX_OBJECTS);
  368.         Assert(remote_objnum > -1);
  369.         Assert(remote_objnum < MAX_OBJECTS);
  370.         Assert(owner > -1);
  371.         Assert(owner != Player_num);
  372.  
  373.         object_owner[local_objnum] = owner;
  374.  
  375.         remote_to_local[owner][remote_objnum] = local_objnum;
  376.         local_to_remote[local_objnum] = remote_objnum;
  377.  
  378.         return;
  379. }
  380.  
  381. void map_objnum_local_to_local(objnum_t local_objnum)
  382. {
  383.         // Add a mapping for our locally created objects
  384.         Assert(local_objnum < MAX_OBJECTS);
  385.  
  386.         object_owner[local_objnum] = Player_num;
  387.         remote_to_local[Player_num][local_objnum] = local_objnum;
  388.         local_to_remote[local_objnum] = local_objnum;
  389.  
  390.         return;
  391. }
  392.  
  393. void reset_network_objects()
  394. {
  395.         local_to_remote.fill(-1);
  396.         range_for (auto &i, remote_to_local)
  397.                 i.fill(object_none);
  398.         object_owner.fill(-1);
  399. }
  400.  
  401. int multi_objnum_is_past(objnum_t objnum)
  402. {
  403.         switch (multi_protocol)
  404.         {
  405.                 case MULTI_PROTO_UDP:
  406. #if DXX_USE_UDP
  407.                         return net_udp_objnum_is_past(objnum);
  408.                         break;
  409. #endif
  410.                 default:
  411.                         (void)objnum;
  412.                         Error("Protocol handling missing in multi_objnum_is_past\n");
  413.                         break;
  414.         }
  415. }
  416.  
  417. namespace dsx {
  418.  
  419. //
  420. // Part 1 : functions whose main purpose in life is to divert the flow
  421. //          of execution to either network  specific code based
  422. //          on the curretn Game_mode value.
  423. //
  424.  
  425. // Show a score list to end of net players
  426. kmatrix_result multi_endlevel_score()
  427. {
  428.         auto &Objects = LevelUniqueObjectState.Objects;
  429.         auto &vmobjptr = Objects.vmptr;
  430.         int old_connect=0, game_wind_visible = 0;
  431.  
  432.         // If there still is a Game_wind and it's suspended (usually both should be the case), bring it up again so host can still take actions of the game
  433.         if (Game_wind)
  434.         {
  435.                 if (!window_is_visible(Game_wind))
  436.                 {
  437.                         game_wind_visible = 1;
  438.                         window_set_visible(Game_wind, 1);
  439.                 }
  440.         }
  441.         // Save connect state and change to new connect state
  442.         if (Game_mode & GM_NETWORK)
  443.         {
  444.                 auto &plr = get_local_player();
  445.                 old_connect = plr.connected;
  446.                 if (plr.connected != CONNECT_DIED_IN_MINE)
  447.                         plr.connected = CONNECT_END_MENU;
  448.                 Network_status = NETSTAT_ENDLEVEL;
  449.         }
  450.  
  451.         // Do the actual screen we wish to show
  452.         const auto rval = kmatrix_view(Game_mode & GM_NETWORK);
  453.  
  454.         // Restore connect state
  455.  
  456.         if (Game_mode & GM_NETWORK)
  457.         {
  458.                 get_local_player().connected = old_connect;
  459.         }
  460.  
  461.         /* Door key flags should only be cleared in cooperative games, not
  462.          * in other games.
  463.          *
  464.          * The capture-the-flag marker can be cleared unconditionally, but
  465.          * would never have been set in a cooperative game.
  466.          *
  467.          * The kill goal count can be cleared unconditionally.
  468.          *
  469.          * For Descent 1, the only flags to clear are the door key flags.
  470.          * Use a no-op mask in non-cooperative games, since there are no
  471.          * flags to clear there.
  472.          *
  473.          * For Descent 2, clear door key flags or the capture-the-flag
  474.          * flag, depending on game type.  This version has the advantage of
  475.          * making only one pass when in cooperative mode, where the previous
  476.          * version would make one pass if in a cooperative game, then make
  477.          * an unconditional pass to try to clear PLAYER_FLAGS_FLAG.
  478.          */
  479.         const auto clear_flags = (Game_mode & GM_MULTI_COOP)
  480.                 // Reset keys
  481.                 ? ~player_flags(PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY)
  482.                 :
  483. #if defined(DXX_BUILD_DESCENT_I)
  484.                 /* Nothing to clear.  Set a mask that has no effect when
  485.                  * applied, so that the loop does not need to retest the
  486.                  * conditional on each pass.
  487.                  */
  488.                 player_flags(~0u)
  489. #elif defined(DXX_BUILD_DESCENT_II)
  490.                 // Clear capture flag
  491.                 ~player_flags(PLAYER_FLAGS_FLAG)
  492. #endif
  493.                 ;
  494.         range_for (auto &i, partial_const_range(Players, Netgame.max_numplayers))
  495.         {
  496.                 auto &obj = *vmobjptr(i.objnum);
  497.                 auto &player_info = obj.ctype.player_info;
  498.                 player_info.powerup_flags &= clear_flags;
  499.                 player_info.KillGoalCount = 0;
  500.         }
  501.  
  502.         // hide Game_wind again if we brought it up
  503.         if (Game_wind && game_wind_visible)
  504.                 window_set_visible(Game_wind, 0);
  505.  
  506.         return rval;
  507. }
  508.  
  509. }
  510.  
  511. int get_team(const playernum_t pnum)
  512. {
  513.         if (Netgame.team_vector & (1 << pnum))
  514.                 return 1;
  515.         else
  516.                 return 0;
  517. }
  518.  
  519. void multi_new_game()
  520. {
  521.         // Reset variables for a new net game
  522.         reset_globals_for_new_game();
  523.  
  524.         LevelUniqueObjectState.accumulated_robots = 0;
  525.         LevelUniqueObjectState.total_hostages = 0;
  526.         GameUniqueState.accumulated_robots = 0;
  527.         GameUniqueState.total_hostages = 0;
  528.         for (uint_fast32_t i = 0; i < MAX_PLAYERS; i++)
  529.                 init_player_stats_game(i);
  530.  
  531.         kill_matrix = {}; // Clear kill matrix
  532.  
  533.         for (playernum_t i = 0; i < MAX_PLAYERS; ++i)
  534.         {
  535.                 sorted_kills[i] = i;
  536.                 vmplayerptr(i)->connected = CONNECT_DISCONNECTED;
  537.         }
  538.         multi_sending_message.fill(msgsend_none);
  539.  
  540.         robot_controlled.fill(-1);
  541.         robot_agitation = {};
  542.         robot_fired = {};
  543.  
  544.         team_kills = {};
  545.         imulti_new_game=1;
  546.         multi_quit_game = 0;
  547.         Show_kill_list = 1;
  548.         game_disable_cheats();
  549. }
  550.  
  551. namespace dsx {
  552.  
  553. void multi_make_player_ghost(const playernum_t playernum)
  554. {
  555.         auto &Objects = LevelUniqueObjectState.Objects;
  556.         auto &vmobjptridx = Objects.vmptridx;
  557.         if (playernum == Player_num || playernum >= MAX_PLAYERS)
  558.         {
  559.                 Int3(); // Non-terminal, see Rob
  560.                 return;
  561.         }
  562.         const auto &&obj = vmobjptridx(vcplayerptr(playernum)->objnum);
  563.         obj->type = OBJ_GHOST;
  564.         obj->render_type = RT_NONE;
  565.         obj->movement_type = MT_NONE;
  566.         multi_reset_player_object(obj);
  567.         multi_strip_robots(playernum);
  568. }
  569.  
  570. void multi_make_ghost_player(const playernum_t playernum)
  571. {
  572.         auto &Objects = LevelUniqueObjectState.Objects;
  573.         auto &vmobjptridx = Objects.vmptridx;
  574.         if ((playernum == Player_num) || (playernum >= MAX_PLAYERS))
  575.         {
  576.                 Int3(); // Non-terminal, see rob
  577.                 return;
  578.         }
  579.         const auto &&obj = vmobjptridx(vcplayerptr(playernum)->objnum);
  580.         obj->type = OBJ_PLAYER;
  581.         obj->movement_type = MT_PHYSICS;
  582.         multi_reset_player_object(obj);
  583.         if (playernum != Player_num)
  584.                 init_player_stats_new_ship(playernum);
  585. }
  586.  
  587. }
  588.  
  589. int multi_get_kill_list(playernum_array_t &plist)
  590. {
  591.         // Returns the number of active net players and their
  592.         // sorted order of kills
  593.         int n = 0;
  594.  
  595.         range_for (const auto i, partial_const_range(sorted_kills, N_players))
  596.                 //if (Players[sorted_kills[i]].connected)
  597.                 plist[n++] = i;
  598.  
  599.         if (n == 0)
  600.                 Int3(); // SEE ROB OR MATT
  601.  
  602.         //memcpy(plist, sorted_kills, N_players*sizeof(int));
  603.  
  604.         return(n);
  605. }
  606.  
  607. namespace dsx {
  608.  
  609. void multi_sort_kill_list()
  610. {
  611.         auto &Objects = LevelUniqueObjectState.Objects;
  612.         auto &vcobjptr = Objects.vcptr;
  613.         // Sort the kills list each time a new kill is added
  614.         std::array<int, MAX_PLAYERS> kills;
  615.         for (playernum_t i = 0; i < MAX_PLAYERS; ++i)
  616.         {
  617.                 auto &player_info = vcobjptr(vcplayerptr(i)->objnum)->ctype.player_info;
  618.                 if (Game_mode & GM_MULTI_COOP)
  619.                 {
  620.                         kills[i] = player_info.mission.score;
  621.                 }
  622. #if defined(DXX_BUILD_DESCENT_II)
  623.                 else
  624.                 if (Show_kill_list==2)
  625.                 {
  626.                         const auto kk = player_info.net_killed_total + player_info.net_kills_total;
  627.                         // always draw the ones without any ratio last
  628.                         kills[i] = kk <= 0
  629.                                 ? kk - 1
  630.                                 : static_cast<int>(
  631.                                         static_cast<float>(player_info.net_kills_total) / (
  632.                                                 static_cast<float>(player_info.net_killed_total) + static_cast<float>(player_info.net_kills_total)
  633.                                         ) * 100.0
  634.                                 );
  635.                 }
  636. #endif
  637.                 else
  638.                         kills[i] = player_info.net_kills_total;
  639.         }
  640.  
  641.         const auto predicate = [&](unsigned a, unsigned b) {
  642.                 return kills[a] > kills[b];
  643.         };
  644.         const auto &range = partial_range(sorted_kills, N_players);
  645.         std::sort(range.begin(), range.end(), predicate);
  646. }
  647.  
  648. static void print_kill_goal_tables(fvcobjptr &vcobjptr)
  649. {
  650.         const auto &local_player = get_local_player();
  651.         const auto pnum = Player_num;
  652.         con_printf(CON_NORMAL, "Kill goal statistics: player #%u \"%s\"", pnum, static_cast<const char *>(local_player.callsign));
  653.         range_for (auto &&e, enumerate(Players))
  654.         {
  655.                 const auto &i = e.value;
  656.                 if (!i.connected)
  657.                         continue;
  658.                 auto &plrobj = *vcobjptr(i.objnum);
  659.                 auto &player_info = plrobj.ctype.player_info;
  660.                 con_printf(CON_NORMAL, "\t#%" PRIuFAST32 " \"%s\"\tdeaths=%i\tkills=%i\tmatrix=%hu/%hu", e.idx, static_cast<const char *>(i.callsign), player_info.net_killed_total, player_info.net_kills_total, kill_matrix[pnum][e.idx], kill_matrix[e.idx][pnum]);
  661.         }
  662. }
  663.  
  664. static void net_destroy_controlcen(object_array &Objects)
  665. {
  666.         print_kill_goal_tables(Objects.vcptr);
  667.         HUD_init_message_literal(HM_MULTI, "The control center has been destroyed!");
  668.         net_destroy_controlcen(obj_find_first_of_type(Objects.vmptridx, OBJ_CNTRLCEN));
  669. }
  670.  
  671. }
  672.  
  673. static const char *prepare_kill_name(const playernum_t pnum, char (&buf)[(CALLSIGN_LEN*2)+4])
  674. {
  675.         if (Game_mode & GM_TEAM)
  676.         {
  677.                 snprintf(buf, sizeof(buf), "%s (%s)", static_cast<const char *>(vcplayerptr(pnum)->callsign), static_cast<const char *>(Netgame.team_name[get_team(pnum)]));
  678.                 return buf;
  679.         }
  680.         else
  681.                 return static_cast<const char *>(vcplayerptr(pnum)->callsign);
  682. }
  683.  
  684. namespace dsx {
  685.  
  686. static void multi_compute_kill(const imobjptridx_t killer, object &killed)
  687. {
  688. #if defined(DXX_BUILD_DESCENT_II)
  689.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  690. #endif
  691.         auto &Objects = LevelUniqueObjectState.Objects;
  692.         auto &vmobjptr = Objects.vmptr;
  693.         // Figure out the results of a network kills and add it to the
  694.         // appropriate player's tally.
  695.  
  696.         playernum_t killed_pnum, killer_pnum;
  697.  
  698.         // Both object numbers are localized already!
  699.  
  700.         const auto killed_type = killed.type;
  701.         if ((killed_type != OBJ_PLAYER) && (killed_type != OBJ_GHOST))
  702.         {
  703.                 Int3(); // compute_kill passed non-player object!
  704.                 return;
  705.         }
  706.  
  707.         killed_pnum = get_player_id(killed);
  708.  
  709.         Assert (killed_pnum < N_players);
  710.  
  711.         char killed_buf[(CALLSIGN_LEN*2)+4];
  712.         const char *killed_name = prepare_kill_name(killed_pnum, killed_buf);
  713.  
  714.         if (Newdemo_state == ND_STATE_RECORDING)
  715.                 newdemo_record_multi_death(killed_pnum);
  716.  
  717.         digi_play_sample( SOUND_HUD_KILL, F3_0 );
  718.  
  719. #if defined(DXX_BUILD_DESCENT_II)
  720.         if (LevelUniqueControlCenterState.Control_center_destroyed)
  721.                 vmplayerptr(killed_pnum)->connected = CONNECT_DIED_IN_MINE;
  722. #endif
  723.  
  724.         if (killer == object_none)
  725.                 return;
  726.         const auto killer_type = killer->type;
  727.         if (killer_type == OBJ_CNTRLCEN)
  728.         {
  729.                 if (Game_mode & GM_TEAM)
  730.                         -- team_kills[get_team(killed_pnum)];
  731.                 ++ killed.ctype.player_info.net_killed_total;
  732.                 -- killed.ctype.player_info.net_kills_total;
  733.                 -- killed.ctype.player_info.KillGoalCount;
  734.  
  735.                 if (Newdemo_state == ND_STATE_RECORDING)
  736.                         newdemo_record_multi_kill(killed_pnum, -1);
  737.  
  738.                 if (killed_pnum == Player_num)
  739.                 {
  740.                         HUD_init_message(HM_MULTI, "%s %s.", TXT_YOU_WERE, TXT_KILLED_BY_NONPLAY);
  741.                         multi_add_lifetime_killed ();
  742.                 }
  743.                 else
  744.                         HUD_init_message(HM_MULTI, "%s %s %s.", killed_name, TXT_WAS, TXT_KILLED_BY_NONPLAY );
  745.                 return;
  746.         }
  747.  
  748.         else if ((killer_type != OBJ_PLAYER) && (killer_type != OBJ_GHOST))
  749.         {
  750. #if defined(DXX_BUILD_DESCENT_II)
  751.                 if (killer_type == OBJ_WEAPON && get_weapon_id(killer) == weapon_id_type::PMINE_ID)
  752.                 {
  753.                         if (killed_pnum == Player_num)
  754.                                 HUD_init_message_literal(HM_MULTI, "You were killed by a mine!");
  755.                         else
  756.                                 HUD_init_message(HM_MULTI, "%s was killed by a mine!",killed_name);
  757.                 }
  758.                 else
  759. #endif
  760.                 {
  761.                         if (killed_pnum == Player_num)
  762.                         {
  763.                                 HUD_init_message(HM_MULTI, "%s %s.", TXT_YOU_WERE, TXT_KILLED_BY_ROBOT);
  764.                                 multi_add_lifetime_killed();
  765.                         }
  766.                         else
  767.                                 HUD_init_message(HM_MULTI, "%s %s %s.", killed_name, TXT_WAS, TXT_KILLED_BY_ROBOT );
  768.                 }
  769.                 ++ killed.ctype.player_info.net_killed_total;
  770.                 return;
  771.         }
  772.  
  773.         killer_pnum = get_player_id(killer);
  774.  
  775.         char killer_buf[(CALLSIGN_LEN*2)+4];
  776.         const char *killer_name = prepare_kill_name(killer_pnum, killer_buf);
  777.  
  778.         // Beyond this point, it was definitely a player-player kill situation
  779.  
  780.         if (killer_pnum >= N_players)
  781.                 Int3(); // See rob, tracking down bug with kill HUD messages
  782.         if (killed_pnum >= N_players)
  783.                 Int3(); // See rob, tracking down bug with kill HUD messages
  784.  
  785.         kill_matrix[killer_pnum][killed_pnum] += 1;
  786.         if (killer_pnum == killed_pnum)
  787.         {
  788.                 if (!game_mode_hoard())
  789.                 {
  790.                         if (Game_mode & GM_TEAM)
  791.                         {
  792.                                 team_kills[get_team(killed_pnum)] -= 1;
  793.                         }
  794.  
  795.                         ++ killed.ctype.player_info.net_killed_total;
  796.                         -- killed.ctype.player_info.net_kills_total;
  797.                         -- killed.ctype.player_info.KillGoalCount;
  798.  
  799.                         if (Newdemo_state == ND_STATE_RECORDING)
  800.                                 newdemo_record_multi_kill(killed_pnum, -1);
  801.                 }
  802.                 if (killer_pnum == Player_num)
  803.                 {
  804.                         HUD_init_message(HM_MULTI, "%s %s %s!", TXT_YOU, TXT_KILLED, TXT_YOURSELF );
  805.                         multi_add_lifetime_killed();
  806.                 }
  807.                 else
  808.                         HUD_init_message(HM_MULTI, "%s %s", killed_name, TXT_SUICIDE);
  809.  
  810.                 /* Bounty mode needs some lovin' */
  811.                 if( Game_mode & GM_BOUNTY && killed_pnum == Bounty_target && multi_i_am_master() )
  812.                 {
  813.                         /* Select a random number */
  814.                         unsigned n = d_rand() % MAX_PLAYERS;
  815.                        
  816.                         /* Make sure they're valid: Don't check against kill flags,
  817.                         * just in case everyone's dead! */
  818.                         while(!vcplayerptr(n)->connected)
  819.                                 n = d_rand() % MAX_PLAYERS;
  820.                        
  821.                         /* Select new target  - it will be sent later when we're done with this function */
  822.                         multi_new_bounty_target( n );
  823.                 }
  824.         }
  825.  
  826.         else
  827.         {
  828.                 short adjust = 1;
  829.                 /* Dead stores to prevent bogus -Wmaybe-uninitialized warnings
  830.                  * when the optimization level prevents the compiler from
  831.                  * recognizing that these are written in a team game and only
  832.                  * read in a team game.
  833.                  */
  834.                 unsigned killed_team = 0, killer_team = 0;
  835.                 DXX_MAKE_VAR_UNDEFINED(killed_team);
  836.                 DXX_MAKE_VAR_UNDEFINED(killer_team);
  837.                 const auto is_team_game = Game_mode & GM_TEAM;
  838.                 if (is_team_game)
  839.                 {
  840.                         killed_team = get_team(killed_pnum);
  841.                         killer_team = get_team(killer_pnum);
  842.                         if (killed_team == killer_team)
  843.                                 adjust = -1;
  844.                 }
  845.                 if (!game_mode_hoard())
  846.                 {
  847.                         if (is_team_game)
  848.                         {
  849.                                 team_kills[killer_team] += adjust;
  850.                                 killer->ctype.player_info.net_kills_total += adjust;
  851.                                 killer->ctype.player_info.KillGoalCount += adjust;
  852.                         }
  853.                         else if( Game_mode & GM_BOUNTY )
  854.                         {
  855.                                 /* Did the target die?  Did the target get a kill? */
  856.                                 if( killed_pnum == Bounty_target || killer_pnum == Bounty_target )
  857.                                 {
  858.                                         /* Increment kill counts */
  859.                                         ++ killer->ctype.player_info.net_kills_total;
  860.                                         ++ killer->ctype.player_info.KillGoalCount;
  861.                                        
  862.                                         /* If the target died, the new one is set! */
  863.                                         if( killed_pnum == Bounty_target )
  864.                                                 multi_new_bounty_target( killer_pnum );
  865.                                 }
  866.                         }
  867.                         else
  868.                         {
  869.                                 ++ killer->ctype.player_info.net_kills_total;
  870.                                 ++ killer->ctype.player_info.KillGoalCount;
  871.                         }
  872.                        
  873.                         if (Newdemo_state == ND_STATE_RECORDING)
  874.                                 newdemo_record_multi_kill(killer_pnum, 1);
  875.                 }
  876.  
  877.                 ++ killed.ctype.player_info.net_killed_total;
  878.                 const char *name0, *name1;
  879.                 if (killer_pnum == Player_num) {
  880.                         if (Game_mode & GM_MULTI_COOP)
  881.                         {
  882.                                 auto &player_info = get_local_plrobj().ctype.player_info;
  883.                                 const auto local_player_score = player_info.mission.score;
  884.                                 add_points_to_score(player_info, local_player_score >= 1000 ? -1000 : -local_player_score);
  885.                         }
  886.                         else
  887.                                 multi_add_lifetime_kills(adjust);
  888.                         name0 = TXT_YOU;
  889.                         name1 = killed_name;
  890.                 }
  891.                 else if (name0 = killer_name, killed_pnum == Player_num)
  892.                 {
  893.                         multi_add_lifetime_killed();
  894.                         name1 = TXT_YOU;
  895.                 }
  896.                 else
  897.                         name1 = killed_name;
  898.                 HUD_init_message(HM_MULTI, "%s %s %s!", name0, TXT_KILLED, name1);
  899.         }
  900.  
  901.         if (Netgame.KillGoal>0)
  902.         {
  903.                 const auto TheGoal = Netgame.KillGoal * 5;
  904.                 if (((Game_mode & GM_TEAM)
  905.                                 ? team_kills[get_team(killer_pnum)]
  906.                                 : killer->ctype.player_info.KillGoalCount
  907.                         ) >= TheGoal)
  908.                 {
  909.                         if (killer_pnum==Player_num)
  910.                         {
  911.                                 HUD_init_message_literal(HM_MULTI, "You reached the kill goal!");
  912.                                 get_local_plrobj().shields = i2f(200);
  913.                         }
  914.                         else
  915.                         {
  916.                                 char buf[(CALLSIGN_LEN*2)+4];
  917.                                 HUD_init_message(HM_MULTI, "%s has reached the kill goal!", prepare_kill_name(killer_pnum, buf));
  918.                         }
  919.                         net_destroy_controlcen(Objects);
  920.                 }
  921.         }
  922.  
  923.         multi_sort_kill_list();
  924.         multi_show_player_list();
  925. #if defined(DXX_BUILD_DESCENT_II)
  926.         // clear the killed guys flags/headlights
  927.         killed.ctype.player_info.powerup_flags &= ~(PLAYER_FLAGS_HEADLIGHT_ON);
  928. #endif
  929. }
  930.  
  931. }
  932.  
  933. void multi_do_protocol_frame(int force, int listen)
  934. {
  935.         switch (multi_protocol)
  936.         {
  937. #if DXX_USE_UDP
  938.                 case MULTI_PROTO_UDP:
  939.                         net_udp_do_frame(force, listen);
  940.                         break;
  941. #endif
  942.                 default:
  943.                         (void)force; (void)listen;
  944.                         Error("Protocol handling missing in multi_do_protocol_frame\n");
  945.                         break;
  946.         }
  947. }
  948.  
  949. window_event_result multi_do_frame()
  950. {
  951.         static d_time_fix lasttime;
  952.         static fix64 last_gmode_time = 0, last_inventory_time = 0, last_repo_time = 0;
  953.  
  954.         if (!(Game_mode & GM_MULTI) || Newdemo_state == ND_STATE_PLAYBACK)
  955.         {
  956.                 Int3();
  957.                 return window_event_result::ignored;
  958.         }
  959.  
  960.         if ((Game_mode & GM_NETWORK) && Netgame.PlayTimeAllowed.count() && lasttime != ThisLevelTime)
  961.         {
  962.                 for (unsigned i = 0; i < N_players; ++i)
  963.                         if (vcplayerptr(i)->connected)
  964.                         {
  965.                                 if (i==Player_num)
  966.                                 {
  967.                                         multi_send_heartbeat();
  968.                                         lasttime = ThisLevelTime;
  969.                                 }
  970.                                 break;
  971.                         }
  972.         }
  973.  
  974.         // Send update about our game mode-specific variables every 2 secs (to keep in sync since delayed kills can invalidate these infos on Clients)
  975.         if (multi_i_am_master() && timer_query() >= last_gmode_time + (F1_0*2))
  976.         {
  977.                 multi_send_gmode_update();
  978.                 last_gmode_time = timer_query();
  979.         }
  980.  
  981.         if (Network_status == NETSTAT_PLAYING)
  982.         {
  983.                 // Send out inventory three times per second
  984.                 if (timer_query() >= last_inventory_time + (F1_0/3))
  985.                 {
  986.                         multi_send_player_inventory(0);
  987.                         last_inventory_time = timer_query();
  988.                 }
  989.                 // Repopulate the level if necessary
  990.                 if (timer_query() >= last_repo_time + (F1_0/2))
  991.                 {
  992.                         MultiLevelInv_Repopulate((F1_0/2));
  993.                         last_repo_time = timer_query();
  994.                 }
  995.         }
  996.  
  997.         multi_send_message(); // Send any waiting messages
  998.  
  999.         if (Game_mode & GM_MULTI_ROBOTS)
  1000.         {
  1001.                 multi_check_robot_timeout();
  1002.         }
  1003.  
  1004.         multi_do_protocol_frame(0, 1);
  1005.  
  1006.         return multi_quit_game ? window_event_result::close : window_event_result::handled;
  1007. }
  1008.  
  1009. void _multi_send_data(const uint8_t *const buf, const unsigned len, const int priority)
  1010. {
  1011.         if (Game_mode & GM_NETWORK)
  1012.         {
  1013.                 switch (multi_protocol)
  1014.                 {
  1015. #if DXX_USE_UDP
  1016.                         case MULTI_PROTO_UDP:
  1017.                                 net_udp_send_data(buf, len, priority);
  1018.                                 break;
  1019. #endif
  1020.                         default:
  1021.                                 (void)buf; (void)len; (void)priority;
  1022.                                 Error("Protocol handling missing in multi_send_data\n");
  1023.                                 break;
  1024.                 }
  1025.         }
  1026. }
  1027.  
  1028. static void _multi_send_data_direct(const ubyte *buf, unsigned len, const playernum_t pnum, int priority)
  1029. {
  1030.         if (pnum >= MAX_PLAYERS)
  1031.                 Error("multi_send_data_direct: Illegal player num: %u\n", pnum);
  1032.  
  1033.         switch (multi_protocol)
  1034.         {
  1035. #if DXX_USE_UDP
  1036.                 case MULTI_PROTO_UDP:
  1037.                         net_udp_send_mdata_direct(buf, len, pnum, priority);
  1038.                         break;
  1039. #endif
  1040.                 default:
  1041.                         (void)buf; (void)len; (void)priority;
  1042.                         Error("Protocol handling missing in multi_send_data_direct\n");
  1043.                         break;
  1044.         }
  1045. }
  1046.  
  1047. template <multiplayer_command_t C>
  1048. static void multi_send_data_direct(uint8_t *const buf, const unsigned len, const playernum_t pnum, const int priority)
  1049. {
  1050.         buf[0] = C;
  1051.         unsigned expected = command_length<C>::value;
  1052. #ifdef DXX_CONSTANT_TRUE
  1053.         if (DXX_CONSTANT_TRUE(len != expected))
  1054.                 DXX_ALWAYS_ERROR_FUNCTION(dxx_trap_multi_send_data, "wrong packet size");
  1055. #endif
  1056.         if (len != expected)
  1057.         {
  1058.                 Error("multi_send_data_direct: Packet type %i length: %i, expected: %i\n", C, len, expected);
  1059.         }
  1060.         _multi_send_data_direct(buf, len, pnum, priority);
  1061. }
  1062.  
  1063. template <multiplayer_command_t C>
  1064. static inline void multi_send_data_direct(multi_command<C> &buf, const playernum_t pnum, const int priority)
  1065. {
  1066.         buf[0] = C;
  1067.         _multi_send_data_direct(buf.data(), buf.size(), pnum, priority);
  1068. }
  1069.  
  1070. namespace dsx {
  1071.  
  1072. void multi_leave_game()
  1073. {
  1074.         auto &Objects = LevelUniqueObjectState.Objects;
  1075.         auto &vmobjptridx = Objects.vmptridx;
  1076.  
  1077.         if (!(Game_mode & GM_MULTI))
  1078.                 return;
  1079.  
  1080.         if (Game_mode & GM_NETWORK)
  1081.         {
  1082.                 Net_create_loc = 0;
  1083.                 const auto cobjp = vmobjptridx(get_local_player().objnum);
  1084.                 multi_send_position(cobjp);
  1085.                 auto &player_info = cobjp->ctype.player_info;
  1086.                 if (!player_info.Player_eggs_dropped)
  1087.                 {
  1088.                         player_info.Player_eggs_dropped = true;
  1089.                         drop_player_eggs(cobjp);
  1090.                 }
  1091.                 multi_send_player_deres(deres_drop);
  1092.         }
  1093.  
  1094.         multi_send_quit();
  1095.  
  1096.         if (Game_mode & GM_NETWORK)
  1097.         {
  1098.                 switch (multi_protocol)
  1099.                 {
  1100. #if DXX_USE_UDP
  1101.                         case MULTI_PROTO_UDP:
  1102.                                 net_udp_leave_game();
  1103.                                 break;
  1104. #endif
  1105.                         default:
  1106.                                 Error("Protocol handling missing in multi_leave_game\n");
  1107.                                 break;
  1108.                 }
  1109.         }
  1110. #if defined(DXX_BUILD_DESCENT_I)
  1111.         plyr_save_stats();
  1112. #endif
  1113.  
  1114.         multi_quit_game = 0;    // quit complete
  1115. }
  1116.  
  1117. }
  1118.  
  1119. void multi_show_player_list()
  1120. {
  1121.         if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_COOP))
  1122.                 return;
  1123.  
  1124.         if (Show_kill_list)
  1125.                 return;
  1126.  
  1127.         Show_kill_list_timer = F1_0*5; // 5 second timer
  1128.         Show_kill_list = 1;
  1129. }
  1130.  
  1131. int multi_endlevel(int *const secret)
  1132. {
  1133.         int result = 0;
  1134.  
  1135.         switch (multi_protocol)
  1136.         {
  1137. #if DXX_USE_UDP
  1138.                 case MULTI_PROTO_UDP:
  1139.                         result = net_udp_endlevel(secret);
  1140.                         break;
  1141. #endif
  1142.                 default:
  1143.                         (void)secret;
  1144.                         Error("Protocol handling missing in multi_endlevel\n");
  1145.                         break;
  1146.         }
  1147.  
  1148.         return(result);
  1149. }
  1150.  
  1151. multi_endlevel_poll *get_multi_endlevel_poll2()
  1152. {
  1153.         switch (multi_protocol)
  1154.         {
  1155. #if DXX_USE_UDP
  1156.                 case MULTI_PROTO_UDP:
  1157.                         return net_udp_kmatrix_poll2;
  1158. #endif
  1159.                 default:
  1160.                         throw std::logic_error("Protocol handling missing in multi_endlevel_poll2");
  1161.         }
  1162. }
  1163.  
  1164. void multi_send_endlevel_packet()
  1165. {
  1166.         switch (multi_protocol)
  1167.         {
  1168. #if DXX_USE_UDP
  1169.                 case MULTI_PROTO_UDP:
  1170.                         net_udp_send_endlevel_packet();
  1171.                         break;
  1172. #endif
  1173.                 default:
  1174.                         Error("Protocol handling missing in multi_send_endlevel_packet\n");
  1175.                         break;
  1176.         }
  1177. }
  1178.  
  1179. //
  1180. // Part 2 : functions that act on network messages and change the
  1181. //          the state of the game in some way.
  1182. //
  1183.  
  1184. void multi_define_macro(const int key)
  1185. {
  1186.         if (!(Game_mode & GM_MULTI))
  1187.                 return;
  1188.  
  1189.         switch (key & ~KEY_SHIFTED)
  1190.         {
  1191.                 case KEY_F9:
  1192.                         multi_defining_message = 1; break;
  1193.                 case KEY_F10:
  1194.                         multi_defining_message = 2; break;
  1195.                 case KEY_F11:
  1196.                         multi_defining_message = 3; break;
  1197.                 case KEY_F12:
  1198.                         multi_defining_message = 4; break;
  1199.                 default:
  1200.                         Int3();
  1201.         }
  1202.  
  1203.         if (multi_defining_message)     {
  1204.                 key_toggle_repeat(1);
  1205.                 multi_message_index = 0;
  1206.                 Network_message[multi_message_index] = 0;
  1207.         }
  1208.  
  1209. }
  1210.  
  1211. static void multi_message_feedback(void)
  1212. {
  1213.         char *colon;
  1214.         int found = 0;
  1215.         char feedback_result[200];
  1216.  
  1217.         if (!(!(colon = strstr(Network_message.data(), ": ")) || colon == Network_message.data() || colon - Network_message.data() > CALLSIGN_LEN))
  1218.         {
  1219.                 std::size_t feedlen = snprintf(feedback_result, sizeof(feedback_result), "%s ", TXT_MESSAGE_SENT_TO);
  1220.                 if ((Game_mode & GM_TEAM) && (Network_message[0] == '1' || Network_message[0] == '2'))
  1221.                 {
  1222.                         snprintf(feedback_result + feedlen, sizeof(feedback_result) - feedlen, "%s '%s'", TXT_TEAM, static_cast<const char *>(Netgame.team_name[Network_message[0] - '1']));
  1223.                         found = 1;
  1224.                 }
  1225.                 if (Game_mode & GM_TEAM)
  1226.                 {
  1227.                         range_for (auto &i, Netgame.team_name)
  1228.                         {
  1229.                                 if (!d_strnicmp(i, Network_message.data(), colon - Network_message.data()))
  1230.                                 {
  1231.                                         const char *comma = found ? ", " : "";
  1232.                                         found++;
  1233.                                         const char *newline = (!(found % 4)) ? "\n" : "";
  1234.                                         size_t l = strlen(feedback_result);
  1235.                                         snprintf(feedback_result + l, sizeof(feedback_result) - l, "%s%s%s '%s'", comma, newline, TXT_TEAM, static_cast<const char *>(i));
  1236.                                 }
  1237.                         }
  1238.                 }
  1239.                 const player *const local_player = &get_local_player();
  1240.                 range_for (auto &i, partial_const_range(Players, N_players))
  1241.                 {
  1242.                         if (&i != local_player && i.connected && !d_strnicmp(static_cast<const char *>(i.callsign), Network_message.data(), colon - Network_message.data()))
  1243.                         {
  1244.                                 const char *comma = found ? ", " : "";
  1245.                                 found++;
  1246.                                 const char *newline = (!(found % 4)) ? "\n" : "";
  1247.                                 size_t l = strlen(feedback_result);
  1248.                                 snprintf(feedback_result + l, sizeof(feedback_result) - l, "%s%s%s", comma, newline, static_cast<const char *>(i.callsign));
  1249.                         }
  1250.                 }
  1251.                 if (!found)
  1252.                         strcat(feedback_result, TXT_NOBODY);
  1253.                 else
  1254.                         strcat(feedback_result, ".");
  1255.  
  1256.                 digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
  1257.  
  1258.                 Assert(strlen(feedback_result) < 200);
  1259.  
  1260.                 HUD_init_message_literal(HM_MULTI, feedback_result);
  1261.         }
  1262. }
  1263.  
  1264. void multi_send_macro(const int fkey)
  1265. {
  1266.         if (! (Game_mode & GM_MULTI) )
  1267.                 return;
  1268.  
  1269.         unsigned key;
  1270.         switch (fkey)
  1271.         {
  1272.                 case KEY_F9:
  1273.                         key = 0; break;
  1274.                 case KEY_F10:
  1275.                         key = 1; break;
  1276.                 case KEY_F11:
  1277.                         key = 2; break;
  1278.                 case KEY_F12:
  1279.                         key = 3; break;
  1280.                 default:
  1281.                         Int3();
  1282.                         return;
  1283.         }
  1284.  
  1285.         if (!PlayerCfg.NetworkMessageMacro[key][0])
  1286.         {
  1287.                 HUD_init_message_literal(HM_MULTI, TXT_NO_MACRO);
  1288.                 return;
  1289.         }
  1290.  
  1291.         Network_message = PlayerCfg.NetworkMessageMacro[key];
  1292.         Network_message_reciever = 100;
  1293.  
  1294.         HUD_init_message(HM_MULTI, "%s '%s'", TXT_SENDING, Network_message.data());
  1295.         multi_message_feedback();
  1296. }
  1297.  
  1298.  
  1299. void multi_send_message_start()
  1300. {
  1301.         if (Game_mode&GM_MULTI) {
  1302.                 multi_sending_message[Player_num] = msgsend_typing;
  1303.                 multi_send_msgsend_state(msgsend_typing);
  1304.                 multi_message_index = 0;
  1305.                 Network_message[multi_message_index] = 0;
  1306.                 key_toggle_repeat(1);
  1307.         }
  1308. }
  1309.  
  1310. namespace dsx {
  1311.  
  1312. static void kick_player(const player &plr, netplayer_info &nplr)
  1313. {
  1314.         switch (multi_protocol)
  1315.         {
  1316. #if DXX_USE_UDP
  1317.                 case MULTI_PROTO_UDP:
  1318.                         net_udp_dump_player(nplr.protocol.udp.addr, DUMP_KICKED);
  1319.                         break;
  1320. #endif
  1321.                 default:
  1322.                         (void)nplr;
  1323.                         Error("Protocol handling missing in multi_send_message_end\n");
  1324.                         break;
  1325.         }
  1326.  
  1327.         HUD_init_message(HM_MULTI, "Dumping %s...", static_cast<const char *>(plr.callsign));
  1328.         multi_message_index = 0;
  1329.         multi_sending_message[Player_num] = msgsend_none;
  1330. #if defined(DXX_BUILD_DESCENT_II)
  1331.         multi_send_msgsend_state(msgsend_none);
  1332. #endif
  1333. }
  1334.  
  1335. static void multi_send_message_end(fvmobjptr &vmobjptr)
  1336. {
  1337.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1338. #if defined(DXX_BUILD_DESCENT_I)
  1339.         multi_message_index = 0;
  1340.         multi_sending_message[Player_num] = msgsend_none;
  1341.         multi_send_msgsend_state(msgsend_none);
  1342.         key_toggle_repeat(0);
  1343. #elif defined(DXX_BUILD_DESCENT_II)
  1344.         Network_message_reciever = 100;
  1345. #endif
  1346.  
  1347.         if (!d_strnicmp(Network_message.data(), "/Handicap: "))
  1348.         {
  1349.                 constexpr auto minimum_allowed_shields = 10ul;
  1350.                 constexpr auto maximum_allowed_shields = 100ul;
  1351.                 auto &plr = get_local_player();
  1352.                 const char *const callsign = plr.callsign;
  1353.                 const auto requested_handicap = strtoul(&Network_message[11], 0, 10);
  1354.                 const auto clamped_handicap_max = std::max(minimum_allowed_shields, requested_handicap);
  1355.                 const auto clamped_handicap_min = std::min(maximum_allowed_shields, clamped_handicap_max);
  1356.                 StartingShields = i2f(clamped_handicap_min);
  1357.                 if (clamped_handicap_min != clamped_handicap_max)
  1358.                         snprintf(Network_message.data(), Network_message.size(), "%s has tried to cheat!", callsign);
  1359.                 else
  1360.                         snprintf(Network_message.data(), Network_message.size(), "%s handicap is now %lu", callsign, clamped_handicap_min);
  1361.                 HUD_init_message(HM_MULTI, "Telling others of your handicap of %lu!",clamped_handicap_min);
  1362.         }
  1363.         else if (!d_strnicmp(Network_message.data(), "/move: "))
  1364.         {
  1365.                 if ((Game_mode & GM_NETWORK) && (Game_mode & GM_TEAM))
  1366.                 {
  1367.                         unsigned name_index=7;
  1368.                         if (strlen(Network_message.data()) > 7)
  1369.                                 while (Network_message[name_index] == ' ')
  1370.                                         name_index++;
  1371.  
  1372.                         if (!multi_i_am_master())
  1373.                         {
  1374.                                 HUD_init_message(HM_MULTI, "Only %s can move players!",static_cast<const char *>(Players[multi_who_is_master()].callsign));
  1375.                                 return;
  1376.                         }
  1377.  
  1378.                         if (strlen(Network_message.data()) <= name_index)
  1379.                         {
  1380.                                 HUD_init_message_literal(HM_MULTI, "You must specify a name to move");
  1381.                                 return;
  1382.                         }
  1383.  
  1384.                         for (unsigned i = 0; i < N_players; ++i)
  1385.                                 if (vcplayerptr(i)->connected && !d_strnicmp(static_cast<const char *>(vcplayerptr(i)->callsign), &Network_message[name_index], strlen(Network_message.data()) - name_index))
  1386.                                 {
  1387. #if defined(DXX_BUILD_DESCENT_II)
  1388.                                         if (game_mode_capture_flag() && (vmobjptr(vcplayerptr(i)->objnum)->ctype.player_info.powerup_flags & PLAYER_FLAGS_FLAG))
  1389.                                         {
  1390.                                                 HUD_init_message_literal(HM_MULTI, "Can't move player because s/he has a flag!");
  1391.                                                 return;
  1392.                                         }
  1393. #endif
  1394.                                         if (Netgame.team_vector & (1<<i))
  1395.                                                 Netgame.team_vector&=(~(1<<i));
  1396.                                         else
  1397.                                                 Netgame.team_vector|=(1<<i);
  1398.  
  1399.                                         range_for (auto &t, partial_const_range(Players, N_players))
  1400.                                                 if (t.connected)
  1401.                                                         multi_reset_object_texture(vmobjptr(t.objnum));
  1402.                                         reset_cockpit();
  1403.  
  1404.                                         multi_send_gmode_update();
  1405.  
  1406.                                         snprintf(Network_message.data(), Network_message.size(), "%s has changed teams!", static_cast<const char *>(vcplayerptr(i)->callsign));
  1407.                                         if (i==Player_num)
  1408.                                         {
  1409.                                                 HUD_init_message_literal(HM_MULTI, "You have changed teams!");
  1410.                                                 reset_cockpit();
  1411.                                         }
  1412.                                         else
  1413.                                                 HUD_init_message(HM_MULTI, "Moving %s to other team.", static_cast<const char *>(vcplayerptr(i)->callsign));
  1414.                                         break;
  1415.                                 }
  1416.                 }
  1417.         }
  1418.  
  1419.         else if (!d_strnicmp(Network_message.data(), "/kick: ") && (Game_mode & GM_NETWORK))
  1420.         {
  1421.                 unsigned name_index=7;
  1422.                 if (strlen(Network_message.data()) > 7)
  1423.                         while (Network_message[name_index] == ' ')
  1424.                                 name_index++;
  1425.  
  1426.                 if (!multi_i_am_master())
  1427.                 {
  1428.                         HUD_init_message(HM_MULTI, "Only %s can kick others out!", static_cast<const char *>(Players[multi_who_is_master()].callsign));
  1429.                         multi_message_index = 0;
  1430.                         multi_sending_message[Player_num] = msgsend_none;
  1431. #if defined(DXX_BUILD_DESCENT_II)
  1432.                         multi_send_msgsend_state(msgsend_none);
  1433. #endif
  1434.                         return;
  1435.                 }
  1436.                 if (strlen(Network_message.data()) <= name_index)
  1437.                 {
  1438.                         HUD_init_message_literal(HM_MULTI, "You must specify a name to kick");
  1439.                         multi_message_index = 0;
  1440.                         multi_sending_message[Player_num] = msgsend_none;
  1441. #if defined(DXX_BUILD_DESCENT_II)
  1442.                         multi_send_msgsend_state(msgsend_none);
  1443. #endif
  1444.                         return;
  1445.                 }
  1446.  
  1447.                 if (Network_message[name_index] == '#' && isdigit(Network_message[name_index+1])) {
  1448.                         playernum_array_t players;
  1449.                         int listpos = Network_message[name_index+1] - '0';
  1450.  
  1451.                         if (Show_kill_list==1 || Show_kill_list==2) {
  1452.                                 if (listpos == 0 || listpos >= N_players) {
  1453.                                         HUD_init_message_literal(HM_MULTI, "Invalid player number for kick.");
  1454.                                         multi_message_index = 0;
  1455.                                         multi_sending_message[Player_num] = msgsend_none;
  1456. #if defined(DXX_BUILD_DESCENT_II)
  1457.                                         multi_send_msgsend_state(msgsend_none);
  1458. #endif
  1459.                                         return;
  1460.                                 }
  1461.                                 multi_get_kill_list(players);
  1462.                                 const auto i = players[listpos];
  1463.                                 if (i != Player_num && vcplayerptr(i)->connected)
  1464.                                 {
  1465.                                         kick_player(*vcplayerptr(i), Netgame.players[i]);
  1466.                                         return;
  1467.                                 }
  1468.                         }
  1469.                         else HUD_init_message_literal(HM_MULTI, "You cannot use # kicking with in team display.");
  1470.  
  1471.  
  1472.                     multi_message_index = 0;
  1473.                     multi_sending_message[Player_num] = msgsend_none;
  1474. #if defined(DXX_BUILD_DESCENT_II)
  1475.                     multi_send_msgsend_state(msgsend_none);
  1476. #endif
  1477.                         return;
  1478.                 }
  1479.  
  1480.                 for (unsigned i = 0; i < N_players; i++)
  1481.                         if (i != Player_num && vcplayerptr(i)->connected && !d_strnicmp(static_cast<const char *>(vcplayerptr(i)->callsign), &Network_message[name_index], strlen(Network_message.data()) - name_index))
  1482.                         {
  1483.                                 kick_player(*vcplayerptr(i), Netgame.players[i]);
  1484.                                 return;
  1485.                         }
  1486.         }
  1487.         else if (!d_stricmp (Network_message.data(), "/killreactor") && (Game_mode & GM_NETWORK) && !LevelUniqueControlCenterState.Control_center_destroyed)
  1488.         {
  1489.                 if (!multi_i_am_master())
  1490.                         HUD_init_message(HM_MULTI, "Only %s can kill the reactor this way!", static_cast<const char *>(Players[multi_who_is_master()].callsign));
  1491.                 else
  1492.                 {
  1493.                         net_destroy_controlcen(object_none);
  1494.                         multi_send_destroy_controlcen(object_none,Player_num);
  1495.                 }
  1496.                 multi_message_index = 0;
  1497.                 multi_sending_message[Player_num] = msgsend_none;
  1498. #if defined(DXX_BUILD_DESCENT_II)
  1499.                 multi_send_msgsend_state(msgsend_none);
  1500. #endif
  1501.                 return;
  1502.         }
  1503.  
  1504. #if defined(DXX_BUILD_DESCENT_II)
  1505.         else
  1506. #endif
  1507.                 HUD_init_message(HM_MULTI, "%s '%s'", TXT_SENDING, Network_message.data());
  1508.  
  1509.         multi_send_message();
  1510.         multi_message_feedback();
  1511.  
  1512. #if defined(DXX_BUILD_DESCENT_I)
  1513.         Network_message_reciever = 100;
  1514. #elif defined(DXX_BUILD_DESCENT_II)
  1515.         multi_message_index = 0;
  1516.         multi_sending_message[Player_num] = msgsend_none;
  1517.         multi_send_msgsend_state(msgsend_none);
  1518.         key_toggle_repeat(0);
  1519. #endif
  1520.         game_flush_inputs();
  1521. }
  1522.  
  1523. }
  1524.  
  1525. static void multi_define_macro_end()
  1526. {
  1527.         Assert( multi_defining_message > 0 );
  1528.  
  1529.         PlayerCfg.NetworkMessageMacro[multi_defining_message-1] = Network_message;
  1530.         write_player_file();
  1531.  
  1532.         multi_message_index = 0;
  1533.         multi_defining_message = 0;
  1534.         key_toggle_repeat(0);
  1535.         game_flush_inputs();
  1536. }
  1537.  
  1538. window_event_result multi_message_input_sub(int key)
  1539. {
  1540.         auto &Objects = LevelUniqueObjectState.Objects;
  1541.         auto &vmobjptr = Objects.vmptr;
  1542.         switch( key )
  1543.         {
  1544.                 case KEY_F8:
  1545.                 case KEY_ESC:
  1546.                         multi_sending_message[Player_num] = msgsend_none;
  1547.                         multi_send_msgsend_state(msgsend_none);
  1548.                         multi_defining_message = 0;
  1549.                         key_toggle_repeat(0);
  1550.                         game_flush_inputs();
  1551.                         return window_event_result::handled;
  1552.                 case KEY_LEFT:
  1553.                 case KEY_BACKSP:
  1554.                 case KEY_PAD4:
  1555.                         if (multi_message_index > 0)
  1556.                                 multi_message_index--;
  1557.                         Network_message[multi_message_index] = 0;
  1558.                         return window_event_result::handled;
  1559.                 case KEY_ENTER:
  1560.                         if ( multi_sending_message[Player_num] )
  1561.                                 multi_send_message_end(vmobjptr);
  1562.                         else if ( multi_defining_message )
  1563.                                 multi_define_macro_end();
  1564.                         game_flush_inputs();
  1565.                         return window_event_result::handled;
  1566.                 default:
  1567.                 {
  1568.                         int ascii = key_ascii();
  1569.                         if ( ascii < 255 )     {
  1570.                                 if (multi_message_index < MAX_MESSAGE_LEN-2 )   {
  1571.                                         Network_message[multi_message_index++] = ascii;
  1572.                                         Network_message[multi_message_index] = 0;
  1573.                                 } else if ( multi_sending_message[Player_num] )     {
  1574.                                         int i;
  1575.                                         char * ptext;
  1576.                                         ptext = NULL;
  1577.                                         Network_message[multi_message_index++] = ascii;
  1578.                                         Network_message[multi_message_index] = 0;
  1579.                                         for (i=multi_message_index-1; i>=0; i-- )       {
  1580.                                                 if ( Network_message[i]==32 )   {
  1581.                                                         ptext = &Network_message[i+1];
  1582.                                                         Network_message[i] = 0;
  1583.                                                         break;
  1584.                                                 }
  1585.                                         }
  1586.                                         multi_send_message_end(vmobjptr);
  1587.                                         if ( ptext )    {
  1588.                                                 multi_sending_message[Player_num] = msgsend_typing;
  1589.                                                 multi_send_msgsend_state(msgsend_typing);
  1590.                                                 auto pcolon = strstr(Network_message.data(), ": " );
  1591.                                                 if ( pcolon )
  1592.                                                         strcpy( pcolon+1, ptext );
  1593.                                                 else
  1594.                                                         strcpy(Network_message.data(), ptext);
  1595.                                                 multi_message_index = strlen( Network_message );
  1596.                                         }
  1597.                                 }
  1598.                         }
  1599.                 }
  1600.         }
  1601.         return window_event_result::ignored;
  1602. }
  1603.  
  1604. void multi_do_death(int)
  1605. {
  1606.         auto &Objects = LevelUniqueObjectState.Objects;
  1607.         auto &vmobjptr = Objects.vmptr;
  1608.         // Do any miscellaneous stuff for a new network player after death
  1609.         if (!(Game_mode & GM_MULTI_COOP))
  1610.         {
  1611.                 auto &player_info = get_local_plrobj().ctype.player_info;
  1612.                 player_info.powerup_flags |= (PLAYER_FLAGS_RED_KEY | PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_GOLD_KEY);
  1613.         }
  1614. }
  1615.  
  1616. namespace dsx {
  1617.  
  1618. static void multi_do_fire(fvmobjptridx &vmobjptridx, const playernum_t pnum, const uint8_t *const buf)
  1619. {
  1620.         ubyte weapon;
  1621.         sbyte flags;
  1622.         vms_vector shot_orientation;
  1623.  
  1624.         // Act out the actual shooting
  1625.         weapon = static_cast<int>(buf[2]);
  1626.  
  1627.         flags = buf[4];
  1628.         icobjidx_t Network_laser_track = object_none;
  1629.         if (buf[0] == MULTI_FIRE_TRACK)
  1630.         {
  1631.                 Network_laser_track = GET_INTEL_SHORT(buf + 18);
  1632.                 Network_laser_track = objnum_remote_to_local(Network_laser_track, buf[20]);
  1633.         }
  1634.  
  1635.         shot_orientation.x = static_cast<fix>(GET_INTEL_INT(buf + 6));
  1636.         shot_orientation.y = static_cast<fix>(GET_INTEL_INT(buf + 10));
  1637.         shot_orientation.z = static_cast<fix>(GET_INTEL_INT(buf + 14));
  1638.  
  1639.         Assert (pnum < N_players);
  1640.  
  1641.         const auto &&obj = vmobjptridx(vcplayerptr(pnum)->objnum);
  1642.         if (obj->type == OBJ_GHOST)
  1643.                 multi_make_ghost_player(pnum);
  1644.  
  1645.         if (weapon == FLARE_ADJUST)
  1646.                 Laser_player_fire(obj, weapon_id_type::FLARE_ID, 6, 1, shot_orientation, object_none);
  1647.         else
  1648.         if (weapon >= MISSILE_ADJUST) {
  1649.                 int weapon_gun,remote_objnum;
  1650.                 const auto weapon_id = Secondary_weapon_to_weapon_info[weapon-MISSILE_ADJUST];
  1651.                 weapon_gun = Secondary_weapon_to_gun_num[weapon-MISSILE_ADJUST] + (flags & 1);
  1652.  
  1653. #if defined(DXX_BUILD_DESCENT_II)
  1654.                 if (weapon-MISSILE_ADJUST==GUIDED_INDEX)
  1655.                 {
  1656.                         Multi_is_guided=1;
  1657.                 }
  1658. #endif
  1659.  
  1660.                 const auto &&objnum = Laser_player_fire(obj, weapon_id, weapon_gun, 1, shot_orientation, Network_laser_track);
  1661.                 if (buf[0] == MULTI_FIRE_BOMB)
  1662.                 {
  1663.                         remote_objnum = GET_INTEL_SHORT(buf + 18);
  1664.                         map_objnum_local_to_remote(objnum, remote_objnum, pnum);
  1665.                 }
  1666.         }
  1667.         else {
  1668.                 if (weapon == primary_weapon_index_t::FUSION_INDEX) {
  1669.                         obj->ctype.player_info.Fusion_charge = flags << 12;
  1670.                 }
  1671.                 if (weapon == weapon_id_type::LASER_ID) {
  1672.                         auto &powerup_flags = obj->ctype.player_info.powerup_flags;
  1673.                         if (flags & LASER_QUAD)
  1674.                                 powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
  1675.                         else
  1676.                                 powerup_flags &= ~PLAYER_FLAGS_QUAD_LASERS;
  1677.                 }
  1678.  
  1679.                 do_laser_firing(obj, weapon, static_cast<int>(buf[3]), flags, static_cast<int>(buf[5]), shot_orientation, Network_laser_track);
  1680.         }
  1681. }
  1682.  
  1683. }
  1684.  
  1685. static void multi_do_message(const uint8_t *const cbuf)
  1686. {
  1687.         const auto buf = reinterpret_cast<const char *>(cbuf);
  1688.         const char *colon;
  1689.         const char *msgstart;
  1690.         int loc = 2;
  1691.  
  1692.         if (((colon = strstr(buf+loc, ": ")) == NULL) || (colon-(buf+loc) < 1) || (colon-(buf+loc) > CALLSIGN_LEN))
  1693.         {
  1694.                 msgstart = buf + 2;
  1695.         }
  1696.         else
  1697.         {
  1698.                 if ( (!d_strnicmp(static_cast<const char *>(get_local_player().callsign), buf+loc, colon-(buf+loc))) ||
  1699.                          ((Game_mode & GM_TEAM) && ( (get_team(Player_num) == atoi(buf+loc)-1) || !d_strnicmp(Netgame.team_name[get_team(Player_num)], buf+loc, colon-(buf+loc)))) )
  1700.                 {
  1701.                         msgstart = colon + 2;
  1702.                 }
  1703.                 else
  1704.                         return;
  1705.         }
  1706.         const playernum_t pnum = buf[1];
  1707.         const auto color = get_player_or_team_color(pnum);
  1708.         char xrgb = BM_XRGB(player_rgb[color].r,player_rgb[color].g,player_rgb[color].b);
  1709.         digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
  1710.         HUD_init_message(HM_MULTI, "%c%c%s:%c%c %s", CC_COLOR, xrgb, static_cast<const char *>(vcplayerptr(pnum)->callsign), CC_COLOR, BM_XRGB(0, 31, 0), msgstart);
  1711.         multi_sending_message[pnum] = msgsend_none;
  1712. }
  1713.  
  1714. namespace dsx {
  1715.  
  1716. static void multi_do_position(fvmobjptridx &vmobjptridx, const playernum_t pnum, const uint8_t *const buf)
  1717. {
  1718.         const auto &&obj = vmobjptridx(vcplayerptr(pnum)->objnum);
  1719.         int count = 1;
  1720.  
  1721.         quaternionpos qpp{};
  1722.         qpp.orient.w = GET_INTEL_SHORT(&buf[count]);                                    count += 2;
  1723.         qpp.orient.x = GET_INTEL_SHORT(&buf[count]);                                    count += 2;
  1724.         qpp.orient.y = GET_INTEL_SHORT(&buf[count]);                                    count += 2;
  1725.         qpp.orient.z = GET_INTEL_SHORT(&buf[count]);                                    count += 2;
  1726.         qpp.pos.x = GET_INTEL_INT(&buf[count]);                                         count += 4;
  1727.         qpp.pos.y = GET_INTEL_INT(&buf[count]);                                         count += 4;
  1728.         qpp.pos.z = GET_INTEL_INT(&buf[count]);                                         count += 4;
  1729.         qpp.segment = GET_INTEL_SHORT(&buf[count]);                                     count += 2;
  1730.         qpp.vel.x = GET_INTEL_INT(&buf[count]);                                         count += 4;
  1731.         qpp.vel.y = GET_INTEL_INT(&buf[count]);                                         count += 4;
  1732.         qpp.vel.z = GET_INTEL_INT(&buf[count]);                                         count += 4;
  1733.         qpp.rotvel.x = GET_INTEL_INT(&buf[count]);                                      count += 4;
  1734.         qpp.rotvel.y = GET_INTEL_INT(&buf[count]);                                      count += 4;
  1735.         qpp.rotvel.z = GET_INTEL_INT(&buf[count]);                                      count += 4;
  1736.         extract_quaternionpos(obj, qpp);
  1737.  
  1738.         if (obj->movement_type == MT_PHYSICS)
  1739.                 set_thrust_from_velocity(obj);
  1740. }
  1741.  
  1742. static void multi_do_reappear(const playernum_t pnum, const ubyte *buf)
  1743. {
  1744.         auto &Objects = LevelUniqueObjectState.Objects;
  1745.         auto &vcobjptr = Objects.vcptr;
  1746.         const objnum_t objnum = GET_INTEL_SHORT(&buf[2]);
  1747.  
  1748.         const auto &&uobj = vcobjptr.check_untrusted(objnum);
  1749.         if (!uobj)
  1750.                 return;
  1751.         auto &obj = **uobj;
  1752.         if (obj.type != OBJ_PLAYER && obj.type != OBJ_GHOST)
  1753.         {
  1754.                 con_printf(CON_URGENT, "%s:%u: BUG: object %hu has type %u, expected %u or %u", __FILE__, __LINE__, objnum, obj.type, OBJ_PLAYER, OBJ_GHOST);
  1755.                 return;
  1756.         }
  1757.         /* Override macro, call only the getter.
  1758.          *
  1759.          * This message is overloaded to be used on both players and ghosts,
  1760.          * so the standard check cannot be used.  Instead, the correct check
  1761.          * is open-coded above.
  1762.          */
  1763.         if (pnum != (get_player_id)(obj))
  1764.                 return;
  1765.  
  1766.         multi_make_ghost_player(pnum);
  1767.         create_player_appearance_effect(Vclip, obj);
  1768. }
  1769.  
  1770. static void multi_do_player_deres(object_array &Objects, const playernum_t pnum, const uint8_t *const buf)
  1771. {
  1772.         auto &vmobjptridx = Objects.vmptridx;
  1773.         auto &vmobjptr = Objects.vmptr;
  1774.         // Only call this for players, not robots.  pnum is player number, not
  1775.         // Object number.
  1776.  
  1777.         int count;
  1778.         char remote_created;
  1779.  
  1780. #ifdef NDEBUG
  1781.         if (pnum >= N_players)
  1782.                 return;
  1783. #else
  1784.         Assert(pnum < N_players);
  1785. #endif
  1786.  
  1787.         // If we are in the process of sending objects to a new player, reset that process
  1788.         if (Network_send_objects)
  1789.         {
  1790.                 Network_send_objnum = -1;
  1791.         }
  1792.  
  1793.         // Stuff the Players structure to prepare for the explosion
  1794.  
  1795.         count = 3;
  1796. #if defined(DXX_BUILD_DESCENT_I)
  1797. #define GET_WEAPON_FLAGS(buf,count)     buf[count++]
  1798. #elif defined(DXX_BUILD_DESCENT_II)
  1799. #define GET_WEAPON_FLAGS(buf,count)     (count += sizeof(uint16_t), GET_INTEL_SHORT(buf + (count - sizeof(uint16_t))))
  1800. #endif
  1801.         const auto &&objp = vmobjptridx(vcplayerptr(pnum)->objnum);
  1802.         auto &player_info = objp->ctype.player_info;
  1803.         player_info.primary_weapon_flags = GET_WEAPON_FLAGS(buf, count);
  1804.         player_info.laser_level = stored_laser_level(buf[count]);                           count++;
  1805.  
  1806.         auto &secondary_ammo = player_info.secondary_ammo;
  1807.         secondary_ammo[HOMING_INDEX] = buf[count];                count++;
  1808.         secondary_ammo[CONCUSSION_INDEX] = buf[count];count++;
  1809.         secondary_ammo[SMART_INDEX] = buf[count];         count++;
  1810.         secondary_ammo[MEGA_INDEX] = buf[count];          count++;
  1811.         secondary_ammo[PROXIMITY_INDEX] = buf[count]; count++;
  1812.  
  1813. #if defined(DXX_BUILD_DESCENT_II)
  1814.         secondary_ammo[SMISSILE1_INDEX] = buf[count]; count++;
  1815.         secondary_ammo[GUIDED_INDEX]    = buf[count]; count++;
  1816.         secondary_ammo[SMART_MINE_INDEX]= buf[count]; count++;
  1817.         secondary_ammo[SMISSILE4_INDEX] = buf[count]; count++;
  1818.         secondary_ammo[SMISSILE5_INDEX] = buf[count]; count++;
  1819. #endif
  1820.  
  1821.         player_info.vulcan_ammo = GET_INTEL_SHORT(buf + count); count += 2;
  1822.         player_info.powerup_flags = player_flags(GET_INTEL_INT(buf + count));    count += 4;
  1823.  
  1824.         //      objp->phys_info.velocity = *reinterpret_cast<vms_vector *>(buf+16); // 12 bytes
  1825.         //      objp->pos = *reinterpret_cast<vms_vector *>(buf+28);                // 12 bytes
  1826.  
  1827.         remote_created = buf[count++]; // How many did the other guy create?
  1828.  
  1829.         Net_create_loc = 0;
  1830.         drop_player_eggs(objp);
  1831.  
  1832.         // Create mapping from remote to local numbering system
  1833.  
  1834.         // We now handle this situation gracefully, Int3 not required
  1835.         //      if (Net_create_loc != remote_created)
  1836.         //              Int3(); // Probably out of object array space, see Rob
  1837.  
  1838.         range_for (const auto i, partial_const_range(Net_create_objnums, std::min(Net_create_loc, static_cast<unsigned>(remote_created))))
  1839.         {
  1840.                 short s;
  1841.  
  1842.                 s = GET_INTEL_SHORT(buf + count);
  1843.  
  1844.                 if ((s > 0) && (i > 0))
  1845.                         map_objnum_local_to_remote(static_cast<short>(i), s, pnum);
  1846.                 count += 2;
  1847.         }
  1848.         range_for (const auto i, partial_const_range(Net_create_objnums, remote_created, Net_create_loc))
  1849.         {
  1850.                 vmobjptr(i)->flags |= OF_SHOULD_BE_DEAD;
  1851.         }
  1852.  
  1853.         if (buf[2] == deres_explode)
  1854.         {
  1855.                 explode_badass_player(objp);
  1856.  
  1857.                 objp->flags &= ~OF_SHOULD_BE_DEAD;              //don't really kill player
  1858.                 multi_make_player_ghost(pnum);
  1859.         }
  1860.         else
  1861.         {
  1862.                 create_player_appearance_effect(Vclip, objp);
  1863.         }
  1864.  
  1865.         player_info.powerup_flags &= ~(PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE);
  1866. #if defined(DXX_BUILD_DESCENT_II)
  1867.         player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG;
  1868. #endif
  1869.         DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
  1870. }
  1871.  
  1872. }
  1873.  
  1874. /*
  1875.  * Process can compute a kill. If I am a Client this might be my own one (see multi_send_kill()) but with more specific data so I can compute my kill correctly.
  1876.  */
  1877. static void multi_do_kill(object_array &Objects, const uint8_t *const buf)
  1878. {
  1879.         int count = 1;
  1880.         int type = static_cast<int>(buf[0]);
  1881.  
  1882.         if (multi_i_am_master() && type != MULTI_KILL_CLIENT)
  1883.                 return;
  1884.         if (!multi_i_am_master() && type != MULTI_KILL_HOST)
  1885.                 return;
  1886.  
  1887.         const playernum_t pnum = buf[1];
  1888.         if (pnum >= N_players)
  1889.         {
  1890.                 Int3(); // Invalid player number killed
  1891.                 return;
  1892.         }
  1893.  
  1894.         // I am host, I know what's going on so take this packet, add game_mode related info which might be necessary for kill computation and send it to everyone so they can compute their kills correctly
  1895.         if (multi_i_am_master())
  1896.         {
  1897.                 multi_command<MULTI_KILL_HOST> multibuf;
  1898.                 std::memcpy(multibuf.data(), buf, 5);
  1899.                 multibuf[5] = Netgame.team_vector;
  1900.                 multibuf[6] = Bounty_target;
  1901.                
  1902.                 multi_send_data(multibuf, 2);
  1903.         }
  1904.  
  1905.         const auto killed = vcplayerptr(pnum)->objnum;
  1906.         count += 1;
  1907.         objnum_t killer;
  1908.         killer = GET_INTEL_SHORT(buf + count);
  1909.         if (killer > 0)
  1910.                 killer = objnum_remote_to_local(killer, buf[count+2]);
  1911.         if (!multi_i_am_master())
  1912.         {
  1913.                 Netgame.team_vector = buf[5];
  1914.                 Bounty_target = buf[6];
  1915.         }
  1916.  
  1917.         multi_compute_kill(Objects.imptridx(killer), Objects.vmptridx(killed));
  1918.  
  1919.         if (Game_mode & GM_BOUNTY && multi_i_am_master()) // update in case if needed... we could attach this to this packet but... meh...
  1920.                 multi_send_bounty();
  1921. }
  1922.  
  1923. namespace dsx {
  1924.  
  1925. //      Changed by MK on 10/20/94 to send NULL as object to net_destroy_controlcen if it got -1
  1926. // which means not a controlcen object, but contained in another object
  1927. static void multi_do_controlcen_destroy(fimobjptridx &imobjptridx, const uint8_t *const buf)
  1928. {
  1929.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1930.         objnum_t objnum = GET_INTEL_SHORT(buf + 1);
  1931.         const playernum_t who = buf[3];
  1932.  
  1933.         if (LevelUniqueControlCenterState.Control_center_destroyed != 1)
  1934.         {
  1935.                 if ((who < N_players) && (who != Player_num)) {
  1936.                         HUD_init_message(HM_MULTI, "%s %s", static_cast<const char *>(vcplayerptr(who)->callsign), TXT_HAS_DEST_CONTROL);
  1937.                 }
  1938.                 else
  1939.                         HUD_init_message_literal(HM_MULTI, who == Player_num ? TXT_YOU_DEST_CONTROL : TXT_CONTROL_DESTROYED);
  1940.  
  1941.                 net_destroy_controlcen(objnum == object_none ? object_none : imobjptridx(objnum));
  1942.         }
  1943. }
  1944.  
  1945. static void multi_do_escape(fvmobjptridx &vmobjptridx, const uint8_t *const buf)
  1946. {
  1947.         digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
  1948.         const playernum_t pnum = buf[1];
  1949.         auto &plr = *vmplayerptr(pnum);
  1950.         const auto &&objnum = vmobjptridx(plr.objnum);
  1951. #if defined(DXX_BUILD_DESCENT_II)
  1952.         digi_kill_sound_linked_to_object (objnum);
  1953. #endif
  1954.  
  1955.         const char *txt;
  1956.         int connected;
  1957. #if defined(DXX_BUILD_DESCENT_I)
  1958.         if (buf[2] == static_cast<uint8_t>(multi_endlevel_type::secret))
  1959.         {
  1960.                 txt = TXT_HAS_FOUND_SECRET;
  1961.                 connected = CONNECT_FOUND_SECRET;
  1962.         }
  1963.         else
  1964. #endif
  1965.         {
  1966.                 txt = TXT_HAS_ESCAPED;
  1967.                 connected = CONNECT_ESCAPE_TUNNEL;
  1968.         }
  1969.         HUD_init_message(HM_MULTI, "%s %s", static_cast<const char *>(plr.callsign), txt);
  1970.         if (Game_mode & GM_NETWORK)
  1971.                 plr.connected = connected;
  1972.         create_player_appearance_effect(Vclip, objnum);
  1973.         multi_make_player_ghost(buf[1]);
  1974. }
  1975.  
  1976. static void multi_do_remobj(fvmobjptr &vmobjptr, const uint8_t *const buf)
  1977. {
  1978.         short objnum; // which object to remove
  1979.  
  1980.         objnum = GET_INTEL_SHORT(buf + 1);
  1981.         // which remote list is it entered in
  1982.         auto obj_owner = buf[3];
  1983.  
  1984.         Assert(objnum >= 0);
  1985.  
  1986.         if (objnum < 1)
  1987.                 return;
  1988.  
  1989.         auto local_objnum = objnum_remote_to_local(objnum, obj_owner); // translate to local objnum
  1990.  
  1991.         if (local_objnum == object_none)
  1992.         {
  1993.                 return;
  1994.         }
  1995.  
  1996.         auto &obj = *vmobjptr(local_objnum);
  1997.         if (obj.type != OBJ_POWERUP && obj.type != OBJ_HOSTAGE)
  1998.         {
  1999.                 return;
  2000.         }
  2001.  
  2002.         if (Network_send_objects && multi_objnum_is_past(local_objnum))
  2003.         {
  2004.                 Network_send_objnum = -1;
  2005.         }
  2006.  
  2007.         obj.flags |= OF_SHOULD_BE_DEAD; // quick and painless
  2008. }
  2009.  
  2010. }
  2011.  
  2012. void multi_disconnect_player(const playernum_t pnum)
  2013. {
  2014.         if (!(Game_mode & GM_NETWORK))
  2015.                 return;
  2016.         if (vcplayerptr(pnum)->connected == CONNECT_DISCONNECTED)
  2017.                 return;
  2018.  
  2019.         if (vcplayerptr(pnum)->connected == CONNECT_PLAYING)
  2020.         {
  2021.                 digi_play_sample( SOUND_HUD_MESSAGE, F1_0 );
  2022.                 HUD_init_message(HM_MULTI,  "%s %s", static_cast<const char *>(vcplayerptr(pnum)->callsign), TXT_HAS_LEFT_THE_GAME);
  2023.  
  2024.                 multi_sending_message[pnum] = msgsend_none;
  2025.  
  2026.                 if (Network_status == NETSTAT_PLAYING)
  2027.                 {
  2028.                         multi_make_player_ghost(pnum);
  2029.                         multi_strip_robots(pnum);
  2030.                 }
  2031.  
  2032.                 if (Newdemo_state == ND_STATE_RECORDING)
  2033.                         newdemo_record_multi_disconnect(pnum);
  2034.  
  2035.                 // Bounty target left - select a new one
  2036.                 if( Game_mode & GM_BOUNTY && pnum == Bounty_target && multi_i_am_master() )
  2037.                 {
  2038.                         /* Select a random number */
  2039.                         vmplayeridx_t n = d_rand() % MAX_PLAYERS;
  2040.                        
  2041.                         /* Make sure they're valid: Don't check against kill flags,
  2042.                                 * just in case everyone's dead! */
  2043.                         while(!vcplayerptr(n)->connected)
  2044.                                 n = d_rand() % MAX_PLAYERS;
  2045.                        
  2046.                         /* Select new target */
  2047.                         multi_new_bounty_target( n );
  2048.                        
  2049.                         /* Send this new data */
  2050.                         multi_send_bounty();
  2051.                 }
  2052.         }
  2053.  
  2054.         vmplayerptr(pnum)->connected = CONNECT_DISCONNECTED;
  2055.         Netgame.players[pnum].connected = CONNECT_DISCONNECTED;
  2056.  
  2057.         switch (multi_protocol)
  2058.         {
  2059. #if DXX_USE_UDP
  2060.                 case MULTI_PROTO_UDP:
  2061.                         net_udp_disconnect_player(pnum);
  2062.                         break;
  2063. #endif
  2064.                 default:
  2065.                         Error("Protocol handling missing in multi_disconnect_player\n");
  2066.                         break;
  2067.         }
  2068.  
  2069.         if (pnum == multi_who_is_master()) // Host has left - Quit game!
  2070.         {
  2071.                 if (Game_wind)
  2072.                         window_set_visible(Game_wind, 0);
  2073.                 nm_messagebox(NULL, 1, TXT_OK, "Host left the game!");
  2074.                 if (Game_wind)
  2075.                         window_set_visible(Game_wind, 1);
  2076.                 multi_quit_game = 1;
  2077.                 game_leave_menus();
  2078.                 return;
  2079.         }
  2080.  
  2081.         int n = 0;
  2082.         range_for (auto &i, partial_const_range(Players, N_players))
  2083.                 if (i.connected)
  2084.                         if (++n > 1)
  2085.                                 break;
  2086.         if (n == 1)
  2087.         {
  2088.                 HUD_init_message_literal(HM_MULTI, "You are the only person remaining in this netgame");
  2089.         }
  2090. }
  2091.  
  2092. static void multi_do_quit(const uint8_t *const buf)
  2093. {
  2094.         if (!(Game_mode & GM_NETWORK))
  2095.                 return;
  2096.         multi_disconnect_player(static_cast<int>(buf[1]));
  2097. }
  2098.  
  2099. namespace dsx {
  2100.  
  2101. static void multi_do_cloak(fvmobjptr &vmobjptr, const playernum_t pnum)
  2102. {
  2103.         Assert(pnum < N_players);
  2104.  
  2105.         const auto &&objp = vmobjptr(vcplayerptr(pnum)->objnum);
  2106.         auto &player_info = objp->ctype.player_info;
  2107.         player_info.powerup_flags |= PLAYER_FLAGS_CLOAKED;
  2108.         player_info.cloak_time = GameTime64;
  2109.         ai_do_cloak_stuff();
  2110.  
  2111.         multi_strip_robots(pnum);
  2112.  
  2113.         if (Newdemo_state == ND_STATE_RECORDING)
  2114.                 newdemo_record_multi_cloak(pnum);
  2115. }
  2116.  
  2117. }
  2118.  
  2119. namespace dcx {
  2120.  
  2121. static const char *deny_multi_save_game_duplicate_callsign(const partial_range_t<const player *> range)
  2122. {
  2123.         const auto e = range.end();
  2124.         for (auto i = range.begin(); i != e; ++i)
  2125.                 for (auto j = std::next(i); j != e; ++j)
  2126.                 {
  2127.                         if (i->callsign == j->callsign)
  2128.                                 return "Multiple players with same callsign!";
  2129.                 }
  2130.         return nullptr;
  2131. }
  2132.  
  2133. static void multi_do_decloak(const playernum_t pnum)
  2134. {
  2135.         if (Newdemo_state == ND_STATE_RECORDING)
  2136.                 newdemo_record_multi_decloak(pnum);
  2137. }
  2138.  
  2139. }
  2140.  
  2141. namespace dsx {
  2142.  
  2143. static void multi_do_door_open(fvmwallptr &vmwallptr, const uint8_t *const buf)
  2144. {
  2145.         ubyte side;
  2146.         const segnum_t segnum = GET_INTEL_SHORT(&buf[1]);
  2147.         side = buf[3];
  2148. #if defined(DXX_BUILD_DESCENT_II)
  2149.         ubyte flag= buf[4];
  2150. #endif
  2151.  
  2152.         if (side > 5)
  2153.         {
  2154.                 Int3();
  2155.                 return;
  2156.         }
  2157.  
  2158.         const auto &&useg = vmsegptridx.check_untrusted(segnum);
  2159.         if (!useg)
  2160.                 return;
  2161.         const auto &&seg = *useg;
  2162.         const auto wall_num = seg->shared_segment::sides[side].wall_num;
  2163.         if (wall_num == wall_none) {  //Opening door on illegal wall
  2164.                 Int3();
  2165.                 return;
  2166.         }
  2167.  
  2168.         auto &w = *vmwallptr(wall_num);
  2169.  
  2170.         if (w.type == WALL_BLASTABLE)
  2171.         {
  2172.                 if (!(w.flags & WALL_BLASTED))
  2173.                 {
  2174.                         wall_destroy(seg, side);
  2175.                 }
  2176.                 return;
  2177.         }
  2178.         else if (w.state != WALL_DOOR_OPENING)
  2179.         {
  2180.                 wall_open_door(seg, side);
  2181.         }
  2182. #if defined(DXX_BUILD_DESCENT_II)
  2183.         w.flags = flag;
  2184. #endif
  2185.  
  2186. }
  2187.  
  2188. static void multi_do_create_explosion(fvmobjptridx &vmobjptridx, const playernum_t pnum)
  2189. {
  2190.         create_small_fireball_on_object(vmobjptridx(vcplayerptr(pnum)->objnum), F1_0, 1);
  2191. }
  2192.  
  2193. static void multi_do_controlcen_fire(const ubyte *buf)
  2194. {
  2195.         auto &Objects = LevelUniqueObjectState.Objects;
  2196.         auto &vmobjptridx = Objects.vmptridx;
  2197.         vms_vector to_target;
  2198.         int gun_num;
  2199.         objnum_t objnum;
  2200.         int count = 1;
  2201.  
  2202.         memcpy(&to_target, buf+count, 12);          count += 12;
  2203.         if constexpr (words_bigendian)// swap the vector to_target
  2204.         {
  2205.                 to_target.x = INTEL_INT(to_target.x);
  2206.                 to_target.y = INTEL_INT(to_target.y);
  2207.                 to_target.z = INTEL_INT(to_target.z);
  2208.         }
  2209.         gun_num = buf[count];                       count += 1;
  2210.         objnum = GET_INTEL_SHORT(buf + count);      count += 2;
  2211.  
  2212.         const auto &&uobj = vmobjptridx.check_untrusted(objnum);
  2213.         if (!uobj)
  2214.                 return;
  2215.         const auto &&objp = *uobj;
  2216.         Laser_create_new_easy(to_target, objp->ctype.reactor_info.gun_pos[gun_num], objp, weapon_id_type::CONTROLCEN_WEAPON_NUM, 1);
  2217. }
  2218.  
  2219. static void multi_do_create_powerup(fvmobjptr &vmobjptr, fvmsegptridx &vmsegptridx, const playernum_t pnum, const uint8_t *const buf)
  2220. {
  2221.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  2222.         int count = 1;
  2223.         vms_vector new_pos;
  2224.         char powerup_type;
  2225.  
  2226.         if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
  2227.                 return;
  2228.  
  2229.         count++;
  2230.         powerup_type = buf[count++];
  2231.         const auto &&useg = vmsegptridx.check_untrusted(GET_INTEL_SHORT(&buf[count]));
  2232.         if (!useg)
  2233.                 return;
  2234.         const auto &&segnum = *useg;
  2235.         count += 2;
  2236.         objnum_t objnum = GET_INTEL_SHORT(buf + count); count += 2;
  2237.         memcpy(&new_pos, buf+count, sizeof(vms_vector)); count+=sizeof(vms_vector);
  2238.         if constexpr (words_bigendian)
  2239.         {
  2240.                 new_pos.x = SWAPINT(new_pos.x);
  2241.                 new_pos.y = SWAPINT(new_pos.y);
  2242.                 new_pos.z = SWAPINT(new_pos.z);
  2243.         }
  2244.  
  2245.         Net_create_loc = 0;
  2246.         const auto &&my_objnum = call_object_create_egg(vmobjptr(vcplayerptr(pnum)->objnum), 1, powerup_type);
  2247.  
  2248.         if (my_objnum == object_none) {
  2249.                 return;
  2250.         }
  2251.  
  2252.         if (Network_send_objects && multi_objnum_is_past(my_objnum))
  2253.         {
  2254.                 Network_send_objnum = -1;
  2255.         }
  2256.  
  2257.         my_objnum->pos = new_pos;
  2258.  
  2259.         vm_vec_zero(my_objnum->mtype.phys_info.velocity);
  2260.  
  2261.         obj_relink(vmobjptr, vmsegptr, my_objnum, segnum);
  2262.  
  2263.         map_objnum_local_to_remote(my_objnum, objnum, pnum);
  2264.  
  2265.         object_create_explosion(segnum, new_pos, i2f(5), VCLIP_POWERUP_DISAPPEARANCE);
  2266. }
  2267.  
  2268. static void multi_do_play_sound(object_array &Objects, const playernum_t pnum, const uint8_t *const buf)
  2269. {
  2270.         auto &vcobjptridx = Objects.vcptridx;
  2271.         const auto &plr = *vcplayerptr(pnum);
  2272.         if (!plr.connected)
  2273.                 return;
  2274.  
  2275.         const unsigned sound_num = buf[2];
  2276.         const uint8_t once = buf[3];
  2277.         const fix volume = GET_INTEL_INT(&buf[4]);
  2278.  
  2279.         assert(plr.objnum <= Highest_object_index);
  2280.         const auto objnum = plr.objnum;
  2281.         digi_link_sound_to_object(sound_num, vcobjptridx(objnum), 0, volume, static_cast<sound_stack>(once));
  2282. }
  2283.  
  2284. }
  2285.  
  2286. static void multi_do_score(fvmobjptr &vmobjptr, const playernum_t pnum, const uint8_t *const buf)
  2287. {
  2288.         if (pnum >= N_players)
  2289.         {
  2290.                 Int3(); // Non-terminal, see rob
  2291.                 return;
  2292.         }
  2293.  
  2294.         if (Newdemo_state == ND_STATE_RECORDING) {
  2295.                 int score;
  2296.                 score = GET_INTEL_INT(buf + 2);
  2297.                 newdemo_record_multi_score(pnum, score);
  2298.         }
  2299.         auto &player_info = vmobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info;
  2300.         player_info.mission.score = GET_INTEL_INT(buf + 2);
  2301.         multi_sort_kill_list();
  2302. }
  2303.  
  2304. static void multi_do_trigger(const playernum_t pnum, const ubyte *buf)
  2305. {
  2306.         auto &Objects = LevelUniqueObjectState.Objects;
  2307.         auto &vmobjptr = Objects.vmptr;
  2308.         int trigger = buf[2];
  2309.         if (pnum >= N_players || pnum == Player_num)
  2310.         {
  2311.                 Int3(); // Got trigger from illegal playernum
  2312.                 return;
  2313.         }
  2314.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  2315.         if ((trigger < 0) || (trigger >= Triggers.get_count()))
  2316.         {
  2317.                 Int3(); // Illegal trigger number in multiplayer
  2318.                 return;
  2319.         }
  2320.         check_trigger_sub(get_local_plrobj(), trigger, pnum,0);
  2321. }
  2322.  
  2323. #if defined(DXX_BUILD_DESCENT_II)
  2324. namespace dsx {
  2325.  
  2326. static void multi_do_effect_blowup(const playernum_t pnum, const ubyte *buf)
  2327. {
  2328.         int side;
  2329.         vms_vector hitpnt;
  2330.  
  2331.         if (pnum >= N_players || pnum == Player_num)
  2332.                 return;
  2333.  
  2334.         multi_do_protocol_frame(1, 0); // force packets to be sent, ensuring this packet will be attached to following MULTI_TRIGGER
  2335.  
  2336.         const auto &&useg = vmsegptridx.check_untrusted(GET_INTEL_SHORT(&buf[2]));
  2337.         if (!useg)
  2338.                 return;
  2339.         side = buf[4];
  2340.         hitpnt.x = GET_INTEL_INT(buf + 5);
  2341.         hitpnt.y = GET_INTEL_INT(buf + 9);
  2342.         hitpnt.z = GET_INTEL_INT(buf + 13);
  2343.  
  2344.         //create a dummy object which will be the weapon that hits
  2345.         //the monitor. the blowup code wants to know who the parent of the
  2346.         //laser is, so create a laser whose parent is the player
  2347.         laser_parent laser;
  2348.         laser.parent_type = OBJ_PLAYER;
  2349.         laser.parent_num = pnum;
  2350.  
  2351.         auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
  2352.         check_effect_blowup(LevelSharedDestructibleLightState, Vclip, *useg, side, hitpnt, laser, 0, 1);
  2353. }
  2354.  
  2355. static void multi_do_drop_marker(object_array &Objects, fvmsegptridx &vmsegptridx, const playernum_t pnum, const uint8_t *const buf)
  2356. {
  2357.         vms_vector position;
  2358.  
  2359.         if (pnum==Player_num)  // my marker? don't set it down cuz it might screw up the orientation
  2360.                 return;
  2361.  
  2362.         const auto mesnum = buf[2];
  2363.         const auto game_mode = Game_mode;
  2364.         const auto max_numplayers = Netgame.max_numplayers;
  2365.         if (mesnum >= MarkerState.get_markers_per_player(game_mode, max_numplayers))
  2366.                 return;
  2367.  
  2368.         position.x = GET_INTEL_INT(buf + 3);
  2369.         position.y = GET_INTEL_INT(buf + 7);
  2370.         position.z = GET_INTEL_INT(buf + 11);
  2371.  
  2372.         const auto gmi = convert_player_marker_index_to_game_marker_index(game_mode, max_numplayers, pnum, player_marker_index{mesnum});
  2373.         auto &marker_message = MarkerState.message[gmi];
  2374.         marker_message = {};
  2375.         for (const auto i : xrange(marker_message.size()))
  2376.         {
  2377.                 const auto c = marker_message[i] = buf[15 + i];
  2378.                 if (!c)
  2379.                         break;
  2380.         }
  2381.  
  2382.         auto &mo = MarkerState.imobjidx[gmi];
  2383.         if (mo != object_none)
  2384.                 obj_delete(LevelUniqueObjectState, Segments, Objects.vmptridx(mo));
  2385.  
  2386.         const auto &&plr_objp = Objects.vcptr(vcplayerptr(pnum)->objnum);
  2387.         mo = drop_marker_object(position, vmsegptridx(plr_objp->segnum), plr_objp->orient, gmi);
  2388. }
  2389.  
  2390. }
  2391. #endif
  2392.  
  2393. static void multi_do_hostage_door_status(fvmsegptridx &vmsegptridx, wall_array &Walls, const uint8_t *const buf)
  2394. {
  2395.         // Update hit point status of a door
  2396.  
  2397.         int count = 1;
  2398.         fix hps;
  2399.  
  2400.         wallnum_t wallnum = GET_INTEL_SHORT(buf + count);     count += 2;
  2401.         hps = GET_INTEL_INT(buf + count);           count += 4;
  2402.  
  2403.         auto &vmwallptr = Walls.vmptr;
  2404.         auto &w = *vmwallptr(wallnum);
  2405.         if (wallnum >= Walls.get_count() || hps < 0 || w.type != WALL_BLASTABLE)
  2406.         {
  2407.                 Int3(); // Non-terminal, see Rob
  2408.                 return;
  2409.         }
  2410.  
  2411.         if (hps < w.hps)
  2412.                 wall_damage(vmsegptridx(w.segnum), w.sidenum, w.hps - hps);
  2413. }
  2414.  
  2415. void multi_reset_stuff()
  2416. {
  2417.         auto &Objects = LevelUniqueObjectState.Objects;
  2418.         auto &vmobjptr = Objects.vmptr;
  2419.         // A generic, emergency function to solve problems that crop up
  2420.         // when a player exits quick-out from the game because of a
  2421.         // connection loss.  Fixes several weird bugs!
  2422.  
  2423.         dead_player_end();
  2424.         get_local_plrobj().ctype.player_info.homing_object_dist = -1; // Turn off homing sound.
  2425.         reset_rear_view();
  2426. }
  2427.  
  2428. namespace dsx {
  2429.  
  2430. void multi_reset_player_object(object &objp)
  2431. {
  2432.         //Init physics for a non-console player
  2433.         Assert((objp.type == OBJ_PLAYER) || (objp.type == OBJ_GHOST));
  2434.  
  2435.         vm_vec_zero(objp.mtype.phys_info.velocity);
  2436.         vm_vec_zero(objp.mtype.phys_info.thrust);
  2437.         vm_vec_zero(objp.mtype.phys_info.rotvel);
  2438.         vm_vec_zero(objp.mtype.phys_info.rotthrust);
  2439.         objp.mtype.phys_info.turnroll = 0;
  2440.         objp.mtype.phys_info.mass = Player_ship->mass;
  2441.         objp.mtype.phys_info.drag = Player_ship->drag;
  2442.         if (objp.type == OBJ_PLAYER)
  2443.                 objp.mtype.phys_info.flags = PF_TURNROLL | PF_WIGGLE | PF_USES_THRUST;
  2444.         else
  2445.                 objp.mtype.phys_info.flags &= ~(PF_TURNROLL | PF_LEVELLING | PF_WIGGLE);
  2446.  
  2447.         //Init render info
  2448.  
  2449.         objp.render_type = RT_POLYOBJ;
  2450.         objp.rtype.pobj_info.model_num = Player_ship->model_num;               //what model is this?
  2451.         objp.rtype.pobj_info.subobj_flags = 0;         //zero the flags
  2452.         objp.rtype.pobj_info.anim_angles = {};
  2453.  
  2454.         // Clear misc
  2455.  
  2456.         objp.flags = 0;
  2457.  
  2458.         if (objp.type == OBJ_GHOST)
  2459.                 objp.render_type = RT_NONE;
  2460.         //reset textures for this, if not player 0
  2461.         multi_reset_object_texture (objp);
  2462. }
  2463.  
  2464. static void multi_reset_object_texture(object_base &objp)
  2465. {
  2466.         if (objp.type == OBJ_GHOST)
  2467.                 return;
  2468.  
  2469.         const auto player_id = get_player_id(objp);
  2470.         const auto id = (Game_mode & GM_TEAM)
  2471.                 ? get_team(player_id)
  2472.                 : player_id;
  2473.  
  2474.         auto &pobj_info = objp.rtype.pobj_info;
  2475.         pobj_info.alt_textures = id;
  2476.         if (id)
  2477.         {
  2478.                 auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  2479.                 auto &model = Polygon_models[pobj_info.model_num];
  2480.                 const unsigned n_textures = model.n_textures;
  2481.                 if (N_PLAYER_SHIP_TEXTURES < n_textures)
  2482.                         Error("Too many player ship textures!\n");
  2483.  
  2484.                 const unsigned first_texture = model.first_texture;
  2485.                 for (unsigned i = 0; i < n_textures; ++i)
  2486.                         multi_player_textures[id - 1][i] = ObjBitmaps[ObjBitmapPtrs[first_texture + i]];
  2487.  
  2488.                 multi_player_textures[id-1][4] = ObjBitmaps[ObjBitmapPtrs[First_multi_bitmap_num+(id-1)*2]];
  2489.                 multi_player_textures[id-1][5] = ObjBitmaps[ObjBitmapPtrs[First_multi_bitmap_num+(id-1)*2+1]];
  2490.         }
  2491. }
  2492.  
  2493. }
  2494.  
  2495. void multi_process_bigdata(const playernum_t pnum, const ubyte *const buf, const uint_fast32_t len)
  2496. {
  2497.         // Takes a bunch of messages, check them for validity,
  2498.         // and pass them to multi_process_data.
  2499.  
  2500.         uint_fast32_t bytes_processed = 0;
  2501.  
  2502.         while( bytes_processed < len )  {
  2503.                 const uint_fast32_t type = buf[bytes_processed];
  2504.  
  2505.                 if (type >= std::size(message_length))
  2506.                 {
  2507.                         con_printf(CON_DEBUG, "multi_process_bigdata: Invalid packet type %" PRIuFAST32 "!", type);
  2508.                         return;
  2509.                 }
  2510.                 const uint_fast32_t sub_len = message_length[type];
  2511.  
  2512.                 Assert(sub_len > 0);
  2513.  
  2514.                 if ( (bytes_processed+sub_len) > len )  {
  2515.                         con_printf(CON_DEBUG, "multi_process_bigdata: packet type %" PRIuFAST32 " too short (%" PRIuFAST32 " > %" PRIuFAST32 ")!", type, bytes_processed + sub_len, len);
  2516.                         Int3();
  2517.                         return;
  2518.                 }
  2519.  
  2520.                 multi_process_data(pnum, &buf[bytes_processed], type);
  2521.                 bytes_processed += sub_len;
  2522.         }
  2523. }
  2524.  
  2525. namespace dsx {
  2526.  
  2527. //
  2528. // Part 2 : Functions that send communication messages to inform the other
  2529. //          players of something we did.
  2530. //
  2531.  
  2532. void multi_send_fire(int laser_gun, int laser_level, int laser_flags, int laser_fired, objnum_t laser_track, const imobjptridx_t is_bomb_objnum)
  2533. {
  2534.         auto &Objects = LevelUniqueObjectState.Objects;
  2535.         auto &vmobjptr = Objects.vmptr;
  2536.         static fix64 last_fireup_time = 0;
  2537.  
  2538.         // provoke positional update if possible (20 times per second max. matches vulcan, the fastest firing weapon)
  2539.         if (timer_query() >= (last_fireup_time+(F1_0/20)))
  2540.         {
  2541.                 multi_do_protocol_frame(1, 0);
  2542.                 last_fireup_time = timer_query();
  2543.         }
  2544.  
  2545.         uint8_t multibuf[MAX_MULTI_MESSAGE_LEN+4];
  2546.         multibuf[0] = static_cast<char>(MULTI_FIRE);
  2547.         if (is_bomb_objnum != object_none)
  2548.         {
  2549.                 if (is_proximity_bomb_or_player_smart_mine(get_weapon_id(is_bomb_objnum)))
  2550.                         multibuf[0] = static_cast<char>(MULTI_FIRE_BOMB);
  2551.         }
  2552.         else if (laser_track != object_none)
  2553.         {
  2554.                 multibuf[0] = static_cast<char>(MULTI_FIRE_TRACK);
  2555.         }
  2556.         multibuf[1] = static_cast<char>(Player_num);
  2557.         multibuf[2] = static_cast<char>(laser_gun);
  2558.         multibuf[3] = static_cast<char>(laser_level);
  2559.         multibuf[4] = static_cast<char>(laser_flags);
  2560.         multibuf[5] = static_cast<char>(laser_fired);
  2561.  
  2562.         const auto &ownship = get_local_plrobj();
  2563.         PUT_INTEL_INT(multibuf+6 , ownship.orient.fvec.x);
  2564.         PUT_INTEL_INT(&multibuf[10], ownship.orient.fvec.y);
  2565.         PUT_INTEL_INT(&multibuf[14], ownship.orient.fvec.z);
  2566.  
  2567.         /*
  2568.          * If we fire a bomb, it's persistent. Let others know of it's objnum so host can track it's behaviour over clients (host-authority functions, D2 chaff ability).
  2569.          * If we fire a tracking projectile, we should others let know abotu what we track but we have to pay attention it's mapped correctly.
  2570.          * If we fire something else, we make the packet as small as possible.
  2571.          */
  2572.         if (multibuf[0] == MULTI_FIRE_BOMB)
  2573.         {
  2574.                 map_objnum_local_to_local(is_bomb_objnum);
  2575.                 PUT_INTEL_SHORT(&multibuf[18], static_cast<objnum_t>(is_bomb_objnum));
  2576.                 multi_send_data<MULTI_FIRE_BOMB>(multibuf, 20, 1);
  2577.         }
  2578.         else if (multibuf[0] == MULTI_FIRE_TRACK)
  2579.         {
  2580.                 sbyte remote_owner;
  2581.                 short remote_laser_track = -1;
  2582.  
  2583.                 remote_laser_track = objnum_local_to_remote(laser_track, &remote_owner);
  2584.                 PUT_INTEL_SHORT(&multibuf[18], remote_laser_track);
  2585.                 multibuf[20] = remote_owner;
  2586.                 multi_send_data<MULTI_FIRE_TRACK>(multibuf, 21, 1);
  2587.         }
  2588.         else
  2589.                 multi_send_data<MULTI_FIRE>(multibuf, 18, 1);
  2590. }
  2591.  
  2592. void multi_send_destroy_controlcen(const objnum_t objnum, const playernum_t player)
  2593. {
  2594.         if (player == Player_num)
  2595.                 HUD_init_message_literal(HM_MULTI, TXT_YOU_DEST_CONTROL);
  2596.         else if ((player > 0) && (player < N_players))
  2597.                 HUD_init_message(HM_MULTI, "%s %s", static_cast<const char *>(vcplayerptr(player)->callsign), TXT_HAS_DEST_CONTROL);
  2598.         else
  2599.                 HUD_init_message_literal(HM_MULTI, TXT_CONTROL_DESTROYED);
  2600.  
  2601.         multi_command<MULTI_CONTROLCEN> multibuf;
  2602.         PUT_INTEL_SHORT(&multibuf[1], objnum);
  2603.         multibuf[3] = player;
  2604.         multi_send_data(multibuf, 2);
  2605. }
  2606.  
  2607. #if defined(DXX_BUILD_DESCENT_II)
  2608. void multi_send_drop_marker(const unsigned player, const vms_vector &position, const player_marker_index messagenum, const marker_message_text_t &text)
  2609. {
  2610.                 uint8_t multibuf[MAX_MULTI_MESSAGE_LEN+4];
  2611.                 multibuf[1]=static_cast<char>(player);
  2612.                 multibuf[2] = static_cast<uint8_t>(messagenum);
  2613.                 PUT_INTEL_INT(&multibuf[3], position.x);
  2614.                 PUT_INTEL_INT(&multibuf[7], position.y);
  2615.                 PUT_INTEL_INT(&multibuf[11], position.z);
  2616.                 for (const auto i : xrange(text.size()))
  2617.                         multibuf[15+i]=text[i];
  2618.                 multi_send_data<MULTI_MARKER>(multibuf, 15 + text.size(), 2);
  2619. }
  2620.  
  2621. void multi_send_markers()
  2622. {
  2623.         auto &Objects = LevelUniqueObjectState.Objects;
  2624.         auto &vcobjptr = Objects.vcptr;
  2625.         // send marker positions/text to new player
  2626.  
  2627.         const auto game_mode = Game_mode;
  2628.         const auto max_numplayers = Netgame.max_numplayers;
  2629.         auto &&player_marker_range = get_player_marker_range(MarkerState.get_markers_per_player(game_mode, max_numplayers));
  2630.         for (const playernum_t pnum : xrange(max_numplayers))
  2631.         {
  2632.                 for (const auto pmi : player_marker_range)
  2633.                 {
  2634.                         const auto gmi = convert_player_marker_index_to_game_marker_index(game_mode, max_numplayers, pnum, pmi);
  2635.                         const auto mo = MarkerState.imobjidx[gmi];
  2636.                         if (mo != object_none)
  2637.                                 multi_send_drop_marker(pnum, vcobjptr(mo)->pos, pmi, MarkerState.message[gmi]);
  2638.                 }
  2639.         }
  2640. }
  2641. #endif
  2642.  
  2643. #if defined(DXX_BUILD_DESCENT_I)
  2644. void multi_send_endlevel_start(const multi_endlevel_type secret)
  2645. #elif defined(DXX_BUILD_DESCENT_II)
  2646. void multi_send_endlevel_start()
  2647. #endif
  2648. {
  2649.         multi_command<MULTI_ENDLEVEL_START> buf;
  2650.         buf[1] = Player_num;
  2651. #if defined(DXX_BUILD_DESCENT_I)
  2652.         buf[2] = static_cast<uint8_t>(secret);
  2653. #endif
  2654.  
  2655.         multi_send_data(buf, 2);
  2656.         if (Game_mode & GM_NETWORK)
  2657.         {
  2658.                 get_local_player().connected = CONNECT_ESCAPE_TUNNEL;
  2659.                 switch (multi_protocol)
  2660.                 {
  2661. #if DXX_USE_UDP
  2662.                         case MULTI_PROTO_UDP:
  2663.                                 net_udp_send_endlevel_packet();
  2664.                                 break;
  2665. #endif
  2666.                         default:
  2667.                                 Error("Protocol handling missing in multi_send_endlevel_start\n");
  2668.                                 break;
  2669.                 }
  2670.         }
  2671. }
  2672.  
  2673. void multi_send_player_deres(deres_type_t type)
  2674. {
  2675.         auto &Objects = LevelUniqueObjectState.Objects;
  2676.         auto &vmobjptr = Objects.vmptr;
  2677.         auto &vmobjptridx = Objects.vmptridx;
  2678.         int count = 0;
  2679.         if (Network_send_objects)
  2680.         {
  2681.                 Network_send_objnum = -1;
  2682.         }
  2683.  
  2684.         multi_send_position(vmobjptridx(get_local_player().objnum));
  2685.  
  2686.         multi_command<MULTI_PLAYER_DERES> multibuf;
  2687.         count++;
  2688.         multibuf[count++] = Player_num;
  2689.         multibuf[count++] = type;
  2690.  
  2691. #if defined(DXX_BUILD_DESCENT_I)
  2692. #define PUT_WEAPON_FLAGS(buf,count,value)       (buf[count] = value, ++count)
  2693. #elif defined(DXX_BUILD_DESCENT_II)
  2694. #define PUT_WEAPON_FLAGS(buf,count,value)       ((PUT_INTEL_SHORT(&buf[count], value)), count+=sizeof(uint16_t))
  2695. #endif
  2696.         auto &player_info = get_local_plrobj().ctype.player_info;
  2697.         PUT_WEAPON_FLAGS(multibuf, count, player_info.primary_weapon_flags);
  2698.         multibuf[count++] = static_cast<char>(player_info.laser_level);
  2699.  
  2700.         auto &secondary_ammo = player_info.secondary_ammo;
  2701.         multibuf[count++] = secondary_ammo[HOMING_INDEX];
  2702.         multibuf[count++] = secondary_ammo[CONCUSSION_INDEX];
  2703.         multibuf[count++] = secondary_ammo[SMART_INDEX];
  2704.         multibuf[count++] = secondary_ammo[MEGA_INDEX];
  2705.         multibuf[count++] = secondary_ammo[PROXIMITY_INDEX];
  2706.  
  2707. #if defined(DXX_BUILD_DESCENT_II)
  2708.         multibuf[count++] = secondary_ammo[SMISSILE1_INDEX];
  2709.         multibuf[count++] = secondary_ammo[GUIDED_INDEX];
  2710.         multibuf[count++] = secondary_ammo[SMART_MINE_INDEX];
  2711.         multibuf[count++] = secondary_ammo[SMISSILE4_INDEX];
  2712.         multibuf[count++] = secondary_ammo[SMISSILE5_INDEX];
  2713. #endif
  2714.  
  2715.         PUT_INTEL_SHORT(&multibuf[count], player_info.vulcan_ammo);
  2716.         count += 2;
  2717.         PUT_INTEL_INT(&multibuf[count], player_info.powerup_flags.get_player_flags());
  2718.         count += 4;
  2719.  
  2720.         multibuf[count++] = Net_create_loc;
  2721.  
  2722.         Assert(Net_create_loc <= MAX_NET_CREATE_OBJECTS);
  2723.  
  2724.         memset(&multibuf[count], -1, MAX_NET_CREATE_OBJECTS*sizeof(short));
  2725.  
  2726.         range_for (const auto i, partial_const_range(Net_create_objnums, Net_create_loc))
  2727.         {
  2728.                 if (i <= 0) {
  2729.                         Int3(); // Illegal value in created egg object numbers
  2730.                         count +=2;
  2731.                         continue;
  2732.                 }
  2733.  
  2734.                 PUT_INTEL_SHORT(&multibuf[count], i); count += 2;
  2735.  
  2736.                 // We created these objs so our local number = the network number
  2737.                 map_objnum_local_to_local(i);
  2738.         }
  2739.  
  2740.         Net_create_loc = 0;
  2741.  
  2742.         if (count > message_length[MULTI_PLAYER_DERES])
  2743.         {
  2744.                 Int3(); // See Rob
  2745.         }
  2746.  
  2747.         multi_send_data(multibuf, 2);
  2748.         if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
  2749.                 multi_send_decloak();
  2750.         multi_strip_robots(Player_num);
  2751. }
  2752.  
  2753. }
  2754.  
  2755. void multi_send_message()
  2756. {
  2757.         int loc = 0;
  2758.         if (Network_message_reciever != -1)
  2759.         {
  2760.                 uint8_t multibuf[MAX_MULTI_MESSAGE_LEN+4];
  2761.                 loc += 1;
  2762.                 multibuf[loc] = static_cast<char>(Player_num);                       loc += 1;
  2763.                 strncpy(reinterpret_cast<char *>(multibuf+loc), Network_message, MAX_MESSAGE_LEN); loc += MAX_MESSAGE_LEN;
  2764.                 multibuf[loc-1] = '\0';
  2765.                 multi_send_data<MULTI_MESSAGE>(multibuf, loc, 0);
  2766.                 Network_message_reciever = -1;
  2767.         }
  2768. }
  2769.  
  2770. void multi_send_reappear()
  2771. {
  2772.         auto &Objects = LevelUniqueObjectState.Objects;
  2773.         auto &vmobjptridx = Objects.vmptridx;
  2774.         auto &plr = get_local_player();
  2775.         multi_send_position(vmobjptridx(plr.objnum));
  2776.         multi_command<MULTI_REAPPEAR> multibuf;
  2777.         multibuf[1] = static_cast<char>(Player_num);
  2778.         PUT_INTEL_SHORT(&multibuf[2], plr.objnum);
  2779.  
  2780.         multi_send_data(multibuf, 2);
  2781. }
  2782.  
  2783. namespace dsx {
  2784.  
  2785. void multi_send_position(object &obj)
  2786. {
  2787.         int count=1;
  2788.  
  2789.         quaternionpos qpp{};
  2790.         create_quaternionpos(qpp, obj);
  2791.         multi_command<MULTI_POSITION> multibuf;
  2792.         PUT_INTEL_SHORT(&multibuf[count], qpp.orient.w);                                                        count += 2;
  2793.         PUT_INTEL_SHORT(&multibuf[count], qpp.orient.x);                                                        count += 2;
  2794.         PUT_INTEL_SHORT(&multibuf[count], qpp.orient.y);                                                        count += 2;
  2795.         PUT_INTEL_SHORT(&multibuf[count], qpp.orient.z);                                                        count += 2;
  2796.         PUT_INTEL_INT(&multibuf[count], qpp.pos.x);                                                     count += 4;
  2797.         PUT_INTEL_INT(&multibuf[count], qpp.pos.y);                                                     count += 4;
  2798.         PUT_INTEL_INT(&multibuf[count], qpp.pos.z);                                                     count += 4;
  2799.         PUT_INTEL_SHORT(&multibuf[count], qpp.segment);                                                 count += 2;
  2800.         PUT_INTEL_INT(&multibuf[count], qpp.vel.x);                                                     count += 4;
  2801.         PUT_INTEL_INT(&multibuf[count], qpp.vel.y);                                                     count += 4;
  2802.         PUT_INTEL_INT(&multibuf[count], qpp.vel.z);                                                     count += 4;
  2803.         PUT_INTEL_INT(&multibuf[count], qpp.rotvel.x);                                                  count += 4;
  2804.         PUT_INTEL_INT(&multibuf[count], qpp.rotvel.y);                                                  count += 4;
  2805.         PUT_INTEL_INT(&multibuf[count], qpp.rotvel.z);                                                  count += 4; // 46
  2806.  
  2807.         // send twice while first has priority so the next one will be attached to the next bigdata packet
  2808.         multi_send_data(multibuf, 1);
  2809.         multi_send_data(multibuf, 0);
  2810. }
  2811.  
  2812. /*
  2813.  * I was killed. If I am host, send this info to everyone and compute kill. If I am just a Client I'll only send the kill but not compute it for me. I (Client) will wait for Host to send me my kill back together with updated game_mode related variables which are important for me to compute consistent kill.
  2814.  */
  2815. void multi_send_kill(const vmobjptridx_t objnum)
  2816. {
  2817.         auto &Objects = LevelUniqueObjectState.Objects;
  2818.         auto &imobjptridx = Objects.imptridx;
  2819.         auto &vmobjptr = Objects.vmptr;
  2820.         // I died, tell the world.
  2821.         int count = 0;
  2822.  
  2823.         Assert(get_player_id(objnum) == Player_num);
  2824.         const auto killer_objnum = get_local_plrobj().ctype.player_info.killer_objnum;
  2825.  
  2826.         union {
  2827.                 multi_command<MULTI_KILL_CLIENT> multibufc;
  2828.                 multi_command<MULTI_KILL_HOST> multibufh;
  2829.         };
  2830.                                                         count += 1;
  2831.         multibufh[count] = Player_num;                  count += 1;
  2832.  
  2833.         if (killer_objnum != object_none)
  2834.         {
  2835.                 const auto s = objnum_local_to_remote(killer_objnum, reinterpret_cast<int8_t *>(&multibufh[count+2])); // do it with variable since INTEL_SHORT won't work on return val from function.
  2836.                 PUT_INTEL_SHORT(&multibufh[count], s);
  2837.         }
  2838.         else
  2839.         {
  2840.                 multibufh[count+2] = static_cast<char>(-1);
  2841.                 PUT_INTEL_SHORT(&multibufh[count], static_cast<int16_t>(-1));
  2842.         }
  2843.         count += 3;
  2844.         // I am host - I know what's going on so attach game_mode related info which might be vital for correct kill computation
  2845.         if (multi_i_am_master())
  2846.         {
  2847.                 multibufh[count] = Netgame.team_vector; count += 1;
  2848.                 multibufh[count] = Bounty_target;       count += 1;
  2849.                 multi_compute_kill(imobjptridx(killer_objnum), objnum);
  2850.                 multi_send_data(multibufh, 2);
  2851.         }
  2852.         else
  2853.                 multi_send_data_direct(multibufc, multi_who_is_master(), 2); // I am just a client so I'll only send my kill but not compute it, yet. I'll get response from host so I can compute it correctly
  2854.  
  2855.         multi_strip_robots(Player_num);
  2856.  
  2857.         if (Game_mode & GM_BOUNTY && multi_i_am_master()) // update in case if needed... we could attach this to this packet but... meh...
  2858.                 multi_send_bounty();
  2859. }
  2860.  
  2861. void multi_send_remobj(const vmobjidx_t objnum)
  2862. {
  2863.         // Tell the other guy to remove an object from his list
  2864.  
  2865.         sbyte obj_owner;
  2866.         short remote_objnum;
  2867.  
  2868.         remote_objnum = objnum_local_to_remote(objnum, &obj_owner);
  2869.  
  2870.         multi_command<MULTI_REMOVE_OBJECT> multibuf;
  2871.         PUT_INTEL_SHORT(&multibuf[1], remote_objnum); // Map to network objnums
  2872.  
  2873.         multibuf[3] = obj_owner;
  2874.  
  2875.         multi_send_data(multibuf, 2);
  2876.  
  2877.         if (Network_send_objects && multi_objnum_is_past(objnum))
  2878.         {
  2879.                 Network_send_objnum = -1;
  2880.         }
  2881. }
  2882.  
  2883. }
  2884.  
  2885. namespace dcx {
  2886.  
  2887. void multi_send_quit()
  2888. {
  2889.         // I am quitting the game, tell the other guy the bad news.
  2890.  
  2891.         multi_command<MULTI_QUIT> multibuf;
  2892.         multibuf[1] = Player_num;
  2893.         multi_send_data(multibuf, 2);
  2894.  
  2895. }
  2896.  
  2897. void multi_send_cloak()
  2898. {
  2899.         // Broadcast a change in our pflags (made to support cloaking)
  2900.  
  2901.         multi_command<MULTI_CLOAK> multibuf;
  2902.         const auto pnum = Player_num;
  2903.         multibuf[1] = pnum;
  2904.  
  2905.         multi_send_data(multibuf, 2);
  2906.  
  2907.         multi_strip_robots(pnum);
  2908. }
  2909.  
  2910. void multi_send_decloak()
  2911. {
  2912.         // Broadcast a change in our pflags (made to support cloaking)
  2913.  
  2914.         multi_command<MULTI_DECLOAK> multibuf;
  2915.         multibuf[1] = Player_num;
  2916.  
  2917.         multi_send_data(multibuf, 2);
  2918. }
  2919.  
  2920. }
  2921.  
  2922. namespace dsx {
  2923.  
  2924. void multi_send_door_open(const vcsegidx_t segnum, const unsigned side, const uint8_t flag)
  2925. {
  2926.         multi_command<MULTI_DOOR_OPEN> multibuf;
  2927.         // When we open a door make sure everyone else opens that door
  2928.         PUT_INTEL_SHORT(&multibuf[1], segnum );
  2929.         multibuf[3] = static_cast<int8_t>(side);
  2930. #if defined(DXX_BUILD_DESCENT_I)
  2931.         (void)flag;
  2932. #elif defined(DXX_BUILD_DESCENT_II)
  2933.         multibuf[4] = flag;
  2934. #endif
  2935.         multi_send_data(multibuf, 2);
  2936. }
  2937.  
  2938. #if defined(DXX_BUILD_DESCENT_II)
  2939. void multi_send_door_open_specific(const playernum_t pnum, const vcsegidx_t segnum, const unsigned side, const uint8_t flag)
  2940. {
  2941.         // For sending doors only to a specific person (usually when they're joining)
  2942.  
  2943.         Assert (Game_mode & GM_NETWORK);
  2944.         //   Assert (pnum>-1 && pnum<N_players);
  2945.  
  2946.         multi_command<MULTI_DOOR_OPEN> multibuf;
  2947.         PUT_INTEL_SHORT(&multibuf[1], segnum);
  2948.         multibuf[3] = static_cast<int8_t>(side);
  2949.         multibuf[4] = flag;
  2950.  
  2951.         multi_send_data_direct(multibuf, pnum, 2);
  2952. }
  2953. #endif
  2954.  
  2955. }
  2956.  
  2957. //
  2958. // Part 3 : Functions that change or prepare the game for multiplayer use.
  2959. //          Not including functions needed to syncronize or start the
  2960. //          particular type of multiplayer game.  Includes preparing the
  2961. //                      mines, player structures, etc.
  2962.  
  2963. void multi_send_create_explosion(const playernum_t pnum)
  2964. {
  2965.         // Send all data needed to create a remote explosion
  2966.  
  2967.         int count = 0;
  2968.  
  2969.         count += 1;
  2970.         multi_command<MULTI_CREATE_EXPLOSION> multibuf;
  2971.         multibuf[count] = static_cast<int8_t>(pnum);                  count += 1;
  2972.         //                                                                                                      -----------
  2973.         //                                                                                                      Total size = 2
  2974.         multi_send_data(multibuf, 0);
  2975. }
  2976.  
  2977. void multi_send_controlcen_fire(const vms_vector &to_goal, int best_gun_num, objnum_t objnum)
  2978. {
  2979.         int count = 0;
  2980.  
  2981.         count +=  1;
  2982.         multi_command<MULTI_CONTROLCEN_FIRE> multibuf;
  2983.         if constexpr (words_bigendian)
  2984.         {
  2985.                 vms_vector swapped_vec;
  2986.                 swapped_vec.x = INTEL_INT(static_cast<int>(to_goal.x));
  2987.                 swapped_vec.y = INTEL_INT(static_cast<int>(to_goal.y));
  2988.                 swapped_vec.z = INTEL_INT(static_cast<int>(to_goal.z));
  2989.                 memcpy(&multibuf[count], &swapped_vec, 12);
  2990.         }
  2991.         else
  2992.         {
  2993.                 memcpy(&multibuf[count], &to_goal, 12);
  2994.         }
  2995.         count += 12;
  2996.         multibuf[count] = static_cast<char>(best_gun_num);                   count +=  1;
  2997.         PUT_INTEL_SHORT(&multibuf[count], objnum );     count +=  2;
  2998.         //                                                                                                                      ------------
  2999.         //                                                                                                                      Total  = 16
  3000.         multi_send_data(multibuf, 0);
  3001. }
  3002.  
  3003. namespace dsx {
  3004.  
  3005. void multi_send_create_powerup(const powerup_type_t powerup_type, const vcsegidx_t segnum, const vcobjidx_t objnum, const vms_vector &pos)
  3006. {
  3007.         auto &Objects = LevelUniqueObjectState.Objects;
  3008.         auto &vmobjptridx = Objects.vmptridx;
  3009.         // Create a powerup on a remote machine, used for remote
  3010.         // placement of used powerups like missiles and cloaking
  3011.         // powerups.
  3012.  
  3013.         int count = 0;
  3014.  
  3015.         multi_send_position(vmobjptridx(get_local_player().objnum));
  3016.  
  3017.         count += 1;
  3018.         multi_command<MULTI_CREATE_POWERUP> multibuf;
  3019.         multibuf[count] = Player_num;                                      count += 1;
  3020.         multibuf[count] = powerup_type;                                 count += 1;
  3021.         PUT_INTEL_SHORT(&multibuf[count], segnum );     count += 2;
  3022.         PUT_INTEL_SHORT(&multibuf[count], objnum );     count += 2;
  3023.         if constexpr (words_bigendian)
  3024.         {
  3025.                 vms_vector swapped_vec;
  3026.                 swapped_vec.x = INTEL_INT(static_cast<int>(pos.x));
  3027.                 swapped_vec.y = INTEL_INT(static_cast<int>(pos.y));
  3028.                 swapped_vec.z = INTEL_INT(static_cast<int>(pos.z));
  3029.                 memcpy(&multibuf[count], &swapped_vec, 12);
  3030.                 count += 12;
  3031.         }
  3032.         else
  3033.         {
  3034.                 memcpy(&multibuf[count], &pos, sizeof(vms_vector));
  3035.                 count += sizeof(vms_vector);
  3036.         }
  3037.         //                                                                                                            -----------
  3038.         //                                                                                                            Total =  19
  3039.         multi_send_data(multibuf, 2);
  3040.  
  3041.         if (Network_send_objects && multi_objnum_is_past(objnum))
  3042.         {
  3043.                 Network_send_objnum = -1;
  3044.         }
  3045.  
  3046.         map_objnum_local_to_local(objnum);
  3047. }
  3048.  
  3049. }
  3050.  
  3051. static void multi_digi_play_sample(const int soundnum, const fix max_volume, const sound_stack once)
  3052. {
  3053.         auto &Objects = LevelUniqueObjectState.Objects;
  3054.         auto &vcobjptridx = Objects.vcptridx;
  3055.         if (Game_mode & GM_MULTI)
  3056.                 multi_send_play_sound(soundnum, max_volume, once);
  3057.         digi_link_sound_to_object(soundnum, vcobjptridx(Viewer), 0, max_volume, once);
  3058. }
  3059.  
  3060. void multi_digi_play_sample_once(int soundnum, fix max_volume)
  3061. {
  3062.         multi_digi_play_sample(soundnum, max_volume, sound_stack::cancel_previous);
  3063. }
  3064.  
  3065. void multi_digi_play_sample(int soundnum, fix max_volume)
  3066. {
  3067.         multi_digi_play_sample(soundnum, max_volume, sound_stack::allow_stacking);
  3068. }
  3069.  
  3070. namespace dsx {
  3071.  
  3072. void multi_digi_link_sound_to_pos(const int soundnum, const vcsegptridx_t segnum, const unsigned sidenum, const vms_vector &pos, const int forever, const fix max_volume)
  3073. {
  3074.         if (Game_mode & GM_MULTI)
  3075.                 multi_send_play_sound(soundnum, max_volume, sound_stack::allow_stacking);
  3076.         digi_link_sound_to_pos(soundnum, segnum, sidenum, pos, forever, max_volume);
  3077. }
  3078.  
  3079. }
  3080.  
  3081. void multi_send_play_sound(const int sound_num, const fix volume, const sound_stack once)
  3082. {
  3083.         int count = 0;
  3084.         count += 1;
  3085.         multi_command<MULTI_PLAY_SOUND> multibuf;
  3086.         multibuf[count] = Player_num;                                   count += 1;
  3087.         multibuf[count] = static_cast<char>(sound_num);                      count += 1;
  3088.         multibuf[count] = static_cast<uint8_t>(once);                      count += 1;
  3089.         PUT_INTEL_INT(&multibuf[count], volume);                                                        count += 4;
  3090.         //                                                                                                         -----------
  3091.         //                                                                                                         Total = 4
  3092.         multi_send_data(multibuf, 0);
  3093. }
  3094.  
  3095. void multi_send_score()
  3096. {
  3097.         auto &Objects = LevelUniqueObjectState.Objects;
  3098.         auto &vmobjptr = Objects.vmptr;
  3099.         // Send my current score to all other players so it will remain
  3100.         // synced.
  3101.         int count = 0;
  3102.  
  3103.         if (Game_mode & GM_MULTI_COOP) {
  3104.                 multi_sort_kill_list();
  3105.                 count += 1;
  3106.                 multi_command<MULTI_SCORE> multibuf;
  3107.                 multibuf[count] = Player_num;                           count += 1;
  3108.                 auto &player_info = get_local_plrobj().ctype.player_info;
  3109.                 PUT_INTEL_INT(&multibuf[count], player_info.mission.score);  count += 4;
  3110.                 multi_send_data(multibuf, 0);
  3111.         }
  3112. }
  3113.  
  3114. void multi_send_trigger(const int triggernum)
  3115. {
  3116.         // Send an event to trigger something in the mine
  3117.  
  3118.         int count = 0;
  3119.  
  3120.         count += 1;
  3121.         multi_command<MULTI_TRIGGER> multibuf;
  3122.         multibuf[count] = Player_num;                                   count += 1;
  3123.         multibuf[count] = triggernum;            count += 1;
  3124.  
  3125.         multi_send_data(multibuf, 2);
  3126. }
  3127.  
  3128. namespace dsx {
  3129.  
  3130. #if defined(DXX_BUILD_DESCENT_II)
  3131. void multi_send_effect_blowup(const vcsegidx_t segnum, const unsigned side, const vms_vector &pnt)
  3132. {
  3133.         // We blew up something connected to a trigger. Send this blowup result to other players shortly before MULTI_TRIGGER.
  3134.         // NOTE: The reason this is now a separate packet is to make sure trigger-connected switches/monitors are in sync with MULTI_TRIGGER.
  3135.         //       If a fire packet is late it might blow up a switch for some clients without the shooter actually registering this hit,
  3136.         //       not sending MULTI_TRIGGER and making puzzles or progress impossible.
  3137.         int count = 0;
  3138.  
  3139.         multi_do_protocol_frame(1, 0); // force packets to be sent, ensuring this packet will be attached to following MULTI_TRIGGER
  3140.        
  3141.         count += 1;
  3142.         multi_command<MULTI_EFFECT_BLOWUP> multibuf;
  3143.         multibuf[count] = Player_num;                                   count += 1;
  3144.         PUT_INTEL_SHORT(&multibuf[count], segnum);                        count += 2;
  3145.         multibuf[count] = static_cast<int8_t>(side);                                  count += 1;
  3146.         PUT_INTEL_INT(&multibuf[count], pnt.x);                          count += 4;
  3147.         PUT_INTEL_INT(&multibuf[count], pnt.y);                          count += 4;
  3148.         PUT_INTEL_INT(&multibuf[count], pnt.z);                          count += 4;
  3149.  
  3150.         multi_send_data(multibuf, 0);
  3151. }
  3152. #endif
  3153.  
  3154. void multi_send_hostage_door_status(const vcwallptridx_t w)
  3155. {
  3156.         // Tell the other player what the hit point status of a hostage door
  3157.         // should be
  3158.  
  3159.         int count = 0;
  3160.  
  3161.         assert(w->type == WALL_BLASTABLE);
  3162.  
  3163.         count += 1;
  3164.         multi_command<MULTI_HOSTAGE_DOOR> multibuf;
  3165.         PUT_INTEL_SHORT(&multibuf[count], static_cast<wallnum_t>(w));
  3166.         count += 2;
  3167.         PUT_INTEL_INT(&multibuf[count], w->hps);  count += 4;
  3168.  
  3169.         multi_send_data(multibuf, 0);
  3170. }
  3171.  
  3172. }
  3173.  
  3174. void multi_consistency_error(int reset)
  3175. {
  3176.         static int count = 0;
  3177.  
  3178.         if (reset)
  3179.                 count = 0;
  3180.  
  3181.         if (++count < 10)
  3182.                 return;
  3183.  
  3184.         if (Game_wind)
  3185.                 window_set_visible(Game_wind, 0);
  3186.         nm_messagebox(NULL, 1, TXT_OK, TXT_CONSISTENCY_ERROR);
  3187.         if (Game_wind)
  3188.                 window_set_visible(Game_wind, 1);
  3189.         count = 0;
  3190.         multi_quit_game = 1;
  3191.         game_leave_menus();
  3192.         multi_reset_stuff();
  3193. }
  3194.  
  3195. static constexpr unsigned grant_shift_helper(const packed_spawn_granted_items p, int s)
  3196. {
  3197.         return s > 0 ? p.mask >> s : p.mask << -s;
  3198. }
  3199.  
  3200. namespace dsx {
  3201.  
  3202. player_flags map_granted_flags_to_player_flags(const packed_spawn_granted_items p)
  3203. {
  3204.         auto &grant = p.mask;
  3205.         const auto None = PLAYER_FLAG::None;
  3206.         return player_flags(
  3207.                 ((grant & NETGRANT_QUAD) ? PLAYER_FLAGS_QUAD_LASERS : None)
  3208. #if defined(DXX_BUILD_DESCENT_II)
  3209.                 | ((grant & NETGRANT_AFTERBURNER) ? PLAYER_FLAGS_AFTERBURNER : None)
  3210.                 | ((grant & NETGRANT_AMMORACK) ? PLAYER_FLAGS_AMMO_RACK : None)
  3211.                 | ((grant & NETGRANT_CONVERTER) ? PLAYER_FLAGS_CONVERTER : None)
  3212.                 | ((grant & NETGRANT_HEADLIGHT) ? PLAYER_FLAGS_HEADLIGHT : None)
  3213. #endif
  3214.         );
  3215. }
  3216.  
  3217. uint_fast32_t map_granted_flags_to_primary_weapon_flags(const packed_spawn_granted_items p)
  3218. {
  3219.         auto &grant = p.mask;
  3220.         return ((grant & NETGRANT_VULCAN) ? HAS_VULCAN_FLAG : 0)
  3221.                 | ((grant & NETGRANT_SPREAD) ? HAS_SPREADFIRE_FLAG : 0)
  3222.                 | ((grant & NETGRANT_PLASMA) ? HAS_PLASMA_FLAG : 0)
  3223.                 | ((grant & NETGRANT_FUSION) ? HAS_FUSION_FLAG : 0)
  3224. #if defined(DXX_BUILD_DESCENT_II)
  3225.                 | ((grant & NETGRANT_GAUSS) ? HAS_GAUSS_FLAG : 0)
  3226.                 | ((grant & NETGRANT_HELIX) ? HAS_HELIX_FLAG : 0)
  3227.                 | ((grant & NETGRANT_PHOENIX) ? HAS_PHOENIX_FLAG : 0)
  3228.                 | ((grant & NETGRANT_OMEGA) ? HAS_OMEGA_FLAG : 0)
  3229. #endif
  3230.                 ;
  3231. }
  3232.  
  3233. uint16_t map_granted_flags_to_vulcan_ammo(const packed_spawn_granted_items p)
  3234. {
  3235.         auto &grant = p.mask;
  3236.         const auto amount = VULCAN_WEAPON_AMMO_AMOUNT;
  3237.         return
  3238. #if defined(DXX_BUILD_DESCENT_II)
  3239.                 (grant & NETGRANT_GAUSS ? amount : 0) +
  3240. #endif
  3241.                 (grant & NETGRANT_VULCAN ? amount : 0);
  3242. }
  3243.  
  3244. static constexpr unsigned map_granted_flags_to_netflag(const packed_spawn_granted_items grant)
  3245. {
  3246.         return (grant_shift_helper(grant, BIT_NETGRANT_QUAD - BIT_NETFLAG_DOQUAD) & (NETFLAG_DOQUAD | NETFLAG_DOVULCAN | NETFLAG_DOSPREAD | NETFLAG_DOPLASMA | NETFLAG_DOFUSION))
  3247. #if defined(DXX_BUILD_DESCENT_II)
  3248.                 | (grant_shift_helper(grant, BIT_NETGRANT_GAUSS - BIT_NETFLAG_DOGAUSS) & (NETFLAG_DOGAUSS | NETFLAG_DOHELIX | NETFLAG_DOPHOENIX | NETFLAG_DOOMEGA))
  3249.                 | (grant_shift_helper(grant, BIT_NETGRANT_AFTERBURNER - BIT_NETFLAG_DOAFTERBURNER) & (NETFLAG_DOAFTERBURNER | NETFLAG_DOAMMORACK | NETFLAG_DOCONVERTER | NETFLAG_DOHEADLIGHT))
  3250. #endif
  3251.                 ;
  3252. }
  3253.  
  3254. assert_equal(0, 0, "zero");
  3255. assert_equal(map_granted_flags_to_netflag(NETGRANT_QUAD), NETFLAG_DOQUAD, "QUAD");
  3256. assert_equal(map_granted_flags_to_netflag(NETGRANT_QUAD | NETGRANT_PLASMA), NETFLAG_DOQUAD | NETFLAG_DOPLASMA, "QUAD | PLASMA");
  3257. #if defined(DXX_BUILD_DESCENT_II)
  3258. assert_equal(map_granted_flags_to_netflag(NETGRANT_GAUSS), NETFLAG_DOGAUSS, "GAUSS");
  3259. assert_equal(map_granted_flags_to_netflag(NETGRANT_GAUSS | NETGRANT_PLASMA), NETFLAG_DOGAUSS | NETFLAG_DOPLASMA, "GAUSS | PLASMA");
  3260. assert_equal(map_granted_flags_to_netflag(NETGRANT_GAUSS | NETGRANT_AFTERBURNER), NETFLAG_DOGAUSS | NETFLAG_DOAFTERBURNER, "GAUSS | AFTERBURNER");
  3261. assert_equal(map_granted_flags_to_netflag(NETGRANT_GAUSS | NETGRANT_PLASMA | NETGRANT_AFTERBURNER), NETFLAG_DOGAUSS | NETFLAG_DOPLASMA | NETFLAG_DOAFTERBURNER, "GAUSS | PLASMA | AFTERBURNER");
  3262. assert_equal(map_granted_flags_to_netflag(NETGRANT_PLASMA | NETGRANT_AFTERBURNER), NETFLAG_DOPLASMA | NETFLAG_DOAFTERBURNER, "PLASMA | AFTERBURNER");
  3263. assert_equal(map_granted_flags_to_netflag(NETGRANT_AFTERBURNER), NETFLAG_DOAFTERBURNER, "AFTERBURNER");
  3264. assert_equal(map_granted_flags_to_netflag(NETGRANT_HEADLIGHT), NETFLAG_DOHEADLIGHT, "HEADLIGHT");
  3265. #endif
  3266.  
  3267. namespace {
  3268.  
  3269. class update_item_state
  3270. {
  3271.         std::bitset<MAX_OBJECTS> m_modified;
  3272. public:
  3273.         bool must_skip(const vcobjidx_t i) const
  3274.         {
  3275.                 return m_modified.test(i);
  3276.         }
  3277.         void process_powerup(const d_vclip_array &Vclip, fvmsegptridx &, const object &, powerup_type_t);
  3278. };
  3279.  
  3280. class powerup_shuffle_state
  3281. {
  3282.         unsigned count = 0;
  3283.         unsigned seed;
  3284.         union {
  3285.                 std::array<vmobjptridx_t, MAX_OBJECTS> ptrs;
  3286.         };
  3287. public:
  3288.         powerup_shuffle_state(const unsigned s) :
  3289.                 seed(s)
  3290.         {
  3291.         }
  3292.         void record_powerup(vmobjptridx_t);
  3293.         void shuffle() const;
  3294. };
  3295.  
  3296. void update_item_state::process_powerup(const d_vclip_array &Vclip, fvmsegptridx &vmsegptridx, const object &o, const powerup_type_t id)
  3297. {
  3298.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  3299.         auto &Vertices = LevelSharedVertexState.get_vertices();
  3300.         uint_fast32_t count;
  3301.         switch (id)
  3302.         {
  3303.                 case POW_LASER:
  3304.                 case POW_QUAD_FIRE:
  3305.                 case POW_VULCAN_WEAPON:
  3306.                 case POW_VULCAN_AMMO:
  3307.                 case POW_SPREADFIRE_WEAPON:
  3308.                 case POW_PLASMA_WEAPON:
  3309.                 case POW_FUSION_WEAPON:
  3310. #if defined(DXX_BUILD_DESCENT_II)
  3311.                 case POW_SUPER_LASER:
  3312.                 case POW_GAUSS_WEAPON:
  3313.                 case POW_HELIX_WEAPON:
  3314.                 case POW_PHOENIX_WEAPON:
  3315.                 case POW_OMEGA_WEAPON:
  3316. #endif
  3317.                         count = Netgame.DuplicatePowerups.get_primary_count();
  3318.                         break;
  3319.                 case POW_MISSILE_1:
  3320.                 case POW_MISSILE_4:
  3321.                 case POW_HOMING_AMMO_1:
  3322.                 case POW_HOMING_AMMO_4:
  3323.                 case POW_PROXIMITY_WEAPON:
  3324.                 case POW_SMARTBOMB_WEAPON:
  3325.                 case POW_MEGA_WEAPON:
  3326. #if defined(DXX_BUILD_DESCENT_II)
  3327.                 case POW_SMISSILE1_1:
  3328.                 case POW_SMISSILE1_4:
  3329.                 case POW_GUIDED_MISSILE_1:
  3330.                 case POW_GUIDED_MISSILE_4:
  3331.                 case POW_SMART_MINE:
  3332.                 case POW_MERCURY_MISSILE_1:
  3333.                 case POW_MERCURY_MISSILE_4:
  3334.                 case POW_EARTHSHAKER_MISSILE:
  3335. #endif
  3336.                         count = Netgame.DuplicatePowerups.get_secondary_count();
  3337.                         break;
  3338. #if defined(DXX_BUILD_DESCENT_II)
  3339.                 case POW_FULL_MAP:
  3340.                 case POW_CONVERTER:
  3341.                 case POW_AMMO_RACK:
  3342.                 case POW_AFTERBURNER:
  3343.                 case POW_HEADLIGHT:
  3344.                         count = Netgame.DuplicatePowerups.get_accessory_count();
  3345.                         break;
  3346. #endif
  3347.                 default:
  3348.                         return;
  3349.         }
  3350.         if (!count)
  3351.                 return;
  3352.         const auto &vc = Vclip[o.rtype.vclip_info.vclip_num];
  3353.         const auto vc_num_frames = vc.num_frames;
  3354.         const auto &&segp = vmsegptridx(o.segnum);
  3355.         const auto &seg_verts = segp->verts;
  3356.         auto &vcvertptr = Vertices.vcptr;
  3357.         for (uint_fast32_t i = count++; i; --i)
  3358.         {
  3359.                 assert(o.movement_type == MT_NONE);
  3360.                 assert(o.render_type == RT_POWERUP);
  3361.                 const auto &&no = obj_create(OBJ_POWERUP, id, segp, vm_vec_avg(o.pos, vcvertptr(seg_verts[i % seg_verts.size()])), &vmd_identity_matrix, o.size, CT_POWERUP, MT_NONE, RT_POWERUP);
  3362.                 if (no == object_none)
  3363.                         return;
  3364.                 m_modified.set(no);
  3365.                 no->rtype.vclip_info = o.rtype.vclip_info;
  3366.                 no->rtype.vclip_info.framenum = (o.rtype.vclip_info.framenum + (i * vc_num_frames) / count) % vc_num_frames;
  3367.                 no->ctype.powerup_info = o.ctype.powerup_info;
  3368.         }
  3369. }
  3370.  
  3371. class accumulate_object_count
  3372. {
  3373. protected:
  3374.         using array_reference = std::array<uint32_t, MAX_POWERUP_TYPES> &;
  3375.         array_reference current;
  3376.         accumulate_object_count(array_reference a) : current(a)
  3377.         {
  3378.         }
  3379. };
  3380.  
  3381. template <typename F, typename M>
  3382. class accumulate_flags_count : accumulate_object_count
  3383. {
  3384.         const F &flags;
  3385. public:
  3386.         accumulate_flags_count(array_reference a, const F &f) :
  3387.                 accumulate_object_count(a), flags(f)
  3388.         {
  3389.         }
  3390.         void process(const M mask, const unsigned id) const
  3391.         {
  3392.                 if (flags & mask)
  3393.                         ++current[id];
  3394.         }
  3395. };
  3396.  
  3397. }
  3398.  
  3399. /*
  3400.  * The place to do objects operations such as:
  3401.  * Robot deletion for non-robot games, Powerup duplication, AllowedItems, Initial powerup counting.
  3402.  * MUST be done before multi_level_sync() in case we join a running game and get updated objects there. We want the initial powerup setup for a level here!
  3403.  */
  3404. void multi_prep_level_objects(const d_vclip_array &Vclip)
  3405. {
  3406.         auto &Objects = LevelUniqueObjectState.Objects;
  3407.         auto &vmobjptridx = Objects.vmptridx;
  3408.         if (!(Game_mode & GM_MULTI_COOP))
  3409.         {
  3410.                 multi_update_objects_for_non_cooperative(); // Removes monsters from level
  3411.         }
  3412.  
  3413.         constexpr unsigned MAX_ALLOWED_INVULNERABILITY = 3;
  3414.         constexpr unsigned MAX_ALLOWED_CLOAK = 3;
  3415.         const auto AllowedItems = Netgame.AllowedItems;
  3416.         const auto SpawnGrantedItems = map_granted_flags_to_netflag(Netgame.SpawnGrantedItems);
  3417.         unsigned inv_remaining = (AllowedItems & NETFLAG_DOINVUL) ? MAX_ALLOWED_INVULNERABILITY : 0;
  3418.         unsigned cloak_remaining = (AllowedItems & NETFLAG_DOCLOAK) ? MAX_ALLOWED_CLOAK : 0;
  3419.         update_item_state duplicates;
  3420.         range_for (const auto &&o, vmobjptridx)
  3421.         {
  3422.                 if ((o->type == OBJ_HOSTAGE) && !(Game_mode & GM_MULTI_COOP))
  3423.                 {
  3424.                         const auto objnum = obj_create(OBJ_POWERUP, POW_SHIELD_BOOST, vmsegptridx(o->segnum), o->pos, &vmd_identity_matrix, Powerup_info[POW_SHIELD_BOOST].size, CT_POWERUP, MT_PHYSICS, RT_POWERUP);
  3425.                         obj_delete(LevelUniqueObjectState, Segments, o);
  3426.                         if (objnum != object_none)
  3427.                         {
  3428.                                 objnum->rtype.vclip_info.vclip_num = Powerup_info[POW_SHIELD_BOOST].vclip_num;
  3429.                                 objnum->rtype.vclip_info.frametime = Vclip[objnum->rtype.vclip_info.vclip_num].frame_time;
  3430.                                 objnum->rtype.vclip_info.framenum = 0;
  3431.                                 objnum->mtype.phys_info.drag = 512;     //1024;
  3432.                                 objnum->mtype.phys_info.mass = F1_0;
  3433.                                 vm_vec_zero(objnum->mtype.phys_info.velocity);
  3434.                         }
  3435.                         continue;
  3436.                 }
  3437.  
  3438.                 if (o->type == OBJ_POWERUP && !duplicates.must_skip(o))
  3439.                 {
  3440.                         switch (const auto id = get_powerup_id(o))
  3441.                         {
  3442.                                 case POW_EXTRA_LIFE:
  3443.                                         set_powerup_id(Powerup_info, Vclip, o, POW_INVULNERABILITY);
  3444.                                         DXX_BOOST_FALLTHROUGH;
  3445.                                 case POW_INVULNERABILITY:
  3446.                                         if (inv_remaining)
  3447.                                                 -- inv_remaining;
  3448.                                         else
  3449.                                                 set_powerup_id(Powerup_info, Vclip, o, POW_SHIELD_BOOST);
  3450.                                         continue;
  3451.                                 case POW_CLOAK:
  3452.                                         if (cloak_remaining)
  3453.                                                 -- cloak_remaining;
  3454.                                         else
  3455.                                                 set_powerup_id(Powerup_info, Vclip, o, POW_SHIELD_BOOST);
  3456.                                         continue;
  3457.                                 default:
  3458.                                         if (!multi_powerup_is_allowed(id, AllowedItems, SpawnGrantedItems))
  3459.                                                 bash_to_shield(Powerup_info, Vclip, o);
  3460.                                         else
  3461.                                                 duplicates.process_powerup(Vclip, vmsegptridx, o, id);
  3462.                                         continue;
  3463.                         }
  3464.                 }
  3465.         }
  3466.  
  3467.         // After everything is done, count initial level inventory.
  3468.         MultiLevelInv_InitializeCount();
  3469. }
  3470.  
  3471. void multi_prep_level_player(void)
  3472. {
  3473.         auto &Objects = LevelUniqueObjectState.Objects;
  3474.         auto &vmobjptr = Objects.vmptr;
  3475.         // Do any special stuff to the level required for games
  3476.         // before we begin playing in it.
  3477.  
  3478.         // Player_num MUST be set before calling this procedure.
  3479.  
  3480.         // This function must be called before checksuming the Object array,
  3481.         // since the resulting checksum with depend on the value of Player_num
  3482.         // at the time this is called.
  3483.  
  3484.         Assert(Game_mode & GM_MULTI);
  3485.  
  3486.         Assert(NumNetPlayerPositions > 0);
  3487.  
  3488. #if defined(DXX_BUILD_DESCENT_II)
  3489.         hoard_highest_record_stats = {};
  3490.         Drop_afterburner_blob_flag=0;
  3491. #endif
  3492.         Bounty_target = 0;
  3493.  
  3494.         multi_consistency_error(1);
  3495.  
  3496.         multi_sending_message.fill(msgsend_none);
  3497.         if (imulti_new_game)
  3498.                 for (uint_fast32_t i = 0; i != Players.size(); i++)
  3499.                         init_player_stats_new_ship(i);
  3500.  
  3501.         for (unsigned i = 0; i < NumNetPlayerPositions; i++)
  3502.         {
  3503.                 const auto &&objp = vmobjptr(vcplayerptr(i)->objnum);
  3504.                 if (i != Player_num)
  3505.                         objp->control_type = CT_REMOTE;
  3506.                 objp->movement_type = MT_PHYSICS;
  3507.                 multi_reset_player_object(objp);
  3508.                 Netgame.players[i].LastPacketTime = 0;
  3509.         }
  3510.  
  3511.         robot_controlled.fill(-1);
  3512.         robot_agitation = {};
  3513.         robot_fired = {};
  3514.  
  3515.         Viewer = ConsoleObject = &get_local_plrobj();
  3516.  
  3517. #if defined(DXX_BUILD_DESCENT_II)
  3518.         if (game_mode_hoard())
  3519.                 init_hoard_data(Vclip);
  3520.  
  3521.         if (game_mode_capture_flag() || game_mode_hoard())
  3522.                 multi_apply_goal_textures();
  3523. #endif
  3524.  
  3525.         multi_sort_kill_list();
  3526.  
  3527.         multi_show_player_list();
  3528.  
  3529.         ConsoleObject->control_type = CT_FLYING;
  3530.  
  3531.         reset_player_object();
  3532.  
  3533.         imulti_new_game=0;
  3534. }
  3535.  
  3536. }
  3537.  
  3538. window_event_result multi_level_sync(void)
  3539. {
  3540.         switch (multi_protocol)
  3541.         {
  3542. #if DXX_USE_UDP
  3543.                 case MULTI_PROTO_UDP:
  3544.                         return net_udp_level_sync();
  3545.                         break;
  3546. #endif
  3547.                 default:
  3548.                         Error("Protocol handling missing in multi_level_sync\n");
  3549.                         break;
  3550.         }
  3551.  
  3552.         return window_event_result::ignored;
  3553. }
  3554.  
  3555. namespace dsx {
  3556.  
  3557. #if defined(DXX_BUILD_DESCENT_II)
  3558. static void apply_segment_goal_texture(unique_segment &seg, const std::size_t tex)
  3559. {
  3560.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  3561.         seg.static_light = i2f(100);    //make static light bright
  3562.         if (tex < TmapInfo.size())
  3563.                 range_for (auto &s, seg.sides)
  3564.                 {
  3565.                         s.tmap_num = tex;
  3566.                         range_for (auto &uvl, s.uvls)
  3567.                                 uvl.l = i2f(100);               //max out
  3568.                 }
  3569. }
  3570.  
  3571. void multi_apply_goal_textures()
  3572. {
  3573.         std::size_t tex_blue, tex_red;
  3574.         if (game_mode_hoard())
  3575.                 tex_blue = tex_red = find_goal_texture(TMI_GOAL_HOARD);
  3576.         else
  3577.         {
  3578.                 tex_blue = find_goal_texture(TMI_GOAL_BLUE);
  3579.                 tex_red = find_goal_texture(TMI_GOAL_RED);
  3580.         }
  3581.         range_for (const auto &&seg, vmsegptr)
  3582.         {
  3583.                 std::size_t tex;
  3584.                 if (seg->special==SEGMENT_IS_GOAL_BLUE)
  3585.                 {
  3586.                         tex = tex_blue;
  3587.                 }
  3588.                 else if (seg->special==SEGMENT_IS_GOAL_RED)
  3589.                 {
  3590.                         // Make both textures the same if Hoard mode
  3591.                         tex = tex_red;
  3592.                 }
  3593.                 else
  3594.                         continue;
  3595.                 apply_segment_goal_texture(seg, tex);
  3596.         }
  3597. }
  3598.  
  3599. std::size_t find_goal_texture (ubyte t)
  3600. {
  3601.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  3602.         const auto &&r = partial_const_range(TmapInfo, NumTextures);
  3603.         return std::distance(r.begin(), std::find_if(r.begin(), r.end(), [t](const tmap_info &i) { return (i.flags & t); }));
  3604. }
  3605.  
  3606. const tmap_info &find_required_goal_texture(const uint8_t t)
  3607. {
  3608.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  3609.         std::size_t r = find_goal_texture(t);
  3610.         if (r < TmapInfo.size())
  3611.                 return TmapInfo[r];
  3612.         Int3(); // Hey, there is no goal texture for this PIG!!!!
  3613.         // Edit bitmaps.tbl and designate two textures to be RED and BLUE
  3614.         // goal textures
  3615.         throw std::runtime_error("PIG missing goal texture");
  3616. }
  3617. #endif
  3618.  
  3619. static int object_allowed_in_anarchy(const object_base &objp)
  3620. {
  3621.         if (objp.type == OBJ_NONE ||
  3622.                 objp.type == OBJ_PLAYER ||
  3623.                 objp.type == OBJ_POWERUP ||
  3624.                 objp.type == OBJ_CNTRLCEN ||
  3625.                 objp.type == OBJ_HOSTAGE)
  3626.                 return 1;
  3627. #if defined(DXX_BUILD_DESCENT_II)
  3628.         if (objp.type == OBJ_WEAPON && get_weapon_id(objp) == weapon_id_type::PMINE_ID)
  3629.                 return 1;
  3630. #endif
  3631.         return 0;
  3632. }
  3633.  
  3634. void powerup_shuffle_state::record_powerup(const vmobjptridx_t o)
  3635. {
  3636.         if (!seed)
  3637.                 return;
  3638.         const auto id = get_powerup_id(o);
  3639.         switch (id)
  3640.         {
  3641.                 /* record_powerup runs before object conversion or duplication,
  3642.                  * so object types that anarchy converts still have their
  3643.                  * original type when this switch runs.  Therefore,
  3644.                  * POW_EXTRA_LIFE and the key powerups must be handled here,
  3645.                  * even though they are converted to other objects before play
  3646.                  * begins.  If they were not handled, no object could exchange
  3647.                  * places with a converted object.
  3648.                  */
  3649.                 case POW_EXTRA_LIFE:
  3650.                 case POW_ENERGY:
  3651.                 case POW_SHIELD_BOOST:
  3652.                 case POW_LASER:
  3653.                 case POW_KEY_BLUE:
  3654.                 case POW_KEY_RED:
  3655.                 case POW_KEY_GOLD:
  3656.                 case POW_MISSILE_1:
  3657.                 case POW_MISSILE_4:
  3658.                 case POW_QUAD_FIRE:
  3659.                 case POW_VULCAN_WEAPON:
  3660.                 case POW_SPREADFIRE_WEAPON:
  3661.                 case POW_PLASMA_WEAPON:
  3662.                 case POW_FUSION_WEAPON:
  3663.                 case POW_PROXIMITY_WEAPON:
  3664.                 case POW_HOMING_AMMO_1:
  3665.                 case POW_HOMING_AMMO_4:
  3666.                 case POW_SMARTBOMB_WEAPON:
  3667.                 case POW_MEGA_WEAPON:
  3668.                 case POW_VULCAN_AMMO:
  3669.                 case POW_CLOAK:
  3670.                 case POW_INVULNERABILITY:
  3671. #if defined(DXX_BUILD_DESCENT_II)
  3672.                 case POW_GAUSS_WEAPON:
  3673.                 case POW_HELIX_WEAPON:
  3674.                 case POW_PHOENIX_WEAPON:
  3675.                 case POW_OMEGA_WEAPON:
  3676.  
  3677.                 case POW_SUPER_LASER:
  3678.                 case POW_FULL_MAP:
  3679.                 case POW_CONVERTER:
  3680.                 case POW_AMMO_RACK:
  3681.                 case POW_AFTERBURNER:
  3682.                 case POW_HEADLIGHT:
  3683.  
  3684.                 case POW_SMISSILE1_1:
  3685.                 case POW_SMISSILE1_4:
  3686.                 case POW_GUIDED_MISSILE_1:
  3687.                 case POW_GUIDED_MISSILE_4:
  3688.                 case POW_SMART_MINE:
  3689.                 case POW_MERCURY_MISSILE_1:
  3690.                 case POW_MERCURY_MISSILE_4:
  3691.                 case POW_EARTHSHAKER_MISSILE:
  3692. #endif
  3693.                         break;
  3694.                 default:
  3695.                         return;
  3696.         }
  3697.         if (count >= ptrs.size())
  3698.                 return;
  3699.         ptrs[count++] = o;
  3700. }
  3701.  
  3702. void powerup_shuffle_state::shuffle() const
  3703. {
  3704.         auto &Objects = LevelUniqueObjectState.Objects;
  3705.         auto &vmobjptr = Objects.vmptr;
  3706.         if (!count)
  3707.                 return;
  3708.         std::minstd_rand mr(seed);
  3709.         for (unsigned j = count; --j;)
  3710.         {
  3711.                 const auto oi = std::uniform_int_distribution<unsigned>(0u, j)(mr);
  3712.                 if (oi == j)
  3713.                         /* Swapping an object with itself is a no-op.  Skip the
  3714.                          * work.  Do not re-roll, both to avoid the potential for an
  3715.                          * infinite loop on unlucky rolls and to ensure a uniform
  3716.                          * distribution of swaps.
  3717.                          */
  3718.                         continue;
  3719.                 const auto o0 = ptrs[j];
  3720.                 const auto o1 = ptrs[oi];
  3721.                 const auto os0 = o0->segnum;
  3722.                 const auto os1 = o1->segnum;
  3723.                 /* Disconnect both objects from their original segments.  Swap
  3724.                  * their positions.  Link each object to the segment that the
  3725.                  * other object previously used.  This is necessary instead of
  3726.                  * using std::swap on object::segnum, since the segment's linked
  3727.                  * list of objects needs to be updated.
  3728.                  */
  3729.                 obj_unlink(vmobjptr, vmsegptr, *o0);
  3730.                 obj_unlink(vmobjptr, vmsegptr, *o1);
  3731.                 std::swap(o0->pos, o1->pos);
  3732.                 obj_link_unchecked(vmobjptr, o0, vmsegptridx(os1));
  3733.                 obj_link_unchecked(vmobjptr, o1, vmsegptridx(os0));
  3734.         }
  3735. }
  3736.  
  3737. void multi_update_objects_for_non_cooperative()
  3738. {
  3739.         auto &Objects = LevelUniqueObjectState.Objects;
  3740.         auto &vmobjptridx = Objects.vmptridx;
  3741.         // Go through the object list and remove any objects not used in
  3742.         // 'Anarchy!' games.
  3743.  
  3744.         const auto game_mode = Game_mode;
  3745.         /* Shuffle objects before object duplication runs.  Otherwise,
  3746.          * duplication-eligible items would be duplicated, then scattered,
  3747.          * causing the original site to be a treasure trove of swapped
  3748.          * items.  This way, duplicated items appear with their original.
  3749.          */
  3750.         powerup_shuffle_state powerup_shuffle(Netgame.ShufflePowerupSeed);
  3751.         range_for (const auto &&objp, vmobjptridx)
  3752.         {
  3753.                 const auto obj_type = objp->type;
  3754.                 if (obj_type == OBJ_PLAYER || obj_type == OBJ_GHOST)
  3755.                         continue;
  3756.                 else if (obj_type == OBJ_ROBOT && (game_mode & GM_MULTI_ROBOTS))
  3757.                         continue;
  3758.                 else if (obj_type == OBJ_POWERUP)
  3759.                 {
  3760.                         powerup_shuffle.record_powerup(objp);
  3761.                         continue;
  3762.                 }
  3763.                 else if (!object_allowed_in_anarchy(objp) ) {
  3764. #if defined(DXX_BUILD_DESCENT_II)
  3765.                         // Before deleting object, if it's a robot, drop it's special powerup, if any
  3766.                         if (obj_type == OBJ_ROBOT)
  3767.                                 if (objp->contains_count && (objp->contains_type == OBJ_POWERUP))
  3768.                                         object_create_robot_egg(objp);
  3769. #endif
  3770.                         obj_delete(LevelUniqueObjectState, Segments, objp);
  3771.                 }
  3772.         }
  3773.         powerup_shuffle.shuffle();
  3774. }
  3775.  
  3776. }
  3777.  
  3778. // Returns the Player_num of Master/Host of this game
  3779. playernum_t multi_who_is_master()
  3780. {
  3781.         return 0;
  3782. }
  3783.  
  3784. void change_playernum_to(const playernum_t new_Player_num)
  3785. {
  3786.         if (Player_num < Players.size())
  3787.         {
  3788.                 vmplayerptr(new_Player_num)->callsign = get_local_player().callsign;
  3789.         }
  3790.         Player_num = new_Player_num;
  3791. }
  3792.  
  3793. namespace dsx {
  3794.  
  3795. #if defined(DXX_BUILD_DESCENT_I)
  3796. static
  3797. #endif
  3798. int multi_all_players_alive(const fvcobjptr &vcobjptr, const partial_range_t<const player *> player_range)
  3799. {
  3800.         range_for (auto &plr, player_range)
  3801.         {
  3802.                 const auto connected = plr.connected;
  3803.                 if (connected == CONNECT_PLAYING)
  3804.                 {
  3805.                         if (vcobjptr(plr.objnum)->type == OBJ_GHOST) // player alive?
  3806.                                 return 0;
  3807.                 }
  3808.                 else if (connected != CONNECT_DISCONNECTED) // ... and actually playing?
  3809.                         return 0;
  3810.         }
  3811.         return (1);
  3812. }
  3813.  
  3814. const char *multi_common_deny_save_game(const fvcobjptr &vcobjptr, const partial_range_t<const player *> player_range)
  3815. {
  3816.         if (Network_status == NETSTAT_ENDLEVEL)
  3817.                 return "Level is ending";
  3818.         if (!multi_all_players_alive(vcobjptr, player_range))
  3819.                 return "All players must be alive and playing!";
  3820.         return deny_multi_save_game_duplicate_callsign(player_range);
  3821. }
  3822.  
  3823. const char *multi_interactive_deny_save_game(const fvcobjptr &vcobjptr, const partial_range_t<const player *> player_range, const d_level_unique_control_center_state &LevelUniqueControlCenterState)
  3824. {
  3825.         if (LevelUniqueControlCenterState.Control_center_destroyed)
  3826.                 return "Countdown in progress";
  3827.         return multi_common_deny_save_game(vcobjptr, player_range);
  3828. }
  3829.  
  3830. void multi_send_drop_weapon(const vmobjptridx_t objp, int seed)
  3831. {
  3832.         auto &Objects = LevelUniqueObjectState.Objects;
  3833.         auto &vmobjptridx = Objects.vmptridx;
  3834.         int count=0;
  3835.         int ammo_count;
  3836.  
  3837.         multi_send_position(vmobjptridx(get_local_player().objnum));
  3838.         ammo_count = objp->ctype.powerup_info.count;
  3839.  
  3840. #if defined(DXX_BUILD_DESCENT_II)
  3841.         if (get_powerup_id(objp) == POW_OMEGA_WEAPON && ammo_count == F1_0)
  3842.                 ammo_count = F1_0 - 1; //make fit in short
  3843. #endif
  3844.  
  3845.         Assert(ammo_count < F1_0); //make sure fits in short
  3846.  
  3847.         count++;
  3848.         multi_command<MULTI_DROP_WEAPON> multibuf;
  3849.         multibuf[count++]=static_cast<char>(get_powerup_id(objp));
  3850.         PUT_INTEL_SHORT(&multibuf[count], objp); count += 2;
  3851.         PUT_INTEL_SHORT(&multibuf[count], static_cast<uint16_t>(ammo_count)); count += 2;
  3852.         PUT_INTEL_INT(&multibuf[count], seed);
  3853.         count += 4;
  3854.  
  3855.         map_objnum_local_to_local(objp);
  3856.  
  3857.         multi_send_data(multibuf, 2);
  3858. }
  3859.  
  3860. static void multi_do_drop_weapon(fvmobjptr &vmobjptr, const playernum_t pnum, const uint8_t *const buf)
  3861. {
  3862.         int ammo,remote_objnum,seed;
  3863.         const auto powerup_id = static_cast<powerup_type_t>(buf[1]);
  3864.         remote_objnum = GET_INTEL_SHORT(buf + 2);
  3865.         ammo = GET_INTEL_SHORT(buf + 4);
  3866.         seed = GET_INTEL_INT(buf + 6);
  3867.         const auto objnum = spit_powerup(Vclip, vmobjptr(vcplayerptr(pnum)->objnum), powerup_id, seed);
  3868.  
  3869.         map_objnum_local_to_remote(objnum, remote_objnum, pnum);
  3870.  
  3871.         if (objnum!=object_none)
  3872.                 objnum->ctype.powerup_info.count = ammo;
  3873. }
  3874.  
  3875. #if defined(DXX_BUILD_DESCENT_II)
  3876. // We collected some ammo from a vulcan/gauss cannon powerup. Now we need to let everyone else know about its new ammo count.
  3877. void multi_send_vulcan_weapon_ammo_adjust(const vmobjptridx_t objnum)
  3878. {
  3879.         sbyte obj_owner;
  3880.         const auto remote_objnum = objnum_local_to_remote(objnum, &obj_owner);
  3881.  
  3882.         multi_command<MULTI_VULWPN_AMMO_ADJ> multibuf;
  3883.         PUT_INTEL_SHORT(&multibuf[1], remote_objnum); // Map to network objnums
  3884.  
  3885.         multibuf[3] = obj_owner;
  3886.  
  3887.         const uint16_t ammo_count = objnum->ctype.powerup_info.count;
  3888.         PUT_INTEL_SHORT(&multibuf[4], ammo_count);
  3889.  
  3890.         multi_send_data(multibuf, 2);
  3891.  
  3892.         if (Network_send_objects && multi_objnum_is_past(objnum))
  3893.         {
  3894.                 Network_send_objnum = -1;
  3895.         }
  3896. }
  3897.  
  3898. static void multi_do_vulcan_weapon_ammo_adjust(fvmobjptr &vmobjptr, const uint8_t *const buf)
  3899. {
  3900.         // which object to update
  3901.         const objnum_t objnum = GET_INTEL_SHORT(buf + 1);
  3902.         // which remote list is it entered in
  3903.         auto obj_owner = buf[3];
  3904.  
  3905.         assert(objnum != object_none);
  3906.  
  3907.         if (objnum < 1)
  3908.                 return;
  3909.  
  3910.         auto local_objnum = objnum_remote_to_local(objnum, obj_owner); // translate to local objnum
  3911.  
  3912.         if (local_objnum == object_none)
  3913.         {
  3914.                 return;
  3915.         }
  3916.  
  3917.         const auto &&obj = vmobjptr(local_objnum);
  3918.         if (obj->type != OBJ_POWERUP)
  3919.         {
  3920.                 return;
  3921.         }
  3922.  
  3923.         if (Network_send_objects && multi_objnum_is_past(local_objnum))
  3924.         {
  3925.                 Network_send_objnum = -1;
  3926.         }
  3927.  
  3928.         const auto ammo = GET_INTEL_SHORT(buf + 4);
  3929.                 obj->ctype.powerup_info.count = ammo;
  3930. }
  3931.  
  3932. namespace {
  3933.  
  3934. struct multi_guided_info
  3935. {
  3936.         uint8_t pnum;
  3937.         uint8_t release;
  3938.         shortpos sp;
  3939. };
  3940.  
  3941. DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_GUIDED, multi_guided_info, g, (g.pnum, g.release, g.sp));
  3942.  
  3943. }
  3944.  
  3945. void multi_send_guided_info(const object_base &miss, const char done)
  3946. {
  3947.         multi_guided_info gi;
  3948.         gi.pnum = static_cast<uint8_t>(Player_num);
  3949.         gi.release = done;
  3950.         create_shortpos_little(LevelSharedSegmentState, gi.sp, miss);
  3951.         multi_serialize_write(0, gi);
  3952. }
  3953.  
  3954. static void multi_do_guided(d_level_unique_object_state &LevelUniqueObjectState, const playernum_t pnum, const uint8_t *const buf)
  3955. {
  3956.         multi_guided_info b;
  3957.         multi_serialize_read(buf, b);
  3958.         auto &Objects = LevelUniqueObjectState.Objects;
  3959.         auto &vmobjptr = Objects.vmptr;
  3960.  
  3961.         if (b.release)
  3962.         {
  3963.                 release_guided_missile(LevelUniqueObjectState, pnum);
  3964.                 return;
  3965.         }
  3966.  
  3967.         const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptridx, pnum);
  3968.         if (gimobj == nullptr)
  3969.                 return;
  3970.         const vmobjptridx_t guided_missile = gimobj;
  3971.         extract_shortpos_little(guided_missile, &b.sp);
  3972.         update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, guided_missile);
  3973. }
  3974.  
  3975. void multi_send_stolen_items ()
  3976. {
  3977.         multi_command<MULTI_STOLEN_ITEMS> multibuf;
  3978.         auto &Stolen_items = LevelUniqueObjectState.ThiefState.Stolen_items;
  3979.         std::copy(Stolen_items.begin(), Stolen_items.end(), std::next(multibuf.begin()));
  3980.         multi_send_data(multibuf, 2);
  3981. }
  3982.  
  3983. static void multi_do_stolen_items(const uint8_t *const buf)
  3984. {
  3985.         auto &Stolen_items = LevelUniqueObjectState.ThiefState.Stolen_items;
  3986.         std::copy_n(buf + 1, Stolen_items.size(), Stolen_items.begin());
  3987. }
  3988.  
  3989. void multi_send_wall_status_specific(const playernum_t pnum,uint16_t wallnum,ubyte type,ubyte flags,ubyte state)
  3990. {
  3991.         // Send wall states a specific rejoining player
  3992.  
  3993.         int count=0;
  3994.  
  3995.         Assert (Game_mode & GM_NETWORK);
  3996.         //Assert (pnum>-1 && pnum<N_players);
  3997.  
  3998.         count++;
  3999.         multi_command<MULTI_WALL_STATUS> multibuf;
  4000.         PUT_INTEL_SHORT(&multibuf[count], wallnum);  count+=2;
  4001.         multibuf[count]=type;                 count++;
  4002.         multibuf[count]=flags;                count++;
  4003.         multibuf[count]=state;                count++;
  4004.  
  4005.         multi_send_data_direct(multibuf, pnum, 2);
  4006. }
  4007.  
  4008. static void multi_do_wall_status(fvmwallptr &vmwallptr, const uint8_t *const buf)
  4009. {
  4010.         ubyte flag,type,state;
  4011.  
  4012.         wallnum_t wallnum = GET_INTEL_SHORT(buf + 1);
  4013.         type=buf[3];
  4014.         flag=buf[4];
  4015.         state=buf[5];
  4016.  
  4017.         auto &w = *vmwallptr(wallnum);
  4018.         w.type = type;
  4019.         w.flags = flag;
  4020.         //Assert(state <= 4);
  4021.         w.state = state;
  4022.  
  4023.         if (w.type == WALL_OPEN)
  4024.         {
  4025.                 digi_kill_sound_linked_to_segment(w.segnum, w.sidenum, SOUND_FORCEFIELD_HUM);
  4026.                 //digi_kill_sound_linked_to_segment(csegp-Segments,cside,SOUND_FORCEFIELD_HUM);
  4027.         }
  4028. }
  4029. #endif
  4030.  
  4031. }
  4032.  
  4033. void multi_send_kill_goal_counts()
  4034. {
  4035.         auto &Objects = LevelUniqueObjectState.Objects;
  4036.         auto &vcobjptr = Objects.vcptr;
  4037.         int count=1;
  4038.  
  4039.         multi_command<MULTI_KILLGOALS> multibuf;
  4040.         range_for (auto &i, Players)
  4041.         {
  4042.                 auto &obj = *vcobjptr(i.objnum);
  4043.                 auto &player_info = obj.ctype.player_info;
  4044.                 multibuf[count] = player_info.KillGoalCount;
  4045.                 count++;
  4046.         }
  4047.         multi_send_data(multibuf, 2);
  4048. }
  4049.  
  4050. static void multi_do_kill_goal_counts(fvmobjptr &vmobjptr, const uint8_t *const buf)
  4051. {
  4052.         int count=1;
  4053.  
  4054.         range_for (auto &i, Players)
  4055.         {
  4056.                 auto &obj = *vmobjptr(i.objnum);
  4057.                 auto &player_info = obj.ctype.player_info;
  4058.                 player_info.KillGoalCount = buf[count];
  4059.                 count++;
  4060.         }
  4061. }
  4062.  
  4063. void multi_send_heartbeat ()
  4064. {
  4065.         if (!Netgame.PlayTimeAllowed.count())
  4066.                 return;
  4067.  
  4068.         multi_command<MULTI_HEARTBEAT> multibuf;
  4069.         PUT_INTEL_INT(&multibuf[1], ThisLevelTime.count());
  4070.         multi_send_data(multibuf, 0);
  4071. }
  4072.  
  4073. static void multi_do_heartbeat (const ubyte *buf)
  4074. {
  4075.         fix num;
  4076.  
  4077.         num = GET_INTEL_INT(buf + 1);
  4078.  
  4079.         ThisLevelTime = d_time_fix(num);
  4080. }
  4081.  
  4082. void multi_check_for_killgoal_winner ()
  4083. {
  4084.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  4085.         auto &Objects = LevelUniqueObjectState.Objects;
  4086.         auto &vcobjptr = Objects.vcptr;
  4087.         if (LevelUniqueControlCenterState.Control_center_destroyed)
  4088.                 return;
  4089.  
  4090.         /* For historical compatibility, this routine has some quirks with
  4091.          * scoring:
  4092.          * - When two or more players have the same number of kills, this
  4093.          *   routine always chooses the lowest index player.  No opportunity
  4094.          *   is provided for the players to score a tie-breaking kill, nor
  4095.          *   is any other property (such as remaining shields) considered.
  4096.          * Historical versions had additional quirks relating to
  4097.          * zero/negative kills, but those quirks have been removed.
  4098.          */
  4099.         const auto &local_player = get_local_player();
  4100.         const player *bestplr = nullptr;
  4101.         int highest_kill_goal_count = 0;
  4102.         range_for (auto &i, partial_const_range(Players, N_players))
  4103.         {
  4104.                 auto &obj = *vcobjptr(i.objnum);
  4105.                 auto &player_info = obj.ctype.player_info;
  4106.                 const auto KillGoalCount = player_info.KillGoalCount;
  4107.                 if (highest_kill_goal_count < KillGoalCount)
  4108.                 {
  4109.                         highest_kill_goal_count = KillGoalCount;
  4110.                         bestplr = &i;
  4111.                 }
  4112.         }
  4113.         if (!bestplr)
  4114.         {
  4115.                 /* No player has at least one kill */
  4116.                 HUD_init_message_literal(HM_MULTI, "No one has scored any kills!");
  4117.         }
  4118.         else if (bestplr == &local_player)
  4119.         {
  4120.                 HUD_init_message(HM_MULTI, "You have the best score at %d kills!", highest_kill_goal_count);
  4121.         }
  4122.         else
  4123.                 HUD_init_message(HM_MULTI, "%s has the best score with %d kills!", static_cast<const char *>(bestplr->callsign), highest_kill_goal_count);
  4124.         net_destroy_controlcen(Objects);
  4125. }
  4126.  
  4127. #if defined(DXX_BUILD_DESCENT_II)
  4128. namespace dsx {
  4129.  
  4130. // Sync our seismic time with other players
  4131. void multi_send_seismic(fix duration)
  4132. {
  4133.         int count=1;
  4134.         multi_command<MULTI_SEISMIC> multibuf;
  4135.         PUT_INTEL_INT(&multibuf[count], duration); count += sizeof(duration);
  4136.         multi_send_data(multibuf, 2);
  4137. }
  4138.  
  4139. static void multi_do_seismic (const ubyte *buf)
  4140. {
  4141.         const fix duration = GET_INTEL_INT(&buf[1]);
  4142.         LevelUniqueSeismicState.Seismic_disturbance_end_time = GameTime64 + duration;
  4143.         digi_play_sample (SOUND_SEISMIC_DISTURBANCE_START, F1_0);
  4144. }
  4145.  
  4146. void multi_send_light_specific (const playernum_t pnum, const vcsegptridx_t segnum, const uint8_t val)
  4147. {
  4148.         int count=1;
  4149.  
  4150.         Assert (Game_mode & GM_NETWORK);
  4151.         //  Assert (pnum>-1 && pnum<N_players);
  4152.  
  4153.         multi_command<MULTI_LIGHT> multibuf;
  4154.         PUT_INTEL_SHORT(&multibuf[count], segnum);
  4155.         count += sizeof(uint16_t);
  4156.         multibuf[count] = val; count++;
  4157.  
  4158.         range_for (auto &i, segnum->unique_segment::sides)
  4159.         {
  4160.                 PUT_INTEL_SHORT(&multibuf[count], i.tmap_num2); count+=2;
  4161.         }
  4162.         multi_send_data_direct(multibuf, pnum, 2);
  4163. }
  4164.  
  4165. static void multi_do_light (const ubyte *buf)
  4166. {
  4167.         int i;
  4168.         const auto sides = buf[3];
  4169.  
  4170.         const segnum_t seg = GET_INTEL_SHORT(&buf[1]);
  4171.         const auto &&usegp = vmsegptridx.check_untrusted(seg);
  4172.         if (!usegp)
  4173.                 return;
  4174.         const auto &&segp = *usegp;
  4175.         auto &side_array = segp->unique_segment::sides;
  4176.         for (i=0;i<6;i++)
  4177.         {
  4178.                 if ((sides & (1<<i)))
  4179.                 {
  4180.                         auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
  4181.                         subtract_light(LevelSharedDestructibleLightState, segp, i);
  4182.                         side_array[i].tmap_num2 = GET_INTEL_SHORT(&buf[4 + (2 * i)]);
  4183.                 }
  4184.         }
  4185. }
  4186.  
  4187. static void multi_do_flags(fvmobjptr &vmobjptr, const playernum_t pnum, const uint8_t *const buf)
  4188. {
  4189.         uint flags;
  4190.  
  4191.         flags = GET_INTEL_INT(buf + 2);
  4192.         if (pnum!=Player_num)
  4193.                 vmobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info.powerup_flags = player_flags(flags);
  4194. }
  4195.  
  4196. void multi_send_flags (const playernum_t pnum)
  4197. {
  4198.         auto &Objects = LevelUniqueObjectState.Objects;
  4199.         auto &vmobjptr = Objects.vmptr;
  4200.         multi_command<MULTI_FLAGS> multibuf;
  4201.         multibuf[1]=pnum;
  4202.         PUT_INTEL_INT(&multibuf[2], vmobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info.powerup_flags.get_player_flags());
  4203.  
  4204.         multi_send_data(multibuf, 2);
  4205. }
  4206.  
  4207. void multi_send_drop_blobs (const playernum_t pnum)
  4208. {
  4209.         multi_command<MULTI_DROP_BLOB> multibuf;
  4210.         multibuf[1]=pnum;
  4211.  
  4212.         multi_send_data(multibuf, 0);
  4213. }
  4214.  
  4215. static void multi_do_drop_blob(fvmobjptr &vmobjptr, const playernum_t pnum)
  4216. {
  4217.         drop_afterburner_blobs (vmobjptr(vcplayerptr(pnum)->objnum), 2, i2f(5) / 2, -1);
  4218. }
  4219.  
  4220. }
  4221. #endif
  4222.  
  4223. #if defined(DXX_BUILD_DESCENT_II)
  4224. namespace dsx {
  4225.  
  4226. void multi_send_sound_function (char whichfunc, char sound)
  4227. {
  4228.         int count=0;
  4229.  
  4230.         count++;
  4231.         multi_command<MULTI_SOUND_FUNCTION> multibuf;
  4232.         multibuf[1]=Player_num;             count++;
  4233.         multibuf[2]=whichfunc;              count++;
  4234.         multibuf[3] = sound; count++;       // this would probably work on the PC as well.  Jason?
  4235.         multi_send_data(multibuf, 2);
  4236. }
  4237.  
  4238. #define AFTERBURNER_LOOP_START  20098
  4239. #define AFTERBURNER_LOOP_END    25776
  4240.  
  4241. static void multi_do_sound_function (const playernum_t pnum, const ubyte *buf)
  4242. {
  4243.         auto &Objects = LevelUniqueObjectState.Objects;
  4244.         auto &vcobjptridx = Objects.vcptridx;
  4245.         // for afterburner
  4246.  
  4247.         char whichfunc;
  4248.         int sound;
  4249.  
  4250.         if (get_local_player().connected!=CONNECT_PLAYING)
  4251.                 return;
  4252.  
  4253.         whichfunc=buf[2];
  4254.         sound=buf[3];
  4255.  
  4256.         const auto plobj = vcobjptridx(vcplayerptr(pnum)->objnum);
  4257.         if (whichfunc==0)
  4258.                 digi_kill_sound_linked_to_object(plobj);
  4259.         else if (whichfunc==3)
  4260.                 digi_link_sound_to_object3(sound, plobj, 1, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)}, AFTERBURNER_LOOP_START, AFTERBURNER_LOOP_END);
  4261. }
  4262.  
  4263. void multi_send_capture_bonus (const playernum_t pnum)
  4264. {
  4265.         multi_command<MULTI_CAPTURE_BONUS> multibuf;
  4266.         Assert (game_mode_capture_flag());
  4267.  
  4268.         multibuf[1]=pnum;
  4269.  
  4270.         multi_send_data(multibuf, 2);
  4271.         multi_do_capture_bonus (pnum);
  4272. }
  4273.  
  4274. void multi_send_orb_bonus (const playernum_t pnum, const uint8_t hoard_orbs)
  4275. {
  4276.         multi_command<MULTI_ORB_BONUS> multibuf;
  4277.         Assert (game_mode_hoard());
  4278.  
  4279.         multibuf[1]=pnum;
  4280.         multibuf[2] = hoard_orbs;
  4281.  
  4282.         multi_send_data(multibuf, 2);
  4283.         multi_do_orb_bonus (pnum, multibuf.data());
  4284. }
  4285.  
  4286. void multi_do_capture_bonus(const playernum_t pnum)
  4287. {
  4288.         auto &Objects = LevelUniqueObjectState.Objects;
  4289.         auto &vmobjptr = Objects.vmptr;
  4290.         // Figure out the results of a network kills and add it to the
  4291.         // appropriate player's tally.
  4292.  
  4293.         int TheGoal;
  4294.  
  4295.         if (pnum==Player_num)
  4296.                 HUD_init_message_literal(HM_MULTI, "You have Scored!");
  4297.         else
  4298.                 HUD_init_message(HM_MULTI, "%s has Scored!", static_cast<const char *>(vcplayerptr(pnum)->callsign));
  4299.  
  4300.         digi_play_sample(pnum == Player_num
  4301.                 ? SOUND_HUD_YOU_GOT_GOAL
  4302.                 : (get_team(pnum) == TEAM_RED
  4303.                         ? SOUND_HUD_RED_GOT_GOAL
  4304.                         : SOUND_HUD_BLUE_GOT_GOAL
  4305.                 ), F1_0*2);
  4306.  
  4307.  
  4308.         team_kills[get_team(pnum)] += 5;
  4309.         auto &plr = *vcplayerptr(pnum);
  4310.         auto &player_info = vmobjptr(plr.objnum)->ctype.player_info;
  4311.         player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG;  // Clear capture flag
  4312.         player_info.net_kills_total += 5;
  4313.         player_info.KillGoalCount += 5;
  4314.  
  4315.         if (Netgame.KillGoal>0)
  4316.         {
  4317.                 TheGoal=Netgame.KillGoal*5;
  4318.  
  4319.                 if (player_info.KillGoalCount >= TheGoal)
  4320.                 {
  4321.                         if (pnum==Player_num)
  4322.                         {
  4323.                                 HUD_init_message_literal(HM_MULTI, "You reached the kill goal!");
  4324.                                 get_local_plrobj().shields = i2f(200);
  4325.                         }
  4326.                         else
  4327.                                 HUD_init_message(HM_MULTI, "%s has reached the kill goal!",static_cast<const char *>(vcplayerptr(pnum)->callsign));
  4328.                         net_destroy_controlcen(Objects);
  4329.                 }
  4330.         }
  4331.  
  4332.         multi_sort_kill_list();
  4333.         multi_show_player_list();
  4334. }
  4335.  
  4336. static int GetOrbBonus (char num)
  4337. {
  4338.         int bonus;
  4339.  
  4340.         bonus=num*(num+1)/2;
  4341.         return (bonus);
  4342. }
  4343.  
  4344. void multi_do_orb_bonus(const playernum_t pnum, const uint8_t *const buf)
  4345. {
  4346.         auto &Objects = LevelUniqueObjectState.Objects;
  4347.         auto &vmobjptr = Objects.vmptr;
  4348.         // Figure out the results of a network kills and add it to the
  4349.         // appropriate player's tally.
  4350.  
  4351.         int TheGoal;
  4352.         int bonus=GetOrbBonus (buf[2]);
  4353.  
  4354.         if (pnum==Player_num)
  4355.                 HUD_init_message(HM_MULTI, "You have scored %d points!",bonus);
  4356.         else
  4357.                 HUD_init_message(HM_MULTI, "%s has scored with %d orbs!",static_cast<const char *>(vcplayerptr(pnum)->callsign), buf[2]);
  4358.  
  4359.         if (pnum==Player_num)
  4360.                 digi_start_sound_queued (SOUND_HUD_YOU_GOT_GOAL,F1_0*2);
  4361.         else
  4362.                 digi_play_sample((Game_mode & GM_TEAM)
  4363.                         ? (get_team(pnum) == TEAM_RED
  4364.                                 ? SOUND_HUD_RED_GOT_GOAL
  4365.                                 : SOUND_HUD_BLUE_GOT_GOAL
  4366.                         ) : SOUND_OPPONENT_HAS_SCORED, F1_0*2);
  4367.  
  4368.         if (bonus > hoard_highest_record_stats.points)
  4369.         {
  4370.                 hoard_highest_record_stats.player = pnum;
  4371.                 hoard_highest_record_stats.points = bonus;
  4372.                 if (pnum==Player_num)
  4373.                         HUD_init_message(HM_MULTI, "You have the record with %d points!",bonus);
  4374.                 else
  4375.                         HUD_init_message(HM_MULTI, "%s has the record with %d points!",static_cast<const char *>(vcplayerptr(pnum)->callsign),bonus);
  4376.                 digi_play_sample (SOUND_BUDDY_MET_GOAL,F1_0*2);
  4377.         }
  4378.  
  4379.  
  4380.         team_kills[get_team(pnum)] += bonus;
  4381.         auto &plr = *vcplayerptr(pnum);
  4382.         auto &player_info = vmobjptr(plr.objnum)->ctype.player_info;
  4383.         player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG;  // Clear orb flag
  4384.         player_info.net_kills_total += bonus;
  4385.         player_info.KillGoalCount += bonus;
  4386.  
  4387.         team_kills[get_team(pnum)]%=1000;
  4388.         player_info.net_kills_total%=1000;
  4389.         player_info.KillGoalCount %= 1000;
  4390.  
  4391.         if (Netgame.KillGoal>0)
  4392.         {
  4393.                 TheGoal=Netgame.KillGoal*5;
  4394.  
  4395.                 if (player_info.KillGoalCount >= TheGoal)
  4396.                 {
  4397.                         if (pnum==Player_num)
  4398.                         {
  4399.                                 HUD_init_message_literal(HM_MULTI, "You reached the kill goal!");
  4400.                                 get_local_plrobj().shields = i2f(200);
  4401.                         }
  4402.                         else
  4403.                                 HUD_init_message(HM_MULTI, "%s has reached the kill goal!",static_cast<const char *>(vcplayerptr(pnum)->callsign));
  4404.                         net_destroy_controlcen(Objects);
  4405.                 }
  4406.         }
  4407.         multi_sort_kill_list();
  4408.         multi_show_player_list();
  4409. }
  4410.  
  4411. void multi_send_got_flag (const playernum_t pnum)
  4412. {
  4413.         multi_command<MULTI_GOT_FLAG> multibuf;
  4414.         multibuf[1]=pnum;
  4415.  
  4416.         digi_start_sound_queued (SOUND_HUD_YOU_GOT_FLAG,F1_0*2);
  4417.  
  4418.         multi_send_data(multibuf, 2);
  4419.         multi_send_flags (Player_num);
  4420. }
  4421.  
  4422. void multi_send_got_orb (const playernum_t pnum)
  4423. {
  4424.         multi_command<MULTI_GOT_ORB> multibuf;
  4425.         multibuf[1]=pnum;
  4426.  
  4427.         digi_play_sample (SOUND_YOU_GOT_ORB,F1_0*2);
  4428.  
  4429.         multi_send_data(multibuf, 2);
  4430.         multi_send_flags (Player_num);
  4431. }
  4432.  
  4433. static void multi_do_got_flag (const playernum_t pnum)
  4434. {
  4435.         auto &Objects = LevelUniqueObjectState.Objects;
  4436.         auto &vmobjptr = Objects.vmptr;
  4437.         digi_start_sound_queued(pnum == Player_num
  4438.                 ? SOUND_HUD_YOU_GOT_FLAG
  4439.                 : (get_team(pnum) == TEAM_RED
  4440.                         ? SOUND_HUD_RED_GOT_FLAG
  4441.                         : SOUND_HUD_BLUE_GOT_FLAG
  4442.                 ), F1_0*2);
  4443.         vmobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info.powerup_flags |= PLAYER_FLAGS_FLAG;
  4444.         HUD_init_message(HM_MULTI, "%s picked up a flag!",static_cast<const char *>(vcplayerptr(pnum)->callsign));
  4445. }
  4446.  
  4447. static void multi_do_got_orb (const playernum_t pnum)
  4448. {
  4449.         auto &Objects = LevelUniqueObjectState.Objects;
  4450.         auto &vmobjptr = Objects.vmptr;
  4451.         Assert (game_mode_hoard());
  4452.  
  4453.         digi_play_sample((Game_mode & GM_TEAM) && get_team(pnum) == get_team(Player_num)
  4454.                 ? SOUND_FRIEND_GOT_ORB
  4455.                 : SOUND_OPPONENT_GOT_ORB, F1_0*2);
  4456.  
  4457.         const auto &&objp = vmobjptr(vcplayerptr(pnum)->objnum);
  4458.         objp->ctype.player_info.powerup_flags |= PLAYER_FLAGS_FLAG;
  4459.         HUD_init_message(HM_MULTI, "%s picked up an orb!",static_cast<const char *>(vcplayerptr(pnum)->callsign));
  4460. }
  4461.  
  4462.  
  4463. static void DropOrb ()
  4464. {
  4465.         auto &Objects = LevelUniqueObjectState.Objects;
  4466.         auto &vmobjptr = Objects.vmptr;
  4467.         int seed;
  4468.  
  4469.         if (!game_mode_hoard())
  4470.                 Int3(); // How did we get here? Get Leighton!
  4471.  
  4472.         auto &player_info = get_local_plrobj().ctype.player_info;
  4473.         auto &proximity = player_info.hoard.orbs;
  4474.         if (!proximity)
  4475.         {
  4476.                 HUD_init_message_literal(HM_MULTI, "No orbs to drop!");
  4477.                 return;
  4478.         }
  4479.  
  4480.         seed = d_rand();
  4481.  
  4482.         const auto &&objnum = spit_powerup(Vclip, vmobjptr(ConsoleObject), POW_HOARD_ORB, seed);
  4483.  
  4484.         if (objnum == object_none)
  4485.                 return;
  4486.  
  4487.         HUD_init_message_literal(HM_MULTI, "Orb dropped!");
  4488.         digi_play_sample (SOUND_DROP_WEAPON,F1_0);
  4489.  
  4490.         multi_send_drop_flag(objnum, seed);
  4491.         -- proximity;
  4492.  
  4493.         // If empty, tell everyone to stop drawing the box around me
  4494.         if (!proximity)
  4495.         {
  4496.                 player_info.powerup_flags &=~(PLAYER_FLAGS_FLAG);
  4497.                 multi_send_flags (Player_num);
  4498.         }
  4499. }
  4500.  
  4501. void DropFlag ()
  4502. {
  4503.         auto &Objects = LevelUniqueObjectState.Objects;
  4504.         auto &vmobjptr = Objects.vmptr;
  4505.         int seed;
  4506.  
  4507.         if (!game_mode_capture_flag() && !game_mode_hoard())
  4508.                 return;
  4509.         if (game_mode_hoard())
  4510.         {
  4511.                 DropOrb();
  4512.                 return;
  4513.         }
  4514.  
  4515.         auto &player_info = get_local_plrobj().ctype.player_info;
  4516.         if (!(player_info.powerup_flags & PLAYER_FLAGS_FLAG))
  4517.         {
  4518.                 HUD_init_message_literal(HM_MULTI, "No flag to drop!");
  4519.                 return;
  4520.         }
  4521.         seed = d_rand();
  4522.         const auto &&objnum = spit_powerup(Vclip, vmobjptr(ConsoleObject), get_team(Player_num) == TEAM_RED ? POW_FLAG_BLUE : POW_FLAG_RED, seed);
  4523.         if (objnum == object_none)
  4524.         {
  4525.                 HUD_init_message_literal(HM_MULTI, "Failed to drop flag!");
  4526.                 return;
  4527.         }
  4528.  
  4529.         HUD_init_message_literal(HM_MULTI, "Flag dropped!");
  4530.         digi_play_sample (SOUND_DROP_WEAPON,F1_0);
  4531.  
  4532.         if (game_mode_capture_flag())
  4533.                 multi_send_drop_flag(objnum,seed);
  4534.  
  4535.         player_info.powerup_flags &=~(PLAYER_FLAGS_FLAG);
  4536. }
  4537.  
  4538.  
  4539. void multi_send_drop_flag(const vmobjptridx_t objp, int seed)
  4540. {
  4541.         multi_command<MULTI_DROP_FLAG> multibuf;
  4542.         int count=0;
  4543.         count++;
  4544.         multibuf[count++]=static_cast<char>(get_powerup_id(objp));
  4545.  
  4546.         PUT_INTEL_SHORT(&multibuf[count], objp.get_unchecked_index());
  4547.         count += 2;
  4548.         PUT_INTEL_INT(&multibuf[count], seed);
  4549.  
  4550.         map_objnum_local_to_local(objp);
  4551.  
  4552.         multi_send_data(multibuf, 2);
  4553. }
  4554.  
  4555. static void multi_do_drop_flag (const playernum_t pnum, const ubyte *buf)
  4556. {
  4557.         auto &Objects = LevelUniqueObjectState.Objects;
  4558.         auto &vmobjptr = Objects.vmptr;
  4559.         int remote_objnum,seed;
  4560.         const auto powerup_id = static_cast<powerup_type_t>(buf[1]);
  4561.         remote_objnum = GET_INTEL_SHORT(buf + 2);
  4562.         seed = GET_INTEL_INT(buf + 6);
  4563.  
  4564.         const auto &&objp = vmobjptr(vcplayerptr(pnum)->objnum);
  4565.  
  4566.         imobjidx_t objnum = spit_powerup(Vclip, objp, powerup_id, seed);
  4567.  
  4568.         map_objnum_local_to_remote(objnum, remote_objnum, pnum);
  4569.         if (!game_mode_hoard())
  4570.                 objp->ctype.player_info.powerup_flags &= ~(PLAYER_FLAGS_FLAG);
  4571. }
  4572.  
  4573. }
  4574. #endif
  4575.  
  4576. namespace dsx {
  4577.  
  4578. uint_fast32_t multi_powerup_is_allowed(const unsigned id, const unsigned AllowedItems)
  4579. {
  4580.         return multi_powerup_is_allowed(id, AllowedItems, map_granted_flags_to_netflag(Netgame.SpawnGrantedItems));
  4581. }
  4582.  
  4583. uint_fast32_t multi_powerup_is_allowed(const unsigned id, const unsigned BaseAllowedItems, const unsigned SpawnGrantedItems)
  4584. {
  4585.         const auto AllowedItems = BaseAllowedItems & ~SpawnGrantedItems;
  4586.         switch (id)
  4587.         {
  4588.                 case POW_KEY_BLUE:
  4589.                 case POW_KEY_GOLD:
  4590.                 case POW_KEY_RED:
  4591.                         return Game_mode & GM_MULTI_COOP;
  4592.                 case POW_INVULNERABILITY:
  4593.                         return AllowedItems & NETFLAG_DOINVUL;
  4594.                 case POW_CLOAK:
  4595.                         return AllowedItems & NETFLAG_DOCLOAK;
  4596.                 case POW_LASER:
  4597.                         if (map_granted_flags_to_laser_level(Netgame.SpawnGrantedItems) >= MAX_LASER_LEVEL)
  4598.                                 return 0;
  4599.                         return AllowedItems & NETFLAG_DOLASER;
  4600.                 case POW_QUAD_FIRE:
  4601.                         return AllowedItems & NETFLAG_DOQUAD;
  4602.                 case POW_VULCAN_WEAPON:
  4603.                         return AllowedItems & NETFLAG_DOVULCAN;
  4604.                 case POW_SPREADFIRE_WEAPON:
  4605.                         return AllowedItems & NETFLAG_DOSPREAD;
  4606.                 case POW_PLASMA_WEAPON:
  4607.                         return AllowedItems & NETFLAG_DOPLASMA;
  4608.                 case POW_FUSION_WEAPON:
  4609.                         return AllowedItems & NETFLAG_DOFUSION;
  4610.                 case POW_HOMING_AMMO_1:
  4611.                 case POW_HOMING_AMMO_4:
  4612.                         return AllowedItems & NETFLAG_DOHOMING;
  4613.                 case POW_PROXIMITY_WEAPON:
  4614.                         return AllowedItems & NETFLAG_DOPROXIM;
  4615.                 case POW_SMARTBOMB_WEAPON:
  4616.                         return AllowedItems & NETFLAG_DOSMART;
  4617.                 case POW_MEGA_WEAPON:
  4618.                         return AllowedItems & NETFLAG_DOMEGA;
  4619.                 case POW_VULCAN_AMMO:
  4620. #if defined(DXX_BUILD_DESCENT_I)
  4621.                         return BaseAllowedItems & NETFLAG_DOVULCAN;
  4622. #elif defined(DXX_BUILD_DESCENT_II)
  4623.                         return BaseAllowedItems & (NETFLAG_DOVULCAN | NETFLAG_DOGAUSS);
  4624. #endif
  4625. #if defined(DXX_BUILD_DESCENT_II)
  4626.                 case POW_SUPER_LASER:
  4627.                         if (map_granted_flags_to_laser_level(Netgame.SpawnGrantedItems) >= MAX_SUPER_LASER_LEVEL)
  4628.                                 return 0;
  4629.                         return AllowedItems & NETFLAG_DOSUPERLASER;
  4630.                 case POW_GAUSS_WEAPON:
  4631.                         return AllowedItems & NETFLAG_DOGAUSS;
  4632.                 case POW_HELIX_WEAPON:
  4633.                         return AllowedItems & NETFLAG_DOHELIX;
  4634.                 case POW_PHOENIX_WEAPON:
  4635.                         return AllowedItems & NETFLAG_DOPHOENIX;
  4636.                 case POW_OMEGA_WEAPON:
  4637.                         return AllowedItems & NETFLAG_DOOMEGA;
  4638.                 case POW_SMISSILE1_1:
  4639.                 case POW_SMISSILE1_4:
  4640.                         return AllowedItems & NETFLAG_DOFLASH;
  4641.                 case POW_GUIDED_MISSILE_1:
  4642.                 case POW_GUIDED_MISSILE_4:
  4643.                         return AllowedItems & NETFLAG_DOGUIDED;
  4644.                 case POW_SMART_MINE:
  4645.                         return AllowedItems & NETFLAG_DOSMARTMINE;
  4646.                 case POW_MERCURY_MISSILE_1:
  4647.                 case POW_MERCURY_MISSILE_4:
  4648.                         return AllowedItems & NETFLAG_DOMERCURY;
  4649.                 case POW_EARTHSHAKER_MISSILE:
  4650.                         return AllowedItems & NETFLAG_DOSHAKER;
  4651.                 case POW_AFTERBURNER:
  4652.                         return AllowedItems & NETFLAG_DOAFTERBURNER;
  4653.                 case POW_CONVERTER:
  4654.                         return AllowedItems & NETFLAG_DOCONVERTER;
  4655.                 case POW_AMMO_RACK:
  4656.                         return AllowedItems & NETFLAG_DOAMMORACK;
  4657.                 case POW_HEADLIGHT:
  4658.                         return AllowedItems & NETFLAG_DOHEADLIGHT;
  4659.                 case POW_FLAG_BLUE:
  4660.                 case POW_FLAG_RED:
  4661.                         return game_mode_capture_flag();
  4662. #endif
  4663.                 default:
  4664.                         return 1;
  4665.         }
  4666. }
  4667.  
  4668. #if defined(DXX_BUILD_DESCENT_II)
  4669. void multi_send_finish_game ()
  4670. {
  4671.         multi_command<MULTI_FINISH_GAME> multibuf;
  4672.         multibuf[1]=Player_num;
  4673.  
  4674.         multi_send_data(multibuf, 2);
  4675. }
  4676.  
  4677. static void multi_do_finish_game(const uint8_t *const buf)
  4678. {
  4679.         if (buf[0]!=MULTI_FINISH_GAME)
  4680.                 return;
  4681.  
  4682.         if (Current_level_num!=Last_level)
  4683.                 return;
  4684.  
  4685.         do_final_boss_hacks();
  4686. }
  4687.  
  4688. void multi_send_trigger_specific(const playernum_t pnum, const uint8_t trig)
  4689. {
  4690.         multi_command<MULTI_START_TRIGGER> multibuf;
  4691.         multibuf[1] = trig;
  4692.  
  4693.         multi_send_data_direct(multibuf, pnum, 2);
  4694. }
  4695.  
  4696. static void multi_do_start_trigger(const uint8_t *const buf)
  4697. {
  4698.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  4699.         auto &vmtrgptr = Triggers.vmptr;
  4700.         vmtrgptr(static_cast<trgnum_t>(buf[1]))->flags |= trigger_behavior_flags::disabled;
  4701. }
  4702. #endif
  4703.  
  4704. }
  4705.  
  4706. namespace dsx {
  4707. static void multi_adjust_lifetime_ranking(int &k, const int count)
  4708. {
  4709.         if (!(Game_mode & GM_NETWORK))
  4710.                 return;
  4711.  
  4712.         const auto oldrank = GetMyNetRanking();
  4713.         k += count;
  4714.         const auto newrank = GetMyNetRanking();
  4715.         if (oldrank != newrank)
  4716.         {
  4717.                 Netgame.players[Player_num].rank = newrank;
  4718.                 multi_send_ranking(newrank);
  4719.                 if (!PlayerCfg.NoRankings)
  4720.                 {
  4721.                         HUD_init_message(HM_MULTI, "You have been %smoted to %s!", newrank > oldrank ? "pro" : "de", RankStrings[newrank]);
  4722. #if defined(DXX_BUILD_DESCENT_I)
  4723.                         digi_play_sample (SOUND_CONTROL_CENTER_WARNING_SIREN,F1_0*2);
  4724. #elif defined(DXX_BUILD_DESCENT_II)
  4725.                         digi_play_sample (SOUND_BUDDY_MET_GOAL,F1_0*2);
  4726. #endif
  4727.                 }
  4728.         }
  4729. }
  4730. }
  4731.  
  4732. void multi_add_lifetime_kills(const int count)
  4733. {
  4734.         // This function adds a kill to lifetime stats of this player, and possibly
  4735.         // gives a promotion.  If so, it will tell everyone else
  4736.  
  4737.         multi_adjust_lifetime_ranking(PlayerCfg.NetlifeKills, count);
  4738. }
  4739.  
  4740. void multi_add_lifetime_killed ()
  4741. {
  4742.         // This function adds a "killed" to lifetime stats of this player, and possibly
  4743.         // gives a demotion.  If so, it will tell everyone else
  4744.  
  4745.         if (Game_mode & GM_MULTI_COOP)
  4746.                 return;
  4747.  
  4748.         multi_adjust_lifetime_ranking(PlayerCfg.NetlifeKilled, 1);
  4749. }
  4750.  
  4751. void multi_send_ranking (uint8_t newrank)
  4752. {
  4753.         multi_command<MULTI_RANK> multibuf;
  4754.         multibuf[1]=static_cast<char>(Player_num);
  4755.         multibuf[2] = newrank;
  4756.  
  4757.         multi_send_data(multibuf, 2);
  4758. }
  4759.  
  4760. static void multi_do_ranking (const playernum_t pnum, const ubyte *buf)
  4761. {
  4762.         const uint8_t rank = buf[2];
  4763.         if (!(rank && rank < RankStrings.size()))
  4764.                 return;
  4765.  
  4766.         auto &netrank = Netgame.players[pnum].rank;
  4767.         if (netrank == rank)
  4768.                 return;
  4769.         const auto rankstr = (netrank < rank) ? "pro" : "de";
  4770.         netrank = rank;
  4771.  
  4772.         if (!PlayerCfg.NoRankings)
  4773.                 HUD_init_message(HM_MULTI, "%s has been %smoted to %s!",static_cast<const char *>(vcplayerptr(pnum)->callsign), rankstr, RankStrings[rank]);
  4774. }
  4775.  
  4776. namespace dcx {
  4777.  
  4778. // Decide if fire from "killer" is friendly. If yes return 1 (no harm to me) otherwise 0 (damage me)
  4779. static int multi_maybe_disable_friendly_fire(const object_base *const killer)
  4780. {
  4781.         if (!(Game_mode & GM_NETWORK)) // no Multiplayer game -> always harm me!
  4782.                 return 0;
  4783.         if (!Netgame.NoFriendlyFire) // friendly fire is activated -> harm me!
  4784.                 return 0;
  4785.         if (!killer) // no actual killer -> harm me!
  4786.                 return 0;
  4787.         if (killer->type != OBJ_PLAYER) // not a player -> harm me!
  4788.                 return 0;
  4789.         if (auto is_coop = Game_mode & GM_MULTI_COOP) // coop mode -> don't harm me!
  4790.                 return is_coop;
  4791.         else if (Game_mode & GM_TEAM) // team mode - find out if killer is in my team
  4792.         {
  4793.                 if (get_team(Player_num) == get_team(get_player_id(*killer))) // in my team -> don't harm me!
  4794.                         return 1;
  4795.                 else // opposite team -> harm me!
  4796.                         return 0;
  4797.         }
  4798.         return 0; // all other cases -> harm me!
  4799. }
  4800.  
  4801. }
  4802.  
  4803. namespace dsx {
  4804.  
  4805. int multi_maybe_disable_friendly_fire(const object *const killer)
  4806. {
  4807.         return multi_maybe_disable_friendly_fire(static_cast<const object_base *>(killer));
  4808. }
  4809.  
  4810. }
  4811.  
  4812. /* Bounty packer sender and handler */
  4813. void multi_send_bounty( void )
  4814. {
  4815.         /* Test game mode */
  4816.         if( !( Game_mode & GM_BOUNTY ) )
  4817.                 return;
  4818.         if ( !multi_i_am_master() )
  4819.                 return;
  4820.        
  4821.         multi_command<MULTI_DO_BOUNTY> multibuf;
  4822.         /* Add opcode, target ID and how often we re-assigned */
  4823.         multibuf[1] = static_cast<char>(Bounty_target);
  4824.        
  4825.         /* Send data */
  4826.         multi_send_data(multibuf, 2);
  4827. }
  4828.  
  4829. static void multi_do_bounty( const ubyte *buf )
  4830. {
  4831.         if ( multi_i_am_master() )
  4832.                 return;
  4833.        
  4834.         multi_new_bounty_target( buf[1] );
  4835. }
  4836.  
  4837. namespace dsx {
  4838.  
  4839. void multi_new_bounty_target(const playernum_t pnum )
  4840. {
  4841.         /* If it's already the same, don't do it */
  4842.         if( Bounty_target == pnum )
  4843.                 return;
  4844.        
  4845.         /* Set the target */
  4846.         Bounty_target = pnum;
  4847.        
  4848.         /* Send a message */
  4849.         HUD_init_message( HM_MULTI, "%c%c%s is the new target!", CC_COLOR,
  4850.                 BM_XRGB(player_rgb[pnum].r, player_rgb[pnum].g, player_rgb[pnum].b),
  4851.                 static_cast<const char *>(vcplayerptr(pnum)->callsign));
  4852.  
  4853. #if defined(DXX_BUILD_DESCENT_I)
  4854.         digi_play_sample( SOUND_CONTROL_CENTER_WARNING_SIREN, F1_0 * 3 );
  4855. #elif defined(DXX_BUILD_DESCENT_II)
  4856.         digi_play_sample( SOUND_BUDDY_MET_GOAL, F1_0 * 2 );
  4857. #endif
  4858. }
  4859.  
  4860. static void multi_do_save_game(const uint8_t *const buf)
  4861. {
  4862.         int count = 1;
  4863.         ubyte slot;
  4864.         uint id;
  4865.         d_game_unique_state::savegame_description desc;
  4866.  
  4867.         slot = buf[count];                      count += 1;
  4868.         id = GET_INTEL_INT(buf+count);                  count += 4;
  4869.         memcpy(desc.data(), &buf[count], desc.size());
  4870.         desc.back() = 0;
  4871.  
  4872.         multi_save_game(static_cast<unsigned>(slot), id, desc);
  4873. }
  4874.  
  4875. }
  4876.  
  4877. static void multi_do_restore_game(const ubyte *buf)
  4878. {
  4879.         int count = 1;
  4880.         ubyte slot;
  4881.         uint id;
  4882.  
  4883.         slot = buf[count];                      count += 1;
  4884.         id = GET_INTEL_INT(buf+count);                  count += 4;
  4885.  
  4886.         multi_restore_game( slot, id );
  4887. }
  4888.  
  4889. namespace dcx {
  4890.  
  4891. static void multi_send_save_game(const d_game_unique_state::save_slot slot, const unsigned id, const d_game_unique_state::savegame_description &desc)
  4892. {
  4893.         int count = 0;
  4894.        
  4895.         count += 1;
  4896.         multi_command<MULTI_SAVE_GAME> multibuf;
  4897.         multibuf[count] = static_cast<uint8_t>(slot);                           count += 1; // Save slot=0
  4898.         PUT_INTEL_INT(&multibuf[count], id );           count += 4; // Save id
  4899.         memcpy(&multibuf[count], desc.data(), desc.size());
  4900.  
  4901.         multi_send_data(multibuf, 2);
  4902. }
  4903.  
  4904. static void multi_send_restore_game(ubyte slot, uint id)
  4905. {
  4906.         int count = 0;
  4907.        
  4908.         count += 1;
  4909.         multi_command<MULTI_RESTORE_GAME> multibuf;
  4910.         multibuf[count] = slot;                         count += 1; // Save slot=0
  4911.         PUT_INTEL_INT(&multibuf[count], id );           count += 4; // Save id
  4912.  
  4913.         multi_send_data(multibuf, 2);
  4914. }
  4915.  
  4916. }
  4917.  
  4918. namespace dsx {
  4919.  
  4920. void multi_initiate_save_game()
  4921. {
  4922.         auto &Objects = LevelUniqueObjectState.Objects;
  4923.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  4924.         auto &vcobjptr = Objects.vcptr;
  4925.  
  4926.         if (const auto reason = multi_i_am_master() ? multi_interactive_deny_save_game(vcobjptr, partial_range(Players, N_players), LevelUniqueControlCenterState) : "Only the host is allowed to save a game!")
  4927.         {
  4928.                 HUD_init_message(HM_MULTI, "Cannot save: %s", reason);
  4929.                 return;
  4930.         }
  4931.  
  4932.         d_game_unique_state::savegame_file_path filename{};
  4933.         d_game_unique_state::savegame_description desc{};
  4934.         const auto slot = state_get_save_file(filename, &desc, blind_save::no);
  4935.         if (!GameUniqueState.valid_save_slot(slot))
  4936.                 return;
  4937.         const auto &&player_range = partial_const_range(Players, N_players);
  4938.         // Execute "alive" and "duplicate callsign" checks again in case things changed while host decided upon the savegame.
  4939.         if (const auto reason = multi_interactive_deny_save_game(vcobjptr, player_range, LevelUniqueControlCenterState))
  4940.         {
  4941.                 HUD_init_message(HM_MULTI, "Cannot save: %s", reason);
  4942.                 return;
  4943.         }
  4944.         multi_execute_save_game(slot, desc, player_range);
  4945. }
  4946.  
  4947. void multi_execute_save_game(const d_game_unique_state::save_slot slot, const d_game_unique_state::savegame_description &desc, const partial_range_t<const player *> player_range)
  4948. {
  4949.         // Make a unique game id
  4950.         fix game_id;
  4951.         game_id = static_cast<fix>(timer_query());
  4952.         game_id ^= N_players<<4;
  4953.         range_for (auto &i, player_range)
  4954.         {
  4955.                 fix call2i;
  4956.                 memcpy(&call2i, static_cast<const char *>(i.callsign), sizeof(fix));
  4957.                 game_id ^= call2i;
  4958.         }
  4959.         if ( game_id == 0 )
  4960.                 game_id = 1; // 0 is invalid
  4961.  
  4962.         multi_send_save_game( slot, game_id, desc );
  4963.         multi_do_frame();
  4964.         multi_save_game(static_cast<unsigned>(slot), game_id, desc);
  4965. }
  4966.  
  4967. void multi_initiate_restore_game()
  4968. {
  4969.         auto &Objects = LevelUniqueObjectState.Objects;
  4970.         auto &vcobjptr = Objects.vcptr;
  4971.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  4972.  
  4973.         if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
  4974.                 return;
  4975.  
  4976.         if (const auto reason = multi_i_am_master() ? multi_interactive_deny_save_game(vcobjptr, partial_const_range(Players, N_players), LevelUniqueControlCenterState) : "Only host is allowed to load a game!")
  4977.         {
  4978.                 HUD_init_message(HM_MULTI, "Cannot load: %s", reason);
  4979.                 return;
  4980.         }
  4981.         d_game_unique_state::savegame_file_path filename;
  4982.         const auto eslot = state_get_restore_file(filename, blind_save::no);
  4983.         if (!GameUniqueState.valid_load_slot(eslot))
  4984.                 return;
  4985.         /* Recheck the interactive conditions, but not the host status.  If
  4986.          * this system was the host before, it must still be the host now.
  4987.          */
  4988.         if (const auto reason = multi_interactive_deny_save_game(vcobjptr, partial_const_range(Players, N_players), LevelUniqueControlCenterState))
  4989.         {
  4990.                 HUD_init_message(HM_MULTI, "Cannot load: %s", reason);
  4991.                 return;
  4992.         }
  4993.         state_game_id = state_get_game_id(filename);
  4994.         if (!state_game_id)
  4995.                 return;
  4996.         const unsigned slot = static_cast<unsigned>(eslot);
  4997.         multi_send_restore_game(slot,state_game_id);
  4998.         multi_do_frame();
  4999.         multi_restore_game(slot,state_game_id);
  5000. }
  5001.  
  5002. void multi_save_game(const unsigned slot, const unsigned id, const d_game_unique_state::savegame_description &desc)
  5003. {
  5004.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  5005.         char filename[PATH_MAX];
  5006.  
  5007.         if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
  5008.                 return;
  5009.  
  5010.         snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%s.mg%x"), static_cast<const char *>(get_local_player().callsign), slot);
  5011.         HUD_init_message(HM_MULTI, "Saving game #%d, '%s'", slot, desc.data());
  5012.         state_game_id = id;
  5013.         pause_game_world_time p;
  5014.         state_save_all_sub(filename, desc.data());
  5015. }
  5016.  
  5017. void multi_restore_game(const unsigned slot, const unsigned id)
  5018. {
  5019.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  5020.         d_game_unique_state::savegame_file_path filename;
  5021.  
  5022.         if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
  5023.                 return;
  5024.  
  5025.         auto &plr = get_local_player();
  5026.         snprintf(filename.data(), filename.size(), PLAYER_DIRECTORY_STRING("%s.mg%x"), static_cast<const char *>(plr.callsign), slot);
  5027.    
  5028.         for (unsigned i = 0, n = N_players; i < n; ++i)
  5029.                 multi_strip_robots(i);
  5030.         if (multi_i_am_master()) // put all players to wait-state again so we can sync up properly
  5031.                 range_for (auto &i, Players)
  5032.                         if (i.connected == CONNECT_PLAYING && &i != &plr)
  5033.                                 i.connected = CONNECT_WAITING;
  5034.    
  5035.         const auto thisid = state_get_game_id(filename);
  5036.         if (thisid!=id)
  5037.         {
  5038.                 nm_messagebox(NULL, 1, TXT_OK, "A multi-save game was restored\nthat you are missing or does not\nmatch that of the others.\nYou must rejoin if you wish to\ncontinue.");
  5039.                 return;
  5040.         }
  5041.  
  5042. #if defined(DXX_BUILD_DESCENT_II)
  5043.         auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
  5044. #endif
  5045.         state_restore_all_sub(
  5046. #if defined(DXX_BUILD_DESCENT_II)
  5047.                 LevelSharedDestructibleLightState, secret_restore::none,
  5048. #endif
  5049.                 filename.data());
  5050.         multi_send_score(); // send my restored scores. I sent 0 when I loaded the level anyways...
  5051. }
  5052.  
  5053. }
  5054.  
  5055. static void multi_do_msgsend_state(const uint8_t *buf)
  5056. {
  5057.         multi_sending_message[static_cast<int>(buf[1])] = static_cast<msgsend_state_t>(buf[2]);
  5058. }
  5059.  
  5060. void multi_send_msgsend_state(msgsend_state_t state)
  5061. {
  5062.         multi_command<MULTI_TYPING_STATE> multibuf;
  5063.         multibuf[1] = Player_num;
  5064.         multibuf[2] = static_cast<char>(state);
  5065.        
  5066.         multi_send_data(multibuf, 2);
  5067. }
  5068.  
  5069. // Specific variables related to our game mode we want the clients to know about
  5070. void multi_send_gmode_update()
  5071. {
  5072.         if (!multi_i_am_master())
  5073.                 return;
  5074.         if (!(Game_mode & GM_TEAM || Game_mode & GM_BOUNTY)) // expand if necessary
  5075.                 return;
  5076.         multi_command<MULTI_GMODE_UPDATE> multibuf;
  5077.         multibuf[1] = Netgame.team_vector;
  5078.         multibuf[2] = Bounty_target;
  5079.        
  5080.         multi_send_data(multibuf, 0);
  5081. }
  5082.  
  5083. static void multi_do_gmode_update(const ubyte *buf)
  5084. {
  5085.         auto &Objects = LevelUniqueObjectState.Objects;
  5086.         auto &vmobjptr = Objects.vmptr;
  5087.         if (multi_i_am_master())
  5088.                 return;
  5089.         if (Game_mode & GM_TEAM)
  5090.         {
  5091.                 if (buf[1] != Netgame.team_vector)
  5092.                 {
  5093.                         Netgame.team_vector = buf[1];
  5094.                         range_for (auto &t, partial_const_range(Players, N_players))
  5095.                                 if (t.connected)
  5096.                                         multi_reset_object_texture (vmobjptr(t.objnum));
  5097.                         reset_cockpit();
  5098.                 }
  5099.         }
  5100.         if (Game_mode & GM_BOUNTY)
  5101.         {
  5102.                 Bounty_target = buf[2]; // accept silently - message about change we SHOULD have gotten due to kill computation
  5103.         }
  5104. }
  5105.  
  5106. /*
  5107.  * Send player inventory to all other players. Intended to be used for the host to repopulate the level with new powerups.
  5108.  * Could also be used to let host decide which powerups a client is allowed to collect and/or drop, anti-cheat functions (needs shield/energy update then and more frequent updates/triggers).
  5109.  */
  5110. namespace dsx {
  5111. void multi_send_player_inventory(int priority)
  5112. {
  5113.         auto &Objects = LevelUniqueObjectState.Objects;
  5114.         auto &vmobjptr = Objects.vmptr;
  5115.         multi_command<MULTI_PLAYER_INV> multibuf;
  5116.         int count = 0;
  5117.  
  5118.         count++;
  5119.         multibuf[count++] = Player_num;
  5120.  
  5121.         auto &player_info = get_local_plrobj().ctype.player_info;
  5122.         PUT_WEAPON_FLAGS(multibuf, count, player_info.primary_weapon_flags);
  5123.         multibuf[count++] = static_cast<char>(player_info.laser_level);
  5124.  
  5125.         auto &secondary_ammo = player_info.secondary_ammo;
  5126.         multibuf[count++] = secondary_ammo[HOMING_INDEX];
  5127.         multibuf[count++] = secondary_ammo[CONCUSSION_INDEX];
  5128.         multibuf[count++] = secondary_ammo[SMART_INDEX];
  5129.         multibuf[count++] = secondary_ammo[MEGA_INDEX];
  5130.         multibuf[count++] = secondary_ammo[PROXIMITY_INDEX];
  5131.  
  5132. #if defined(DXX_BUILD_DESCENT_II)
  5133.         multibuf[count++] = secondary_ammo[SMISSILE1_INDEX];
  5134.         multibuf[count++] = secondary_ammo[GUIDED_INDEX];
  5135.         multibuf[count++] = secondary_ammo[SMART_MINE_INDEX];
  5136.         multibuf[count++] = secondary_ammo[SMISSILE4_INDEX];
  5137.         multibuf[count++] = secondary_ammo[SMISSILE5_INDEX];
  5138. #endif
  5139.  
  5140.         PUT_INTEL_SHORT(&multibuf[count], player_info.vulcan_ammo);
  5141.         count += 2;
  5142.         PUT_INTEL_INT(&multibuf[count], player_info.powerup_flags.get_player_flags());
  5143.         count += 4;
  5144.  
  5145.         multi_send_data(multibuf, priority);
  5146. }
  5147. }
  5148.  
  5149. namespace dsx {
  5150. static void multi_do_player_inventory(const playernum_t pnum, const ubyte *buf)
  5151. {
  5152.         auto &Objects = LevelUniqueObjectState.Objects;
  5153.         auto &vmobjptridx = Objects.vmptridx;
  5154.         int count;
  5155.  
  5156. #ifdef NDEBUG
  5157.         if (pnum >= N_players || pnum == Player_num)
  5158.                 return;
  5159. #else
  5160.         Assert(pnum < N_players);
  5161.         Assert(pnum != Player_num);
  5162. #endif
  5163.  
  5164.         count = 2;
  5165. #if defined(DXX_BUILD_DESCENT_I)
  5166. #define GET_WEAPON_FLAGS(buf,count)     buf[count++]
  5167. #elif defined(DXX_BUILD_DESCENT_II)
  5168. #define GET_WEAPON_FLAGS(buf,count)     (count += sizeof(uint16_t), GET_INTEL_SHORT(buf + (count - sizeof(uint16_t))))
  5169. #endif
  5170.         const auto &&objp = vmobjptridx(vcplayerptr(pnum)->objnum);
  5171.         auto &player_info = objp->ctype.player_info;
  5172.         player_info.primary_weapon_flags = GET_WEAPON_FLAGS(buf, count);
  5173.         player_info.laser_level = stored_laser_level(buf[count]);                           count++;
  5174.  
  5175.         auto &secondary_ammo = player_info.secondary_ammo;
  5176.         secondary_ammo[HOMING_INDEX] = buf[count];                count++;
  5177.         secondary_ammo[CONCUSSION_INDEX] = buf[count];count++;
  5178.         secondary_ammo[SMART_INDEX] = buf[count];         count++;
  5179.         secondary_ammo[MEGA_INDEX] = buf[count];          count++;
  5180.         secondary_ammo[PROXIMITY_INDEX] = buf[count]; count++;
  5181.  
  5182. #if defined(DXX_BUILD_DESCENT_II)
  5183.         secondary_ammo[SMISSILE1_INDEX] = buf[count]; count++;
  5184.         secondary_ammo[GUIDED_INDEX]    = buf[count]; count++;
  5185.         secondary_ammo[SMART_MINE_INDEX]= buf[count]; count++;
  5186.         secondary_ammo[SMISSILE4_INDEX] = buf[count]; count++;
  5187.         secondary_ammo[SMISSILE5_INDEX] = buf[count]; count++;
  5188. #endif
  5189.  
  5190.         player_info.vulcan_ammo = GET_INTEL_SHORT(buf + count); count += 2;
  5191.         player_info.powerup_flags = player_flags(GET_INTEL_INT(buf + count));    count += 4;
  5192. }
  5193. }
  5194.  
  5195. /*
  5196.  * Count the inventory of the level. Initial (start) or current (now).
  5197.  * In 'current', also consider player inventories (and the thief bot).
  5198.  * NOTE: We add actual ammo amount - we do not want to count in 'amount of powerups'. Makes it easier to keep track of overhead (proximities, vulcan ammo)
  5199.  */
  5200. namespace dsx {
  5201. static void MultiLevelInv_CountLevelPowerups()
  5202. {
  5203.         auto &Objects = LevelUniqueObjectState.Objects;
  5204.         auto &vmobjptridx = Objects.vmptridx;
  5205.         if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_COOP))
  5206.                 return;
  5207.         MultiLevelInv.Current = {};
  5208.  
  5209.         range_for (const auto &&objp, vmobjptridx)
  5210.         {
  5211.                 if (objp->type == OBJ_WEAPON) // keep live bombs in inventory so they will respawn after they're gone
  5212.                 {
  5213.                         auto wid = get_weapon_id(objp);
  5214.                         if (wid == weapon_id_type::PROXIMITY_ID)
  5215.                                 MultiLevelInv.Current[POW_PROXIMITY_WEAPON]++;
  5216. #if defined(DXX_BUILD_DESCENT_II)
  5217.                         if (wid == weapon_id_type::SUPERPROX_ID)
  5218.                                 MultiLevelInv.Current[POW_SMART_MINE]++;
  5219. #endif
  5220.                 }
  5221.                 if (objp->type != OBJ_POWERUP)
  5222.                         continue;
  5223.                 auto pid = get_powerup_id(objp);
  5224.                 switch (pid)
  5225.                 {
  5226.                                         case POW_VULCAN_WEAPON:
  5227. #if defined(DXX_BUILD_DESCENT_II)
  5228.                                         case POW_GAUSS_WEAPON:
  5229. #endif
  5230.                                                 MultiLevelInv.Current[POW_VULCAN_AMMO] += objp->ctype.powerup_info.count; // add contained ammo so we do not lose this from level when used up
  5231.                                                 /* fall through to increment Current[pid] */
  5232.                                                 DXX_BOOST_FALLTHROUGH;
  5233.                         case POW_LASER:
  5234.                         case POW_QUAD_FIRE:
  5235.                         case POW_SPREADFIRE_WEAPON:
  5236.                         case POW_PLASMA_WEAPON:
  5237.                         case POW_FUSION_WEAPON:
  5238.                         case POW_MISSILE_1:
  5239.                         case POW_HOMING_AMMO_1:
  5240.                         case POW_SMARTBOMB_WEAPON:
  5241.                         case POW_MEGA_WEAPON:
  5242.                         case POW_CLOAK:
  5243.                         case POW_INVULNERABILITY:
  5244. #if defined(DXX_BUILD_DESCENT_II)
  5245.                         case POW_SUPER_LASER:
  5246.                         case POW_HELIX_WEAPON:
  5247.                         case POW_PHOENIX_WEAPON:
  5248.                         case POW_OMEGA_WEAPON:
  5249.                         case POW_SMISSILE1_1:
  5250.                         case POW_GUIDED_MISSILE_1:
  5251.                         case POW_MERCURY_MISSILE_1:
  5252.                         case POW_EARTHSHAKER_MISSILE:
  5253.                         case POW_FULL_MAP:
  5254.                         case POW_CONVERTER:
  5255.                         case POW_AMMO_RACK:
  5256.                         case POW_AFTERBURNER:
  5257.                         case POW_HEADLIGHT:
  5258.                         case POW_FLAG_BLUE:
  5259.                         case POW_FLAG_RED:
  5260. #endif
  5261.                                 MultiLevelInv.Current[pid]++;
  5262.                                 break;
  5263.                         case POW_MISSILE_4:
  5264.                         case POW_HOMING_AMMO_4:
  5265. #if defined(DXX_BUILD_DESCENT_II)
  5266.                         case POW_SMISSILE1_4:
  5267.                         case POW_GUIDED_MISSILE_4:
  5268.                         case POW_MERCURY_MISSILE_4:
  5269. #endif
  5270.                                 MultiLevelInv.Current[pid-1] += 4;
  5271.                                 break;
  5272.                         case POW_PROXIMITY_WEAPON:
  5273. #if defined(DXX_BUILD_DESCENT_II)
  5274.                         case POW_SMART_MINE:
  5275. #endif
  5276.                                 MultiLevelInv.Current[pid] += 4; // count the actual bombs
  5277.                                 break;
  5278.                         case POW_VULCAN_AMMO:
  5279.                                 MultiLevelInv.Current[pid] += VULCAN_AMMO_AMOUNT; // count the actual ammo
  5280.                                 break;
  5281.                         default:
  5282.                                 break; // All other items either do not exist or we NEVER want to have them respawn.
  5283.                 }
  5284.         }
  5285. }
  5286. }
  5287.  
  5288. namespace dsx {
  5289. static void MultiLevelInv_CountPlayerInventory()
  5290. {
  5291.         auto &Objects = LevelUniqueObjectState.Objects;
  5292.         auto &vcobjptr = Objects.vcptr;
  5293.         auto &Current = MultiLevelInv.Current;
  5294.                 for (playernum_t i = 0; i < MAX_PLAYERS; i++)
  5295.                 {
  5296.                         if (vcplayerptr(i)->connected != CONNECT_PLAYING)
  5297.                                 continue;
  5298.                 auto &obj = *vcobjptr(vcplayerptr(i)->objnum);
  5299.                 if (obj.type == OBJ_GHOST) // Player is dead. Their items are dropped now.
  5300.                                 continue;
  5301.                 auto &player_info = obj.ctype.player_info;
  5302.                         // NOTE: We do not need to consider Granted Spawn Items here. These are replaced with shields and even if not, the repopulation function will take care of it.
  5303. #if defined(DXX_BUILD_DESCENT_II)
  5304.                         if (player_info.laser_level > MAX_LASER_LEVEL)
  5305.                         {
  5306.                                 /*
  5307.                                  * We do not know exactly how many normal lasers the player collected before going super so assume they have all.
  5308.                                  * This loss possible is insignificant since we have super lasers and normal ones may respawn some time after this player dies.
  5309.                                  */
  5310.                         Current[POW_LASER] += 4;
  5311.                         Current[POW_SUPER_LASER] += player_info.laser_level-MAX_LASER_LEVEL+1; // Laser levels start at 0!
  5312.                         }
  5313.                         else
  5314. #endif
  5315.                         {
  5316.                         Current[POW_LASER] += player_info.laser_level+1; // Laser levels start at 0!
  5317.                         }
  5318.                                                 accumulate_flags_count<player_flags, PLAYER_FLAG> powerup_flags(Current, player_info.powerup_flags);
  5319.                                                 accumulate_flags_count<player_info::primary_weapon_flag_type, unsigned> primary_weapon_flags(Current, player_info.primary_weapon_flags);
  5320.                                                 powerup_flags.process(PLAYER_FLAGS_QUAD_LASERS, POW_QUAD_FIRE);
  5321.                                                 primary_weapon_flags.process(HAS_PRIMARY_FLAG(VULCAN_INDEX), POW_VULCAN_WEAPON);
  5322.                                                 primary_weapon_flags.process(HAS_PRIMARY_FLAG(SPREADFIRE_INDEX), POW_SPREADFIRE_WEAPON);
  5323.                                                 primary_weapon_flags.process(HAS_PRIMARY_FLAG(PLASMA_INDEX), POW_PLASMA_WEAPON);
  5324.                                                 primary_weapon_flags.process(HAS_PRIMARY_FLAG(FUSION_INDEX), POW_FUSION_WEAPON);
  5325.                                                 powerup_flags.process(PLAYER_FLAGS_CLOAKED, POW_CLOAK);
  5326.                                                 powerup_flags.process(PLAYER_FLAGS_INVULNERABLE, POW_INVULNERABILITY);
  5327.                         // NOTE: The following can probably be simplified.
  5328. #if defined(DXX_BUILD_DESCENT_II)
  5329.                                                 primary_weapon_flags.process(HAS_PRIMARY_FLAG(GAUSS_INDEX), POW_GAUSS_WEAPON);
  5330.                                                 primary_weapon_flags.process(HAS_PRIMARY_FLAG(HELIX_INDEX), POW_HELIX_WEAPON);
  5331.                                                 primary_weapon_flags.process(HAS_PRIMARY_FLAG(PHOENIX_INDEX), POW_PHOENIX_WEAPON);
  5332.                                                 primary_weapon_flags.process(HAS_PRIMARY_FLAG(OMEGA_INDEX), POW_OMEGA_WEAPON);
  5333.                                                 powerup_flags.process(PLAYER_FLAGS_MAP_ALL, POW_FULL_MAP);
  5334.                                                 powerup_flags.process(PLAYER_FLAGS_CONVERTER, POW_CONVERTER);
  5335.                                                 powerup_flags.process(PLAYER_FLAGS_AMMO_RACK, POW_AMMO_RACK);
  5336.                                                 powerup_flags.process(PLAYER_FLAGS_AFTERBURNER, POW_AFTERBURNER);
  5337.                                                 powerup_flags.process(PLAYER_FLAGS_HEADLIGHT, POW_HEADLIGHT);
  5338.                         if ((Game_mode & GM_CAPTURE) && (player_info.powerup_flags & PLAYER_FLAGS_FLAG))
  5339.                         {
  5340.                                 ++Current[(get_team(i) == TEAM_RED) ? POW_FLAG_BLUE : POW_FLAG_RED];
  5341.                         }
  5342. #endif
  5343.                 Current[POW_VULCAN_AMMO] += player_info.vulcan_ammo;
  5344.                 Current[POW_MISSILE_1] += player_info.secondary_ammo[CONCUSSION_INDEX];
  5345.                 Current[POW_HOMING_AMMO_1] += player_info.secondary_ammo[HOMING_INDEX];
  5346.                 Current[POW_PROXIMITY_WEAPON] += player_info.secondary_ammo[PROXIMITY_INDEX];
  5347.                 Current[POW_SMARTBOMB_WEAPON] += player_info.secondary_ammo[SMART_INDEX];
  5348.                 Current[POW_MEGA_WEAPON] += player_info.secondary_ammo[MEGA_INDEX];
  5349. #if defined(DXX_BUILD_DESCENT_II)
  5350.                 Current[POW_SMISSILE1_1] += player_info.secondary_ammo[SMISSILE1_INDEX];
  5351.                 Current[POW_GUIDED_MISSILE_1] += player_info.secondary_ammo[GUIDED_INDEX];
  5352.                 Current[POW_SMART_MINE] += player_info.secondary_ammo[SMART_MINE_INDEX];
  5353.                 Current[POW_MERCURY_MISSILE_1] += player_info.secondary_ammo[SMISSILE4_INDEX];
  5354.                 Current[POW_EARTHSHAKER_MISSILE] += player_info.secondary_ammo[SMISSILE5_INDEX];
  5355. #endif
  5356.                 }
  5357. #if defined(DXX_BUILD_DESCENT_II)
  5358.                 if (Game_mode & GM_MULTI_ROBOTS) // Add (possible) thief inventory
  5359.                 {
  5360.                         range_for (auto &i, LevelUniqueObjectState.ThiefState.Stolen_items)
  5361.                         {
  5362.                                                 if (i >= Current.size() || i == POW_ENERGY || i == POW_SHIELD_BOOST)
  5363.                                         continue;
  5364.                                                 auto &c = Current[i];
  5365.                                 // NOTE: We don't need to consider vulcan ammo or 4pack items as the thief should not steal those items.
  5366.                                                         if (i == POW_PROXIMITY_WEAPON || i == POW_SMART_MINE)
  5367.                                                                 c += 4;
  5368.                                 else
  5369.                                                                 ++c;
  5370.                         }
  5371.                 }
  5372. #endif
  5373. }
  5374. }
  5375.  
  5376. void MultiLevelInv_InitializeCount()
  5377. {
  5378.         MultiLevelInv_CountLevelPowerups();
  5379.         MultiLevelInv.Initial = MultiLevelInv.Current;
  5380.         MultiLevelInv.RespawnTimer = {};
  5381. }
  5382.  
  5383. void MultiLevelInv_Recount()
  5384. {
  5385.         MultiLevelInv_CountLevelPowerups();
  5386.         MultiLevelInv_CountPlayerInventory();
  5387. }
  5388.  
  5389. // Takes a powerup type and checks if we are allowed to spawn it.
  5390. namespace dsx {
  5391. bool MultiLevelInv_AllowSpawn(powerup_type_t powerup_type)
  5392. {
  5393.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  5394.         if ((Game_mode & GM_MULTI_COOP) || LevelUniqueControlCenterState.Control_center_destroyed || Network_status != NETSTAT_PLAYING)
  5395.                 return 0;
  5396.  
  5397.         int req_amount = 1; // required amount of item to drop a powerup.
  5398.  
  5399.         if (powerup_type == POW_VULCAN_AMMO)
  5400.                 req_amount = VULCAN_AMMO_AMOUNT;
  5401.         else if (powerup_type == POW_PROXIMITY_WEAPON
  5402. #if defined(DXX_BUILD_DESCENT_II)
  5403.                 || powerup_type == POW_SMART_MINE
  5404. #endif
  5405.         )
  5406.                 req_amount = 4;
  5407.  
  5408.         if (MultiLevelInv.Initial[powerup_type] == 0 || MultiLevelInv.Current[powerup_type] > MultiLevelInv.Initial[powerup_type]) // Item does not exist in level or we have too many.
  5409.                 return 0;
  5410.         else if (MultiLevelInv.Initial[powerup_type] - MultiLevelInv.Current[powerup_type] >= req_amount)
  5411.                 return 1;
  5412.         return 0;
  5413. }
  5414. }
  5415.  
  5416. // Repopulate the level with missing items.
  5417. void MultiLevelInv_Repopulate(fix frequency)
  5418. {
  5419.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  5420.         if (!multi_i_am_master() || (Game_mode & GM_MULTI_COOP) || LevelUniqueControlCenterState.Control_center_destroyed)
  5421.                 return;
  5422.  
  5423.         MultiLevelInv_Recount(); // recount current items
  5424.         for (unsigned i = 0; i < MAX_POWERUP_TYPES; i++)
  5425.         {
  5426.                 if (MultiLevelInv_AllowSpawn(static_cast<powerup_type_t>(i)))
  5427.                         MultiLevelInv.RespawnTimer[i] += frequency;
  5428.                 else
  5429.                         MultiLevelInv.RespawnTimer[i] = 0;
  5430.  
  5431.                 if (MultiLevelInv.RespawnTimer[i] >= F1_0*2)
  5432.                 {
  5433.                         con_printf(CON_VERBOSE, "MultiLevelInv_Repopulate type: %i - Init: %i Cur: %i", i, MultiLevelInv.Initial[i], MultiLevelInv.Current[i]);
  5434.                         MultiLevelInv.RespawnTimer[i] = 0;
  5435.                         maybe_drop_net_powerup(static_cast<powerup_type_t>(i), 0, 1);
  5436.                 }
  5437.         }
  5438. }
  5439.  
  5440. namespace dsx {
  5441.  
  5442. #if defined(DXX_BUILD_DESCENT_II)
  5443. ///
  5444. /// CODE TO LOAD HOARD DATA
  5445. ///
  5446.  
  5447. namespace {
  5448.  
  5449. class hoard_resources_type
  5450. {
  5451.         static constexpr auto invalid_bm_idx = std::integral_constant<int, -1>{};
  5452.         static constexpr auto invalid_snd_idx = std::integral_constant<unsigned, ~0u>{};
  5453. public:
  5454.         int bm_idx = invalid_bm_idx;
  5455.         unsigned snd_idx = invalid_snd_idx;
  5456.         void reset();
  5457.         ~hoard_resources_type()
  5458.         {
  5459.                 reset();
  5460.         }
  5461. };
  5462.  
  5463. constexpr std::integral_constant<int, -1> hoard_resources_type::invalid_bm_idx;
  5464. constexpr std::integral_constant<unsigned, ~0u> hoard_resources_type::invalid_snd_idx;
  5465.  
  5466. }
  5467.  
  5468. static hoard_resources_type hoard_resources;
  5469.  
  5470. int HoardEquipped()
  5471. {
  5472.         static int checked=-1;
  5473.  
  5474.         if (unlikely(checked == -1))
  5475.         {
  5476.                 checked = PHYSFSX_exists("hoard.ham",1);
  5477.         }
  5478.         return (checked);
  5479. }
  5480.  
  5481. std::array<grs_main_bitmap, 2> Orb_icons;
  5482.  
  5483. void hoard_resources_type::reset()
  5484. {
  5485.         if (bm_idx != invalid_bm_idx)
  5486.                 d_free(GameBitmaps[std::exchange(bm_idx, invalid_bm_idx)].bm_mdata);
  5487.         if (snd_idx != invalid_snd_idx)
  5488.         {
  5489.                 const auto idx = std::exchange(snd_idx, invalid_snd_idx);
  5490.                 range_for (auto &i, partial_range(GameSounds, idx, idx + 4))
  5491.                         d_free(i.data);
  5492.         }
  5493.         range_for (auto &i, Orb_icons)
  5494.                 i.reset();
  5495. }
  5496.  
  5497. void init_hoard_data(d_vclip_array &Vclip)
  5498. {
  5499.         auto &Effects = LevelUniqueEffectsClipState.Effects;
  5500.         hoard_resources.reset();
  5501.         static int orb_vclip;
  5502.         unsigned n_orb_frames,n_goal_frames;
  5503.         int orb_w,orb_h;
  5504.         palette_array_t palette;
  5505.         uint8_t *bitmap_data1;
  5506.         int save_pos;
  5507.         int bitmap_num = hoard_resources.bm_idx = Num_bitmap_files;
  5508.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  5509.  
  5510.         auto ifile = PHYSFSX_openReadBuffered("hoard.ham");
  5511.         if (!ifile)
  5512.                 Error("can't open <hoard.ham>");
  5513.  
  5514.         n_orb_frames = PHYSFSX_readShort(ifile);
  5515.         orb_w = PHYSFSX_readShort(ifile);
  5516.         orb_h = PHYSFSX_readShort(ifile);
  5517.         save_pos = PHYSFS_tell(ifile);
  5518.         PHYSFSX_fseek(ifile,sizeof(palette)+n_orb_frames*orb_w*orb_h,SEEK_CUR);
  5519.         n_goal_frames = PHYSFSX_readShort(ifile);
  5520.         PHYSFSX_fseek(ifile,save_pos,SEEK_SET);
  5521.  
  5522.         //Allocate memory for bitmaps
  5523.         MALLOC( bitmap_data1, ubyte, n_orb_frames*orb_w*orb_h + n_goal_frames*64*64 );
  5524.  
  5525.         //Create orb vclip
  5526.         orb_vclip = Num_vclips++;
  5527.         assert(Num_vclips <= Vclip.size());
  5528.         Vclip[orb_vclip].play_time = F1_0/2;
  5529.         Vclip[orb_vclip].num_frames = n_orb_frames;
  5530.         Vclip[orb_vclip].frame_time = Vclip[orb_vclip].play_time / Vclip[orb_vclip].num_frames;
  5531.         Vclip[orb_vclip].flags = 0;
  5532.         Vclip[orb_vclip].sound_num = -1;
  5533.         Vclip[orb_vclip].light_value = F1_0;
  5534.         range_for (auto &i, partial_range(Vclip[orb_vclip].frames, n_orb_frames))
  5535.         {
  5536.                 i.index = bitmap_num;
  5537.                 gr_init_bitmap(GameBitmaps[bitmap_num],bm_mode::linear,0,0,orb_w,orb_h,orb_w,bitmap_data1);
  5538.                 gr_set_transparent(GameBitmaps[bitmap_num], 1);
  5539.                 bitmap_data1 += orb_w*orb_h;
  5540.                 bitmap_num++;
  5541.                 Assert(bitmap_num < MAX_BITMAP_FILES);
  5542.         }
  5543.  
  5544.         //Create obj powerup
  5545.         Powerup_info[POW_HOARD_ORB].vclip_num = orb_vclip;
  5546.         Powerup_info[POW_HOARD_ORB].hit_sound = -1; //Powerup_info[POW_SHIELD_BOOST].hit_sound;
  5547.         Powerup_info[POW_HOARD_ORB].size = Powerup_info[POW_SHIELD_BOOST].size;
  5548.         Powerup_info[POW_HOARD_ORB].light = Powerup_info[POW_SHIELD_BOOST].light;
  5549.  
  5550.         //Create orb goal wall effect
  5551.         const auto goal_eclip = Num_effects++;
  5552.         assert(goal_eclip < Effects.size());
  5553.         Effects[goal_eclip] = Effects[94];        //copy from blue goal
  5554.         Effects[goal_eclip].changing_wall_texture = NumTextures;
  5555.         Effects[goal_eclip].vc.num_frames=n_goal_frames;
  5556.  
  5557.         TmapInfo[NumTextures] = find_required_goal_texture(TMI_GOAL_BLUE);
  5558.         TmapInfo[NumTextures].eclip_num = goal_eclip;
  5559.         TmapInfo[NumTextures].flags = TMI_GOAL_HOARD;
  5560.         NumTextures++;
  5561.         Assert(NumTextures < MAX_TEXTURES);
  5562.         range_for (auto &i, partial_range(Effects[goal_eclip].vc.frames, n_goal_frames))
  5563.         {
  5564.                 i.index = bitmap_num;
  5565.                 gr_init_bitmap(GameBitmaps[bitmap_num],bm_mode::linear,0,0,64,64,64,bitmap_data1);
  5566.                 bitmap_data1 += 64*64;
  5567.                 bitmap_num++;
  5568.                 Assert(bitmap_num < MAX_BITMAP_FILES);
  5569.         }
  5570.  
  5571.         //Load and remap bitmap data for orb
  5572.         PHYSFS_read(ifile,&palette[0],sizeof(palette[0]),palette.size());
  5573.         range_for (auto &i, partial_const_range(Vclip[orb_vclip].frames, n_orb_frames))
  5574.         {
  5575.                 grs_bitmap *bm = &GameBitmaps[i.index];
  5576.                 PHYSFS_read(ifile,bm->get_bitmap_data(),1,orb_w*orb_h);
  5577.                 gr_remap_bitmap_good(*bm, palette, 255, -1);
  5578.         }
  5579.  
  5580.         //Load and remap bitmap data for goal texture
  5581.         PHYSFSX_readShort(ifile);        //skip frame count
  5582.         PHYSFS_read(ifile,&palette[0],sizeof(palette[0]),palette.size());
  5583.         range_for (auto &i, partial_const_range(Effects[goal_eclip].vc.frames, n_goal_frames))
  5584.         {
  5585.                 grs_bitmap *bm = &GameBitmaps[i.index];
  5586.                 PHYSFS_read(ifile,bm->get_bitmap_data(),1,64*64);
  5587.                 gr_remap_bitmap_good(*bm, palette, 255, -1);
  5588.         }
  5589.  
  5590.         //Load and remap bitmap data for HUD icons
  5591.         range_for (auto &i, Orb_icons)
  5592.         {
  5593.                 const unsigned icon_w = PHYSFSX_readShort(ifile);
  5594.                 if (icon_w > 32)
  5595.                         return;
  5596.                 const unsigned icon_h = PHYSFSX_readShort(ifile);
  5597.                 if (icon_h > 32)
  5598.                         return;
  5599.                 const unsigned extent = icon_w * icon_h;
  5600.                 RAIIdmem<uint8_t[]> bitmap_data2;
  5601.                 MALLOC(bitmap_data2, uint8_t, extent);
  5602.                 PHYSFS_read(ifile,&palette[0],sizeof(palette[0]),palette.size());
  5603.                 PHYSFS_read(ifile, bitmap_data2.get(), 1, extent);
  5604.                 gr_init_main_bitmap(i, bm_mode::linear, 0, 0, icon_w, icon_h, icon_w, std::move(bitmap_data2));
  5605.                 gr_set_transparent(i, 1);
  5606.                 gr_remap_bitmap_good(i, palette, 255, -1);
  5607.         }
  5608.  
  5609.         //Load sounds for orb game
  5610.         hoard_resources.snd_idx = Num_sound_files;
  5611.         range_for (const unsigned i, xrange(4u))
  5612.         {
  5613.                 int len;
  5614.  
  5615.                 len = PHYSFSX_readInt(ifile);        //get 11k len
  5616.  
  5617.                 if (GameArg.SndDigiSampleRate == SAMPLE_RATE_22K) {
  5618.                         PHYSFSX_fseek(ifile,len,SEEK_CUR);     //skip over 11k sample
  5619.                         len = PHYSFSX_readInt(ifile);    //get 22k len
  5620.                 }
  5621.  
  5622.                 GameSounds[Num_sound_files+i].length = len;
  5623.                 MALLOC(GameSounds[Num_sound_files+i].data, ubyte, len);
  5624.                 PHYSFS_read(ifile,GameSounds[Num_sound_files+i].data,1,len);
  5625.  
  5626.                 if (GameArg.SndDigiSampleRate == SAMPLE_RATE_11K) {
  5627.                         len = PHYSFSX_readInt(ifile);    //get 22k len
  5628.                         PHYSFSX_fseek(ifile,len,SEEK_CUR);     //skip over 22k sample
  5629.                 }
  5630.  
  5631.                 Sounds[SOUND_YOU_GOT_ORB+i] = Num_sound_files+i;
  5632.                 AltSounds[SOUND_YOU_GOT_ORB+i] = Sounds[SOUND_YOU_GOT_ORB+i];
  5633.         }
  5634. }
  5635.  
  5636. #if DXX_USE_EDITOR
  5637. void save_hoard_data(void)
  5638. {
  5639.         grs_bitmap icon;
  5640.         unsigned nframes;
  5641.         palette_array_t palette;
  5642.         int iff_error;
  5643.         static const char sounds[][13] = {"selforb.raw","selforb.r22",          //SOUND_YOU_GOT_ORB
  5644.                                 "teamorb.raw","teamorb.r22",    //SOUND_FRIEND_GOT_ORB
  5645.                                 "enemyorb.raw","enemyorb.r22",  //SOUND_OPPONENT_GOT_ORB
  5646.                                 "OPSCORE1.raw","OPSCORE1.r22"}; //SOUND_OPPONENT_HAS_SCORED
  5647.                
  5648.         auto ofile = PHYSFSX_openWriteBuffered("hoard.ham");
  5649.  
  5650.         std::array<std::unique_ptr<grs_main_bitmap>, MAX_BITMAPS_PER_BRUSH> bm;
  5651.         iff_error = iff_read_animbrush("orb.abm",bm,&nframes,palette);
  5652.         Assert(iff_error == IFF_NO_ERROR);
  5653.         PHYSFS_writeULE16(ofile, nframes);
  5654.         PHYSFS_writeULE16(ofile, bm[0]->bm_w);
  5655.         PHYSFS_writeULE16(ofile, bm[0]->bm_h);
  5656.         PHYSFS_write(ofile, &palette[0], sizeof(palette[0]), palette.size());
  5657.         range_for (auto &i, partial_const_range(bm, nframes))
  5658.                 PHYSFS_write(ofile, i->bm_data, i->bm_w * i->bm_h, 1);
  5659.  
  5660.         iff_error = iff_read_animbrush("orbgoal.abm",bm,&nframes,palette);
  5661.         Assert(iff_error == IFF_NO_ERROR);
  5662.         Assert(bm[0]->bm_w == 64 && bm[0]->bm_h == 64);
  5663.         PHYSFS_writeULE16(ofile, nframes);
  5664.         PHYSFS_write(ofile, &palette[0], sizeof(palette[0]), palette.size());
  5665.         range_for (auto &i, partial_const_range(bm, nframes))
  5666.                 PHYSFS_write(ofile, i->bm_data, i->bm_w * i->bm_h, 1);
  5667.  
  5668.         range_for (const unsigned i, xrange(2u))
  5669.         {
  5670.                 iff_error = iff_read_bitmap(i ? "orbb.bbm" : "orb.bbm", icon, &palette);
  5671.                 Assert(iff_error == IFF_NO_ERROR);
  5672.                 PHYSFS_writeULE16(ofile, icon.bm_w);
  5673.                 PHYSFS_writeULE16(ofile, icon.bm_h);
  5674.                 PHYSFS_write(ofile, &palette[0], sizeof(palette[0]), palette.size());
  5675.                 PHYSFS_write(ofile, icon.bm_data, icon.bm_w*icon.bm_h, 1);
  5676.         }
  5677.         (void)iff_error;
  5678.                
  5679.         range_for (auto &i, sounds)
  5680.                 if (RAIIPHYSFS_File ifile{PHYSFS_openRead(i)})
  5681.         {
  5682.                 int size;
  5683.                 size = PHYSFS_fileLength(ifile);
  5684.                 RAIIdmem<uint8_t[]> buf;
  5685.                 MALLOC(buf, uint8_t[], size);
  5686.                 PHYSFS_read(ifile, buf, size, 1);
  5687.                 PHYSFS_writeULE32(ofile, size);
  5688.                 PHYSFS_write(ofile, buf, size, 1);
  5689.         }
  5690. }
  5691. #endif
  5692. #endif
  5693.  
  5694. static void multi_process_data(const playernum_t pnum, const ubyte *buf, const uint_fast32_t type)
  5695. {
  5696.         auto &Objects = LevelUniqueObjectState.Objects;
  5697.         auto &imobjptridx = Objects.imptridx;
  5698.         auto &vmobjptr = Objects.vmptr;
  5699.         auto &vmobjptridx = Objects.vmptridx;
  5700.         // Take an entire message (that has already been checked for validity,
  5701.         // if necessary) and act on it.
  5702.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  5703.         auto &vmwallptr = Walls.vmptr;
  5704.         switch (static_cast<multiplayer_command_t>(type))
  5705.         {
  5706.                 case MULTI_POSITION:
  5707.                         multi_do_position(vmobjptridx, pnum, buf);
  5708.                         break;
  5709.                 case MULTI_REAPPEAR:
  5710.                         multi_do_reappear(pnum, buf); break;
  5711.                 case MULTI_FIRE:
  5712.                 case MULTI_FIRE_TRACK:
  5713.                 case MULTI_FIRE_BOMB:
  5714.                         multi_do_fire(vmobjptridx, pnum, buf);
  5715.                         break;
  5716.                 case MULTI_REMOVE_OBJECT:
  5717.                         multi_do_remobj(vmobjptr, buf);
  5718.                         break;
  5719.                 case MULTI_PLAYER_DERES:
  5720.                         multi_do_player_deres(Objects, pnum, buf);
  5721.                         break;
  5722.                 case MULTI_MESSAGE:
  5723.                         multi_do_message(buf); break;
  5724.                 case MULTI_QUIT:
  5725.                         multi_do_quit(buf); break;
  5726.                 case MULTI_CONTROLCEN:
  5727.                         multi_do_controlcen_destroy(imobjptridx, buf);
  5728.                         break;
  5729.                 case MULTI_DROP_WEAPON:
  5730.                         multi_do_drop_weapon(vmobjptr, pnum, buf);
  5731.                         break;
  5732. #if defined(DXX_BUILD_DESCENT_II)
  5733.                 case MULTI_VULWPN_AMMO_ADJ:
  5734.                         multi_do_vulcan_weapon_ammo_adjust(vmobjptr, buf);
  5735.                         break;
  5736.                 case MULTI_SOUND_FUNCTION:
  5737.                         multi_do_sound_function(pnum, buf); break;
  5738.                 case MULTI_MARKER:
  5739.                         multi_do_drop_marker(Objects, vmsegptridx, pnum, buf);
  5740.                         break;
  5741.                 case MULTI_DROP_FLAG:
  5742.                         multi_do_drop_flag(pnum, buf); break;
  5743.                 case MULTI_GUIDED:
  5744.                         multi_do_guided(LevelUniqueObjectState, pnum, buf);
  5745.                         break;
  5746.                 case MULTI_STOLEN_ITEMS:
  5747.                         multi_do_stolen_items(buf); break;
  5748.                 case MULTI_WALL_STATUS:
  5749.                         multi_do_wall_status(vmwallptr, buf);
  5750.                         break;
  5751.                 case MULTI_SEISMIC:
  5752.                         multi_do_seismic (buf); break;
  5753.                 case MULTI_LIGHT:
  5754.                         multi_do_light (buf); break;
  5755. #endif
  5756.                 case MULTI_ENDLEVEL_START:
  5757.                         multi_do_escape(vmobjptridx, buf);
  5758.                         break;
  5759.                 case MULTI_CLOAK:
  5760.                         multi_do_cloak(vmobjptr, pnum);
  5761.                         break;
  5762.                 case MULTI_DECLOAK:
  5763.                         multi_do_decloak(pnum); break;
  5764.                 case MULTI_DOOR_OPEN:
  5765.                         multi_do_door_open(vmwallptr, buf);
  5766.                         break;
  5767.                 case MULTI_CREATE_EXPLOSION:
  5768.                         multi_do_create_explosion(vmobjptridx, pnum);
  5769.                         break;
  5770.                 case MULTI_CONTROLCEN_FIRE:
  5771.                         multi_do_controlcen_fire(buf); break;
  5772.                 case MULTI_CREATE_POWERUP:
  5773.                         multi_do_create_powerup(vmobjptr, vmsegptridx, pnum, buf);
  5774.                         break;
  5775.                 case MULTI_PLAY_SOUND:
  5776.                         multi_do_play_sound(Objects, pnum, buf);
  5777.                         break;
  5778. #if defined(DXX_BUILD_DESCENT_II)
  5779.                 case MULTI_CAPTURE_BONUS:
  5780.                         multi_do_capture_bonus(pnum); break;
  5781.                 case MULTI_ORB_BONUS:
  5782.                         multi_do_orb_bonus(pnum, buf); break;
  5783.                 case MULTI_GOT_FLAG:
  5784.                         multi_do_got_flag(pnum); break;
  5785.                 case MULTI_GOT_ORB:
  5786.                         multi_do_got_orb(pnum); break;
  5787.                 case MULTI_FINISH_GAME:
  5788.                         multi_do_finish_game(buf); break;  // do this one regardless of endsequence
  5789. #endif
  5790.                 case MULTI_RANK:
  5791.                         multi_do_ranking (pnum, buf); break;
  5792.                 case MULTI_ROBOT_CLAIM:
  5793.                         multi_do_claim_robot(pnum, buf); break;
  5794.                 case MULTI_ROBOT_POSITION:
  5795.                         multi_do_robot_position(pnum, buf); break;
  5796.                 case MULTI_ROBOT_EXPLODE:
  5797.                         multi_do_robot_explode(buf); break;
  5798.                 case MULTI_ROBOT_RELEASE:
  5799.                         multi_do_release_robot(pnum, buf); break;
  5800.                 case MULTI_ROBOT_FIRE:
  5801.                         multi_do_robot_fire(buf); break;
  5802.                 case MULTI_SCORE:
  5803.                         multi_do_score(vmobjptr, pnum, buf);
  5804.                         break;
  5805.                 case MULTI_CREATE_ROBOT:
  5806.                         multi_do_create_robot(Vclip, pnum, buf); break;
  5807.                 case MULTI_TRIGGER:
  5808.                         multi_do_trigger(pnum, buf); break;
  5809. #if defined(DXX_BUILD_DESCENT_II)
  5810.                 case MULTI_START_TRIGGER:
  5811.                         multi_do_start_trigger(buf); break;
  5812.                 case MULTI_EFFECT_BLOWUP:
  5813.                         multi_do_effect_blowup(pnum, buf); break;
  5814.                 case MULTI_FLAGS:
  5815.                         multi_do_flags(vmobjptr, pnum, buf);
  5816.                         break;
  5817.                 case MULTI_DROP_BLOB:
  5818.                         multi_do_drop_blob(vmobjptr, pnum);
  5819.                         break;
  5820.                 case MULTI_UPDATE_BUDDY_STATE:
  5821.                         multi_recv_escort_goal(LevelUniqueObjectState.BuddyState, buf);
  5822.                         break;
  5823. #endif
  5824.                 case MULTI_BOSS_TELEPORT:
  5825.                         multi_do_boss_teleport(Vclip, pnum, buf); break;
  5826.                 case MULTI_BOSS_CLOAK:
  5827.                         multi_do_boss_cloak(buf); break;
  5828.                 case MULTI_BOSS_START_GATE:
  5829.                         multi_do_boss_start_gate(buf); break;
  5830.                 case MULTI_BOSS_STOP_GATE:
  5831.                         multi_do_boss_stop_gate(buf); break;
  5832.                 case MULTI_BOSS_CREATE_ROBOT:
  5833.                         multi_do_boss_create_robot(pnum, buf); break;
  5834.                 case MULTI_CREATE_ROBOT_POWERUPS:
  5835.                         multi_do_create_robot_powerups(pnum, buf); break;
  5836.                 case MULTI_HOSTAGE_DOOR:
  5837.                         multi_do_hostage_door_status(vmsegptridx, Walls, buf);
  5838.                         break;
  5839.                 case MULTI_SAVE_GAME:
  5840.                         multi_do_save_game(buf); break;
  5841.                 case MULTI_RESTORE_GAME:
  5842.                         multi_do_restore_game(buf); break;
  5843.                 case MULTI_HEARTBEAT:
  5844.                         multi_do_heartbeat (buf); break;
  5845.                 case MULTI_KILLGOALS:
  5846.                         multi_do_kill_goal_counts(vmobjptr, buf);
  5847.                         break;
  5848.                 case MULTI_DO_BOUNTY:
  5849.                         multi_do_bounty( buf ); break;
  5850.                 case MULTI_TYPING_STATE:
  5851.                         multi_do_msgsend_state( buf ); break;
  5852.                 case MULTI_GMODE_UPDATE:
  5853.                         multi_do_gmode_update( buf ); break;
  5854.                 case MULTI_KILL_HOST:
  5855.                         multi_do_kill(Objects, buf);
  5856.                         break;
  5857.                 case MULTI_KILL_CLIENT:
  5858.                         multi_do_kill(Objects, buf);
  5859.                         break;
  5860.                 case MULTI_PLAYER_INV:
  5861.                         multi_do_player_inventory( pnum, buf ); break;
  5862.         }
  5863. }
  5864.  
  5865. // Following functions convert object to object_rw and back.
  5866. // turn object to object_rw for sending
  5867. void multi_object_to_object_rw(object &obj, object_rw *obj_rw)
  5868. {
  5869.         /* Avoid leaking any uninitialized bytes onto the network.  Some of
  5870.          * the unions are incompletely initialized for some branches.
  5871.          *
  5872.          * For poison enabled builds, poison any uninitialized fields.
  5873.          * Everything that the peer should read will be initialized by the
  5874.          * end of this function.
  5875.          */
  5876.         *obj_rw = {};
  5877.         DXX_POISON_DEFINED_VAR(*obj_rw, 0xfd);
  5878.         obj_rw->signature     = obj.signature.get();
  5879.         obj_rw->type          = obj.type;
  5880.         obj_rw->id            = obj.id;
  5881.         obj_rw->control_type  = obj.control_type;
  5882.         obj_rw->movement_type = obj.movement_type;
  5883.         obj_rw->render_type   = obj.render_type;
  5884.         obj_rw->flags         = obj.flags;
  5885.         obj_rw->segnum        = obj.segnum;
  5886.         obj_rw->pos         = obj.pos;
  5887.         obj_rw->orient = obj.orient;
  5888.         obj_rw->size          = obj.size;
  5889.         obj_rw->shields       = obj.shields;
  5890.         obj_rw->last_pos    = obj.pos;
  5891.         obj_rw->contains_type = obj.contains_type;
  5892.         obj_rw->contains_id   = obj.contains_id;
  5893.         obj_rw->contains_count= obj.contains_count;
  5894.         obj_rw->matcen_creator= obj.matcen_creator;
  5895.         obj_rw->lifeleft      = obj.lifeleft;
  5896.        
  5897.         switch (obj_rw->movement_type)
  5898.         {
  5899.                 case MT_PHYSICS:
  5900.                         obj_rw->mtype.phys_info.velocity.x  = obj.mtype.phys_info.velocity.x;
  5901.                         obj_rw->mtype.phys_info.velocity.y  = obj.mtype.phys_info.velocity.y;
  5902.                         obj_rw->mtype.phys_info.velocity.z  = obj.mtype.phys_info.velocity.z;
  5903.                         obj_rw->mtype.phys_info.thrust.x    = obj.mtype.phys_info.thrust.x;
  5904.                         obj_rw->mtype.phys_info.thrust.y    = obj.mtype.phys_info.thrust.y;
  5905.                         obj_rw->mtype.phys_info.thrust.z    = obj.mtype.phys_info.thrust.z;
  5906.                         obj_rw->mtype.phys_info.mass        = obj.mtype.phys_info.mass;
  5907.                         obj_rw->mtype.phys_info.drag        = obj.mtype.phys_info.drag;
  5908.                         obj_rw->mtype.phys_info.rotvel.x    = obj.mtype.phys_info.rotvel.x;
  5909.                         obj_rw->mtype.phys_info.rotvel.y    = obj.mtype.phys_info.rotvel.y;
  5910.                         obj_rw->mtype.phys_info.rotvel.z    = obj.mtype.phys_info.rotvel.z;
  5911.                         obj_rw->mtype.phys_info.rotthrust.x = obj.mtype.phys_info.rotthrust.x;
  5912.                         obj_rw->mtype.phys_info.rotthrust.y = obj.mtype.phys_info.rotthrust.y;
  5913.                         obj_rw->mtype.phys_info.rotthrust.z = obj.mtype.phys_info.rotthrust.z;
  5914.                         obj_rw->mtype.phys_info.turnroll    = obj.mtype.phys_info.turnroll;
  5915.                         obj_rw->mtype.phys_info.flags       = obj.mtype.phys_info.flags;
  5916.                         break;
  5917.                        
  5918.                 case MT_SPINNING:
  5919.                         obj_rw->mtype.spin_rate.x = obj.mtype.spin_rate.x;
  5920.                         obj_rw->mtype.spin_rate.y = obj.mtype.spin_rate.y;
  5921.                         obj_rw->mtype.spin_rate.z = obj.mtype.spin_rate.z;
  5922.                         break;
  5923.         }
  5924.        
  5925.         switch (obj_rw->control_type)
  5926.         {
  5927.                 case CT_WEAPON:
  5928.                         obj_rw->ctype.laser_info.parent_type      = obj.ctype.laser_info.parent_type;
  5929.                         obj_rw->ctype.laser_info.parent_num       = obj.ctype.laser_info.parent_num;
  5930.                         obj_rw->ctype.laser_info.parent_signature = obj.ctype.laser_info.parent_signature.get();
  5931.                         if (obj.ctype.laser_info.creation_time - GameTime64 < F1_0*(-18000))
  5932.                                 obj_rw->ctype.laser_info.creation_time = F1_0*(-18000);
  5933.                         else
  5934.                                 obj_rw->ctype.laser_info.creation_time = obj.ctype.laser_info.creation_time - GameTime64;
  5935.                         obj_rw->ctype.laser_info.last_hitobj      = obj.ctype.laser_info.get_last_hitobj();
  5936.                         obj_rw->ctype.laser_info.track_goal       = obj.ctype.laser_info.track_goal;
  5937.                         obj_rw->ctype.laser_info.multiplier       = obj.ctype.laser_info.multiplier;
  5938.                         break;
  5939.                        
  5940.                 case CT_EXPLOSION:
  5941.                         obj_rw->ctype.expl_info.spawn_time    = obj.ctype.expl_info.spawn_time;
  5942.                         obj_rw->ctype.expl_info.delete_time   = obj.ctype.expl_info.delete_time;
  5943.                         obj_rw->ctype.expl_info.delete_objnum = obj.ctype.expl_info.delete_objnum;
  5944.                         obj_rw->ctype.expl_info.attach_parent = obj.ctype.expl_info.attach_parent;
  5945.                         obj_rw->ctype.expl_info.prev_attach   = obj.ctype.expl_info.prev_attach;
  5946.                         obj_rw->ctype.expl_info.next_attach   = obj.ctype.expl_info.next_attach;
  5947.                         break;
  5948.                        
  5949.                 case CT_AI:
  5950.                 {
  5951.                         int i;
  5952.                         obj_rw->ctype.ai_info.behavior               = static_cast<uint8_t>(obj.ctype.ai_info.behavior);
  5953.                         for (i = 0; i < MAX_AI_FLAGS; i++)
  5954.                                 obj_rw->ctype.ai_info.flags[i]       = obj.ctype.ai_info.flags[i];
  5955.                         obj_rw->ctype.ai_info.hide_segment           = obj.ctype.ai_info.hide_segment;
  5956.                         obj_rw->ctype.ai_info.hide_index             = obj.ctype.ai_info.hide_index;
  5957.                         obj_rw->ctype.ai_info.path_length            = obj.ctype.ai_info.path_length;
  5958.                         obj_rw->ctype.ai_info.cur_path_index         = obj.ctype.ai_info.cur_path_index;
  5959.                         obj_rw->ctype.ai_info.danger_laser_num       = obj.ctype.ai_info.danger_laser_num;
  5960.                         if (obj.ctype.ai_info.danger_laser_num != object_none)
  5961.                                 obj_rw->ctype.ai_info.danger_laser_signature = obj.ctype.ai_info.danger_laser_signature.get();
  5962.                         else
  5963.                                 obj_rw->ctype.ai_info.danger_laser_signature = 0;
  5964. #if defined(DXX_BUILD_DESCENT_I)
  5965.                         obj_rw->ctype.ai_info.follow_path_start_seg  = segment_none;
  5966.                         obj_rw->ctype.ai_info.follow_path_end_seg    = segment_none;
  5967. #elif defined(DXX_BUILD_DESCENT_II)
  5968.                         obj_rw->ctype.ai_info.dying_sound_playing    = obj.ctype.ai_info.dying_sound_playing;
  5969.                         if (obj.ctype.ai_info.dying_start_time == 0) // if bot not dead, anything but 0 will kill it
  5970.                                 obj_rw->ctype.ai_info.dying_start_time = 0;
  5971.                         else
  5972.                                 obj_rw->ctype.ai_info.dying_start_time = obj.ctype.ai_info.dying_start_time - GameTime64;
  5973. #endif
  5974.                         break;
  5975.                 }
  5976.                        
  5977.                 case CT_LIGHT:
  5978.                         obj_rw->ctype.light_info.intensity = obj.ctype.light_info.intensity;
  5979.                         break;
  5980.                        
  5981.                 case CT_POWERUP:
  5982.                         obj_rw->ctype.powerup_info.count         = obj.ctype.powerup_info.count;
  5983. #if defined(DXX_BUILD_DESCENT_II)
  5984.                         if (obj.ctype.powerup_info.creation_time - GameTime64 < F1_0*(-18000))
  5985.                                 obj_rw->ctype.powerup_info.creation_time = F1_0*(-18000);
  5986.                         else
  5987.                                 obj_rw->ctype.powerup_info.creation_time = obj.ctype.powerup_info.creation_time - GameTime64;
  5988.                         obj_rw->ctype.powerup_info.flags         = obj.ctype.powerup_info.flags;
  5989. #endif
  5990.                         break;
  5991.         }
  5992.        
  5993.         switch (obj_rw->render_type)
  5994.         {
  5995.                 case RT_MORPH:
  5996.                 case RT_POLYOBJ:
  5997.                 case RT_NONE: // HACK below
  5998.                 {
  5999.                         int i;
  6000.                         if (obj.render_type == RT_NONE && obj.type != OBJ_GHOST) // HACK: when a player is dead or not connected yet, clients still expect to get polyobj data - even if render_type == RT_NONE at this time.
  6001.                                 break;
  6002.                         obj_rw->rtype.pobj_info.model_num                = obj.rtype.pobj_info.model_num;
  6003.                         for (i=0;i<MAX_SUBMODELS;i++)
  6004.                         {
  6005.                                 obj_rw->rtype.pobj_info.anim_angles[i].p = obj.rtype.pobj_info.anim_angles[i].p;
  6006.                                 obj_rw->rtype.pobj_info.anim_angles[i].b = obj.rtype.pobj_info.anim_angles[i].b;
  6007.                                 obj_rw->rtype.pobj_info.anim_angles[i].h = obj.rtype.pobj_info.anim_angles[i].h;
  6008.                         }
  6009.                         obj_rw->rtype.pobj_info.subobj_flags             = obj.rtype.pobj_info.subobj_flags;
  6010.                         obj_rw->rtype.pobj_info.tmap_override            = obj.rtype.pobj_info.tmap_override;
  6011.                         obj_rw->rtype.pobj_info.alt_textures             = obj.rtype.pobj_info.alt_textures;
  6012.                         break;
  6013.                 }
  6014.                        
  6015.                 case RT_WEAPON_VCLIP:
  6016.                 case RT_HOSTAGE:
  6017.                 case RT_POWERUP:
  6018.                 case RT_FIREBALL:
  6019.                         obj_rw->rtype.vclip_info.vclip_num = obj.rtype.vclip_info.vclip_num;
  6020.                         obj_rw->rtype.vclip_info.frametime = obj.rtype.vclip_info.frametime;
  6021.                         obj_rw->rtype.vclip_info.framenum  = obj.rtype.vclip_info.framenum;
  6022.                         break;
  6023.                        
  6024.                 case RT_LASER:
  6025.                         break;
  6026.                        
  6027.         }
  6028. }
  6029.  
  6030. // turn object_rw to object after receiving
  6031. void multi_object_rw_to_object(object_rw *obj_rw, object &obj)
  6032. {
  6033.         obj = {};
  6034.         DXX_POISON_VAR(obj, 0xfd);
  6035.         set_object_type(obj, obj_rw->type);
  6036.         if (obj.type == OBJ_NONE)
  6037.                 return;
  6038.         obj.signature     = object_signature_t{static_cast<uint16_t>(obj_rw->signature)};
  6039.         obj.id            = obj_rw->id;
  6040.         /* obj->next,obj->prev handled by caller based on segment */
  6041.         obj.control_type  = obj_rw->control_type;
  6042.         set_object_movement_type(obj, obj_rw->movement_type);
  6043.         const auto render_type = obj_rw->render_type;
  6044.         if (valid_render_type(render_type))
  6045.                 obj.render_type = render_type_t{render_type};
  6046.         else
  6047.         {
  6048.                 con_printf(CON_URGENT, "peer sent bogus render type %#x for object %p; using none instead", render_type, &obj);
  6049.                 obj.render_type = RT_NONE;
  6050.         }
  6051.         obj.flags         = obj_rw->flags;
  6052.         obj.segnum        = obj_rw->segnum;
  6053.         /* obj->attached_obj cleared by caller */
  6054.         obj.pos         = obj_rw->pos;
  6055.         obj.orient = obj_rw->orient;
  6056.         obj.size          = obj_rw->size;
  6057.         obj.shields       = obj_rw->shields;
  6058.         obj.contains_type = obj_rw->contains_type;
  6059.         obj.contains_id   = obj_rw->contains_id;
  6060.         obj.contains_count= obj_rw->contains_count;
  6061.         obj.matcen_creator= obj_rw->matcen_creator;
  6062.         obj.lifeleft      = obj_rw->lifeleft;
  6063.        
  6064.         switch (obj.movement_type)
  6065.         {
  6066.                 case MT_NONE:
  6067.                         break;
  6068.                 case MT_PHYSICS:
  6069.                         obj.mtype.phys_info.velocity.x  = obj_rw->mtype.phys_info.velocity.x;
  6070.                         obj.mtype.phys_info.velocity.y  = obj_rw->mtype.phys_info.velocity.y;
  6071.                         obj.mtype.phys_info.velocity.z  = obj_rw->mtype.phys_info.velocity.z;
  6072.                         obj.mtype.phys_info.thrust.x    = obj_rw->mtype.phys_info.thrust.x;
  6073.                         obj.mtype.phys_info.thrust.y    = obj_rw->mtype.phys_info.thrust.y;
  6074.                         obj.mtype.phys_info.thrust.z    = obj_rw->mtype.phys_info.thrust.z;
  6075.                         obj.mtype.phys_info.mass        = obj_rw->mtype.phys_info.mass;
  6076.                         obj.mtype.phys_info.drag        = obj_rw->mtype.phys_info.drag;
  6077.                         obj.mtype.phys_info.rotvel.x    = obj_rw->mtype.phys_info.rotvel.x;
  6078.                         obj.mtype.phys_info.rotvel.y    = obj_rw->mtype.phys_info.rotvel.y;
  6079.                         obj.mtype.phys_info.rotvel.z    = obj_rw->mtype.phys_info.rotvel.z;
  6080.                         obj.mtype.phys_info.rotthrust.x = obj_rw->mtype.phys_info.rotthrust.x;
  6081.                         obj.mtype.phys_info.rotthrust.y = obj_rw->mtype.phys_info.rotthrust.y;
  6082.                         obj.mtype.phys_info.rotthrust.z = obj_rw->mtype.phys_info.rotthrust.z;
  6083.                         obj.mtype.phys_info.turnroll    = obj_rw->mtype.phys_info.turnroll;
  6084.                         obj.mtype.phys_info.flags       = obj_rw->mtype.phys_info.flags;
  6085.                         break;
  6086.                        
  6087.                 case MT_SPINNING:
  6088.                         obj.mtype.spin_rate.x = obj_rw->mtype.spin_rate.x;
  6089.                         obj.mtype.spin_rate.y = obj_rw->mtype.spin_rate.y;
  6090.                         obj.mtype.spin_rate.z = obj_rw->mtype.spin_rate.z;
  6091.                         break;
  6092.         }
  6093.        
  6094.         switch (obj.control_type)
  6095.         {
  6096.                 case CT_WEAPON:
  6097.                         obj.ctype.laser_info.parent_type      = obj_rw->ctype.laser_info.parent_type;
  6098.                         obj.ctype.laser_info.parent_num       = obj_rw->ctype.laser_info.parent_num;
  6099.                         obj.ctype.laser_info.parent_signature = object_signature_t{static_cast<uint16_t>(obj_rw->ctype.laser_info.parent_signature)};
  6100.                         obj.ctype.laser_info.creation_time    = obj_rw->ctype.laser_info.creation_time;
  6101.                         {
  6102.                                 const auto last_hitobj = obj_rw->ctype.laser_info.last_hitobj;
  6103.                                 obj.ctype.laser_info.reset_hitobj(last_hitobj);
  6104.                         }
  6105.                         obj.ctype.laser_info.track_goal       = obj_rw->ctype.laser_info.track_goal;
  6106.                         obj.ctype.laser_info.multiplier       = obj_rw->ctype.laser_info.multiplier;
  6107. #if defined(DXX_BUILD_DESCENT_II)
  6108.                         obj.ctype.laser_info.last_afterburner_time = 0;
  6109. #endif
  6110.                         break;
  6111.                        
  6112.                 case CT_EXPLOSION:
  6113.                         obj.ctype.expl_info.spawn_time    = obj_rw->ctype.expl_info.spawn_time;
  6114.                         obj.ctype.expl_info.delete_time   = obj_rw->ctype.expl_info.delete_time;
  6115.                         obj.ctype.expl_info.delete_objnum = obj_rw->ctype.expl_info.delete_objnum;
  6116.                         obj.ctype.expl_info.attach_parent = obj_rw->ctype.expl_info.attach_parent;
  6117.                         obj.ctype.expl_info.prev_attach   = obj_rw->ctype.expl_info.prev_attach;
  6118.                         obj.ctype.expl_info.next_attach   = obj_rw->ctype.expl_info.next_attach;
  6119.                         break;
  6120.                        
  6121.                 case CT_AI:
  6122.                 {
  6123.                         int i;
  6124.                         obj.ctype.ai_info.behavior               = static_cast<ai_behavior>(obj_rw->ctype.ai_info.behavior);
  6125.                         for (i = 0; i < MAX_AI_FLAGS; i++)
  6126.                                 obj.ctype.ai_info.flags[i]       = obj_rw->ctype.ai_info.flags[i];
  6127.                         obj.ctype.ai_info.hide_segment           = obj_rw->ctype.ai_info.hide_segment;
  6128.                         obj.ctype.ai_info.hide_index             = obj_rw->ctype.ai_info.hide_index;
  6129.                         obj.ctype.ai_info.path_length            = obj_rw->ctype.ai_info.path_length;
  6130.                         obj.ctype.ai_info.cur_path_index         = obj_rw->ctype.ai_info.cur_path_index;
  6131.                         obj.ctype.ai_info.danger_laser_num       = obj_rw->ctype.ai_info.danger_laser_num;
  6132.                         if (obj.ctype.ai_info.danger_laser_num != object_none)
  6133.                                 obj.ctype.ai_info.danger_laser_signature = object_signature_t{static_cast<uint16_t>(obj_rw->ctype.ai_info.danger_laser_signature)};
  6134. #if defined(DXX_BUILD_DESCENT_I)
  6135. #elif defined(DXX_BUILD_DESCENT_II)
  6136.                         obj.ctype.ai_info.dying_sound_playing    = obj_rw->ctype.ai_info.dying_sound_playing;
  6137.                         obj.ctype.ai_info.dying_start_time       = obj_rw->ctype.ai_info.dying_start_time;
  6138. #endif
  6139.                         break;
  6140.                 }
  6141.                        
  6142.                 case CT_LIGHT:
  6143.                         obj.ctype.light_info.intensity = obj_rw->ctype.light_info.intensity;
  6144.                         break;
  6145.                        
  6146.                 case CT_POWERUP:
  6147.                         obj.ctype.powerup_info.count         = obj_rw->ctype.powerup_info.count;
  6148. #if defined(DXX_BUILD_DESCENT_I)
  6149.                         obj.ctype.powerup_info.creation_time = 0;
  6150.                         obj.ctype.powerup_info.flags         = 0;
  6151. #elif defined(DXX_BUILD_DESCENT_II)
  6152.                         obj.ctype.powerup_info.creation_time = obj_rw->ctype.powerup_info.creation_time;
  6153.                         obj.ctype.powerup_info.flags         = obj_rw->ctype.powerup_info.flags;
  6154. #endif
  6155.                         break;
  6156.                 case CT_CNTRLCEN:
  6157.                 {
  6158.                         // gun points of reactor now part of the object but of course not saved in object_rw. Let's just recompute them.
  6159.                         calc_controlcen_gun_point(obj);
  6160.                         break;
  6161.                 }
  6162.         }
  6163.        
  6164.         switch (obj.render_type)
  6165.         {
  6166.                 case RT_MORPH:
  6167.                 case RT_POLYOBJ:
  6168.                 case RT_NONE: // HACK below
  6169.                 {
  6170.                         int i;
  6171.                         if (obj.render_type == RT_NONE && obj.type != OBJ_GHOST) // HACK: when a player is dead or not connected yet, clients still expect to get polyobj data - even if render_type == RT_NONE at this time.
  6172.                                 break;
  6173.                         obj.rtype.pobj_info.model_num                = obj_rw->rtype.pobj_info.model_num;
  6174.                         for (i=0;i<MAX_SUBMODELS;i++)
  6175.                         {
  6176.                                 obj.rtype.pobj_info.anim_angles[i].p = obj_rw->rtype.pobj_info.anim_angles[i].p;
  6177.                                 obj.rtype.pobj_info.anim_angles[i].b = obj_rw->rtype.pobj_info.anim_angles[i].b;
  6178.                                 obj.rtype.pobj_info.anim_angles[i].h = obj_rw->rtype.pobj_info.anim_angles[i].h;
  6179.                         }
  6180.                         obj.rtype.pobj_info.subobj_flags             = obj_rw->rtype.pobj_info.subobj_flags;
  6181.                         obj.rtype.pobj_info.tmap_override            = obj_rw->rtype.pobj_info.tmap_override;
  6182.                         obj.rtype.pobj_info.alt_textures             = obj_rw->rtype.pobj_info.alt_textures;
  6183.                         break;
  6184.                 }
  6185.                        
  6186.                 case RT_WEAPON_VCLIP:
  6187.                 case RT_HOSTAGE:
  6188.                 case RT_POWERUP:
  6189.                 case RT_FIREBALL:
  6190.                         obj.rtype.vclip_info.vclip_num = obj_rw->rtype.vclip_info.vclip_num;
  6191.                         obj.rtype.vclip_info.frametime = obj_rw->rtype.vclip_info.frametime;
  6192.                         obj.rtype.vclip_info.framenum  = obj_rw->rtype.vclip_info.framenum;
  6193.                         break;
  6194.                        
  6195.                 case RT_LASER:
  6196.                         break;
  6197.                        
  6198.         }
  6199. }
  6200.  
  6201. }
  6202.  
  6203. namespace dsx {
  6204. static int show_netgame_info_poll( newmenu *menu, const d_event &event, char *ngii  )
  6205. {
  6206.         newmenu_item *menus = newmenu_get_items(menu);
  6207.         switch (event.type)
  6208.         {
  6209.                 case EVENT_WINDOW_CLOSE:
  6210.                 {
  6211.                         d_free(ngii);
  6212.                         d_free(menus);
  6213.                         return 0;
  6214.                 }
  6215.                 default:
  6216.                         break;
  6217.         }
  6218.         return 0;
  6219. }
  6220.  
  6221. void show_netgame_info(const netgame_info &netgame)
  6222. {
  6223.         char *ngii;
  6224.         newmenu_item *m;
  6225.         int loc=0, ngilen = 50;
  6226. #if defined(DXX_BUILD_DESCENT_I)
  6227.         constexpr int nginum = 50;
  6228. #elif defined(DXX_BUILD_DESCENT_II)
  6229.         constexpr int nginum = 78;
  6230. #endif
  6231.  
  6232.         CALLOC(m, newmenu_item, nginum);
  6233.         if (!m)
  6234.                 return;
  6235.         MALLOC(ngii, char, nginum * ngilen);
  6236.         if (!ngii)
  6237.         {
  6238.                 d_free(m);
  6239.                 return;
  6240.         }
  6241.  
  6242.         snprintf(ngii+(ngilen*loc),ngilen,"Game Name\t  %s",netgame.game_name.data());                                                                      loc++;
  6243.         snprintf(ngii+(ngilen*loc),ngilen,"Mission Name\t  %s",netgame.mission_title.data());                                                               loc++;
  6244.         snprintf(ngii+(ngilen*loc),ngilen,"Level\t  %s%i", (netgame.levelnum<0)?"S":" ", abs(netgame.levelnum));                                           loc++;
  6245.         snprintf(ngii+(ngilen*loc),ngilen,"Game Mode\t  %s", netgame.gamemode < GMNames.size() ? GMNames[netgame.gamemode] : "INVALID");                   loc++;
  6246.         snprintf(ngii+(ngilen*loc),ngilen,"Players\t  %i/%i", netgame.numplayers, netgame.max_numplayers);                                                 loc++;
  6247.         snprintf(ngii+(ngilen*loc),ngilen," ");                                                                                                              loc++;
  6248.         snprintf(ngii+(ngilen*loc),ngilen,"Game Options:");                                                                                                  loc++;
  6249.         snprintf(ngii+(ngilen*loc),ngilen,"Difficulty\t  %s", MENU_DIFFICULTY_TEXT(netgame.difficulty));                                                    loc++;
  6250.         snprintf(ngii+(ngilen*loc),ngilen,"Reactor Life\t  %i %s", netgame.control_invul_time / F1_0 / 60, TXT_MINUTES_ABBREV);                             loc++;
  6251.         snprintf(ngii+(ngilen*loc),ngilen,"Max Time\t  %i %s", netgame.PlayTimeAllowed.count() / (F1_0 * 60), TXT_MINUTES_ABBREV);                                            loc++;
  6252.         snprintf(ngii+(ngilen*loc),ngilen,"Kill Goal\t  %i", netgame.KillGoal * 5);                                                                         loc++;
  6253.         snprintf(ngii+(ngilen*loc),ngilen," ");                                                                                                              loc++;
  6254.         snprintf(ngii+(ngilen*loc),ngilen,"Duplicate Powerups:");                                                                                            loc++;
  6255.         snprintf(ngii+(ngilen*loc),ngilen,"Primaries\t  %i", static_cast<int>(netgame.DuplicatePowerups.get_primary_count()));                                           loc++;
  6256.         snprintf(ngii+(ngilen*loc),ngilen,"Secondaries\t  %i", static_cast<int>(netgame.DuplicatePowerups.get_secondary_count()));                                       loc++;
  6257. #if defined(DXX_BUILD_DESCENT_II)
  6258.         snprintf(ngii+(ngilen*loc),ngilen,"Accessories\t  %i", static_cast<int>(netgame.DuplicatePowerups.get_accessory_count()));                                       loc++;
  6259. #endif
  6260.         snprintf(ngii+(ngilen*loc),ngilen," ");                                                                                                              loc++;
  6261.         snprintf(ngii+(ngilen*loc),ngilen,"Spawn Options:");                                                                                                 loc++;
  6262.         snprintf(ngii+(ngilen*loc),ngilen,"Use * Furthest Spawn Sites\t  %i", netgame.SecludedSpawns+1);                                                    loc++;
  6263.         snprintf(ngii+(ngilen*loc),ngilen,"Invulnerable Time\t  %1.1f sec", static_cast<float>(netgame.InvulAppear) / 2);                                   loc++;
  6264.         snprintf(ngii+(ngilen*loc),ngilen," ");                                                                                                              loc++;
  6265.         snprintf(ngii+(ngilen*loc),ngilen,"Objects Allowed:");                                                                                               loc++;
  6266.         snprintf(ngii+(ngilen*loc),ngilen,"Laser Upgrade\t  %s", (netgame.AllowedItems & NETFLAG_DOLASER)?TXT_YES:TXT_NO);                                  loc++;
  6267.         snprintf(ngii+(ngilen*loc),ngilen,"Quad Lasers\t  %s", (netgame.AllowedItems & NETFLAG_DOQUAD)?TXT_YES:TXT_NO);                                     loc++;
  6268.         snprintf(ngii+(ngilen*loc),ngilen,"Vulcan Cannon\t  %s", (netgame.AllowedItems & NETFLAG_DOVULCAN)?TXT_YES:TXT_NO);                                 loc++;
  6269.         snprintf(ngii+(ngilen*loc),ngilen,"Spreadfire Cannon\t  %s", (netgame.AllowedItems & NETFLAG_DOSPREAD)?TXT_YES:TXT_NO);                             loc++;
  6270.         snprintf(ngii+(ngilen*loc),ngilen,"Plasma Cannon\t  %s", (netgame.AllowedItems & NETFLAG_DOPLASMA)?TXT_YES:TXT_NO);                                 loc++;
  6271.         snprintf(ngii+(ngilen*loc),ngilen,"Fusion Cannon\t  %s", (netgame.AllowedItems & NETFLAG_DOFUSION)?TXT_YES:TXT_NO);                                 loc++;
  6272.         snprintf(ngii+(ngilen*loc),ngilen,"Homing Missiles\t  %s", (netgame.AllowedItems & NETFLAG_DOHOMING)?TXT_YES:TXT_NO);                               loc++;
  6273.         snprintf(ngii+(ngilen*loc),ngilen,"Proximity Bombs\t  %s", (netgame.AllowedItems & NETFLAG_DOPROXIM)?TXT_YES:TXT_NO);                               loc++;
  6274.         snprintf(ngii+(ngilen*loc),ngilen,"Smart Missiles\t  %s", (netgame.AllowedItems & NETFLAG_DOSMART)?TXT_YES:TXT_NO);                                 loc++;
  6275.         snprintf(ngii+(ngilen*loc),ngilen,"Mega Missiles\t  %s", (netgame.AllowedItems & NETFLAG_DOMEGA)?TXT_YES:TXT_NO);                                   loc++;
  6276. #if defined(DXX_BUILD_DESCENT_II)
  6277.         snprintf(ngii+(ngilen*loc),ngilen,"Super Lasers\t  %s", (netgame.AllowedItems & NETFLAG_DOSUPERLASER)?TXT_YES:TXT_NO);                              loc++;
  6278.         snprintf(ngii+(ngilen*loc),ngilen,"Gauss Cannon\t  %s", (netgame.AllowedItems & NETFLAG_DOGAUSS)?TXT_YES:TXT_NO);                                   loc++;
  6279.         snprintf(ngii+(ngilen*loc),ngilen,"Helix Cannon\t  %s", (netgame.AllowedItems & NETFLAG_DOHELIX)?TXT_YES:TXT_NO);                                   loc++;
  6280.         snprintf(ngii+(ngilen*loc),ngilen,"Phoenix Cannon\t  %s", (netgame.AllowedItems & NETFLAG_DOPHOENIX)?TXT_YES:TXT_NO);                               loc++;
  6281.         snprintf(ngii+(ngilen*loc),ngilen,"Omega Cannon\t  %s", (netgame.AllowedItems & NETFLAG_DOOMEGA)?TXT_YES:TXT_NO);                                   loc++;
  6282.         snprintf(ngii+(ngilen*loc),ngilen,"Flash Missiles\t  %s", (netgame.AllowedItems & NETFLAG_DOFLASH)?TXT_YES:TXT_NO);                                 loc++;
  6283.         snprintf(ngii+(ngilen*loc),ngilen,"Guides Missiles\t  %s", (netgame.AllowedItems & NETFLAG_DOGUIDED)?TXT_YES:TXT_NO);                               loc++;
  6284.         snprintf(ngii+(ngilen*loc),ngilen,"Smart Mines\t  %s", (netgame.AllowedItems & NETFLAG_DOSMARTMINE)?TXT_YES:TXT_NO);                                loc++;
  6285.         snprintf(ngii+(ngilen*loc),ngilen,"Mercury Missiles\t  %s", (netgame.AllowedItems & NETFLAG_DOMERCURY)?TXT_YES:TXT_NO);                             loc++;
  6286.         snprintf(ngii+(ngilen*loc),ngilen,"Earthshaker Missiles\t  %s", (netgame.AllowedItems & NETFLAG_DOSHAKER)?TXT_YES:TXT_NO);                          loc++;
  6287. #endif
  6288.         snprintf(ngii+(ngilen*loc),ngilen,"Cloaking\t  %s", (netgame.AllowedItems & NETFLAG_DOCLOAK)?TXT_YES:TXT_NO);                                       loc++;
  6289.         snprintf(ngii+(ngilen*loc),ngilen,"Invulnerability\t  %s", (netgame.AllowedItems & NETFLAG_DOINVUL)?TXT_YES:TXT_NO);                                loc++;
  6290. #if defined(DXX_BUILD_DESCENT_II)
  6291.         snprintf(ngii+(ngilen*loc),ngilen,"Afterburners\t  %s", (netgame.AllowedItems & NETFLAG_DOAFTERBURNER)?TXT_YES:TXT_NO);                             loc++;
  6292.         snprintf(ngii+(ngilen*loc),ngilen,"Ammo Rack\t  %s", (netgame.AllowedItems & NETFLAG_DOAMMORACK)?TXT_YES:TXT_NO);                                   loc++;
  6293.         snprintf(ngii+(ngilen*loc),ngilen,"Enery Converter\t  %s", (netgame.AllowedItems & NETFLAG_DOCONVERTER)?TXT_YES:TXT_NO);                            loc++;
  6294.         snprintf(ngii+(ngilen*loc),ngilen,"Headlight\t  %s", (netgame.AllowedItems & NETFLAG_DOHEADLIGHT)?TXT_YES:TXT_NO);                                  loc++;
  6295. #endif
  6296.         snprintf(ngii+(ngilen*loc),ngilen," ");                                                                                                              loc++;
  6297.         snprintf(ngii+(ngilen*loc),ngilen,"Objects Granted At Spawn:");                                                                                      loc++;
  6298.         snprintf(ngii+(ngilen*loc),ngilen,"Laser Level\t  %i", map_granted_flags_to_laser_level(netgame.SpawnGrantedItems)+1);                              loc++;
  6299.         snprintf(ngii+(ngilen*loc),ngilen,"Quad Lasers\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_QUAD)?TXT_YES:TXT_NO);             loc++;
  6300.         snprintf(ngii+(ngilen*loc),ngilen,"Vulcan Cannon\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_VULCAN)?TXT_YES:TXT_NO);         loc++;
  6301.         snprintf(ngii+(ngilen*loc),ngilen,"Spreadfire Cannon\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_SPREAD)?TXT_YES:TXT_NO);     loc++;
  6302.         snprintf(ngii+(ngilen*loc),ngilen,"Plasma Cannon\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_PLASMA)?TXT_YES:TXT_NO);         loc++;
  6303.         snprintf(ngii+(ngilen*loc),ngilen,"Fusion Cannon\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_FUSION)?TXT_YES:TXT_NO);         loc++;
  6304. #if defined(DXX_BUILD_DESCENT_II)
  6305.         snprintf(ngii+(ngilen*loc),ngilen,"Gauss Cannon\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_GAUSS)?TXT_YES:TXT_NO);           loc++;
  6306.         snprintf(ngii+(ngilen*loc),ngilen,"Helix Cannon\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_HELIX)?TXT_YES:TXT_NO);           loc++;
  6307.         snprintf(ngii+(ngilen*loc),ngilen,"Phoenix Cannon\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_PHOENIX)?TXT_YES:TXT_NO);       loc++;
  6308.         snprintf(ngii+(ngilen*loc),ngilen,"Omega Cannon\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_OMEGA)?TXT_YES:TXT_NO);           loc++;
  6309.         snprintf(ngii+(ngilen*loc),ngilen,"Afterburners\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_AFTERBURNER)?TXT_YES:TXT_NO);     loc++;
  6310.         snprintf(ngii+(ngilen*loc),ngilen,"Ammo Rack\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_AMMORACK)?TXT_YES:TXT_NO);           loc++;
  6311.         snprintf(ngii+(ngilen*loc),ngilen,"Enery Converter\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_CONVERTER)?TXT_YES:TXT_NO);    loc++;
  6312.         snprintf(ngii+(ngilen*loc),ngilen,"Headlight\t  %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_HEADLIGHT)?TXT_YES:TXT_NO);          loc++;
  6313. #endif
  6314.         snprintf(ngii+(ngilen*loc),ngilen," ");                                                                                                              loc++;
  6315.         snprintf(ngii+(ngilen*loc),ngilen,"Misc. Options:");                                                                                                 loc++;
  6316.         snprintf(ngii+(ngilen*loc),ngilen,"Show All Players On Automap\t  %s", netgame.game_flag.show_on_map?TXT_YES:TXT_NO);                               loc++;
  6317. #if defined(DXX_BUILD_DESCENT_II)
  6318.         snprintf(ngii+(ngilen*loc),ngilen,"Allow Marker Camera Views\t  %s", netgame.Allow_marker_view?TXT_YES:TXT_NO);                                     loc++;
  6319.         snprintf(ngii+(ngilen*loc),ngilen,"Indestructible Lights\t  %s", netgame.AlwaysLighting?TXT_YES:TXT_NO);                                            loc++;
  6320.         snprintf(ngii+(ngilen*loc),ngilen,"Thief permitted\t  %s", (netgame.ThiefModifierFlags & ThiefModifier::Absent) ? TXT_NO : TXT_YES);                                            loc++;
  6321.         snprintf(ngii+(ngilen*loc),ngilen,"Thief steals energy weapons\t  %s", (netgame.ThiefModifierFlags & ThiefModifier::NoEnergyWeapons) ? TXT_NO : TXT_YES);                                            loc++;
  6322.         snprintf(ngii+(ngilen*loc),ngilen,"Guidebot enabled (experimental)\t  %s", Netgame.AllowGuidebot ? TXT_YES : TXT_NO);                                            loc++;
  6323. #endif
  6324.         snprintf(ngii+(ngilen*loc),ngilen,"Bright Player Ships\t  %s", netgame.BrightPlayers?TXT_YES:TXT_NO);                                               loc++;
  6325.         snprintf(ngii+(ngilen*loc),ngilen,"Enemy Names On Hud\t  %s", netgame.ShowEnemyNames?TXT_YES:TXT_NO);                                               loc++;
  6326.         snprintf(ngii+(ngilen*loc),ngilen,"Friendly Fire (Team, Coop)\t  %s", netgame.NoFriendlyFire?TXT_NO:TXT_YES);                                       loc++;
  6327.         snprintf(ngii+(ngilen*loc),ngilen," ");                                                                                                              loc++;
  6328.         snprintf(ngii+(ngilen*loc),ngilen,"Network Options:");                                                                                               loc++;
  6329.         snprintf(ngii+(ngilen*loc),ngilen,"Packets Per Second\t  %i", netgame.PacketsPerSec);                                                               loc++;
  6330.  
  6331.         Assert(loc == nginum);
  6332.         for (int i = 0; i < nginum; i++) {
  6333.                 m[i].type = NM_TYPE_TEXT;
  6334.                 m[i].text = ngii+(i*ngilen);
  6335.         }
  6336.  
  6337.         newmenu_dotiny(NULL, "Netgame Info & Rules", nginum, m, 0, show_netgame_info_poll, ngii);
  6338. }
  6339. }
  6340.