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.  * Routines for EndGame, EndLevel, etc.
  23.  *
  24.  */
  25.  
  26. #include "dxxsconf.h"
  27. #include <cctype>
  28. #include <utility>
  29. #include <stdio.h>
  30. #include <stdlib.h>
  31. #include <string.h>
  32. #if !defined(_MSC_VER) && !defined(macintosh)
  33. #include <unistd.h>
  34. #endif
  35. #include <time.h>
  36.  
  37. #if DXX_USE_OGL
  38. #include "ogl_init.h"
  39. #endif
  40.  
  41. #include "inferno.h"
  42. #include "game.h"
  43. #include "player.h"
  44. #include "key.h"
  45. #include "object.h"
  46. #include "physics.h"
  47. #include "dxxerror.h"
  48. #include "joy.h"
  49. #include "iff.h"
  50. #include "pcx.h"
  51. #include "timer.h"
  52. #include "render.h"
  53. #include "laser.h"
  54. #include "event.h"
  55. #include "screens.h"
  56. #include "textures.h"
  57. #include "slew.h"
  58. #include "gauges.h"
  59. #include "texmap.h"
  60. #include "3d.h"
  61. #include "effects.h"
  62. #include "menu.h"
  63. #include "gameseg.h"
  64. #include "wall.h"
  65. #include "ai.h"
  66. #include "fuelcen.h"
  67. #include "switch.h"
  68. #include "digi.h"
  69. #include "gamesave.h"
  70. #include "scores.h"
  71. #include "u_mem.h"
  72. #include "palette.h"
  73. #include "morph.h"
  74. #include "lighting.h"
  75. #include "newdemo.h"
  76. #include "titles.h"
  77. #include "collide.h"
  78. #include "weapon.h"
  79. #include "sounds.h"
  80. #include "args.h"
  81. #include "gameseq.h"
  82. #include "gamefont.h"
  83. #include "newmenu.h"
  84. #include "hudmsg.h"
  85. #include "endlevel.h"
  86. #include "kmatrix.h"
  87. #  include "multi.h"
  88. #include "playsave.h"
  89. #include "fireball.h"
  90. #include "kconfig.h"
  91. #include "config.h"
  92. #include "robot.h"
  93. #include "automap.h"
  94. #include "cntrlcen.h"
  95. #include "powerup.h"
  96. #include "text.h"
  97. #include "piggy.h"
  98. #include "texmerge.h"
  99. #include "paging.h"
  100. #include "mission.h"
  101. #include "state.h"
  102. #include "songs.h"
  103. #include "gamepal.h"
  104. #include "controls.h"
  105. #include "credits.h"
  106. #include "gamemine.h"
  107. #if DXX_USE_EDITOR
  108. #include "editor/editor.h"
  109. #endif
  110. #include "strutil.h"
  111. #include "rle.h"
  112. #include "segment.h"
  113. #include "gameseg.h"
  114. #include "fmtcheck.h"
  115.  
  116. #include "compiler-range_for.h"
  117. #include "d_enumerate.h"
  118. #include "partial_range.h"
  119. #include "d_range.h"
  120. #include "d_zip.h"
  121.  
  122. #if defined(DXX_BUILD_DESCENT_I)
  123. #include "custom.h"
  124. #define GLITZ_BACKGROUND        Menu_pcx_name
  125.  
  126. #elif defined(DXX_BUILD_DESCENT_II)
  127. #include "movie.h"
  128. #define GLITZ_BACKGROUND        STARS_BACKGROUND
  129.  
  130. namespace dsx {
  131. static void StartNewLevelSecret(int level_num, int page_in_textures);
  132. static void InitPlayerPosition(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, int random_flag);
  133. static void DoEndGame();
  134. static void filter_objects_from_level(fvmobjptr &vmobjptr);
  135. PHYSFSX_gets_line_t<FILENAME_LEN> Current_level_palette;
  136. int     First_secret_visit = 1;
  137. }
  138. #endif
  139.  
  140. namespace {
  141.  
  142. class preserve_player_object_info
  143. {
  144.         player_info plr_info;
  145.         fix plr_shields;
  146.         /* Cache the reference, not the value.  This class is designed
  147.          * to be alive across a call to LoadLevel, which may change the
  148.          * value of the object number.
  149.          */
  150.         const objnum_t &objnum;
  151. public:
  152.         preserve_player_object_info(fvcobjptr &vcobjptr, const objnum_t &o) :
  153.                 objnum(o)
  154.         {
  155.                 auto &plr = *vcobjptr(objnum);
  156.                 plr_shields = plr.shields;
  157.                 plr_info = plr.ctype.player_info;
  158.         }
  159.         void restore(fvmobjptr &vmobjptr) const
  160.         {
  161.                 auto &plr = *vmobjptr(objnum);
  162.                 plr.shields = plr_shields;
  163.                 plr.ctype.player_info = plr_info;
  164.         }
  165. };
  166.  
  167. }
  168.  
  169. namespace dsx {
  170. static void init_player_stats_ship(object &, fix GameTime64);
  171. static window_event_result AdvanceLevel(int secret_flag);
  172. static void StartLevel(int random_flag);
  173. static void copy_defaults_to_robot_all(void);
  174. }
  175.  
  176. namespace dcx {
  177. //Current_level_num starts at 1 for the first level
  178. //-1,-2,-3 are secret levels
  179. //0 used to mean not a real level loaded (i.e. editor generated level), but this hack has been removed
  180. int     Current_level_num=1,Next_level_num;
  181. PHYSFSX_gets_line_t<LEVEL_NAME_LEN> Current_level_name;
  182.  
  183. // Global variables describing the player
  184. unsigned        N_players=1;    // Number of players ( >1 means a net game, eh?)
  185. playernum_t Player_num; // The player number who is on the console.
  186. fix StartingShields=INITIAL_SHIELDS;
  187. std::array<obj_position, MAX_PLAYERS> Player_init;
  188.  
  189. // Global variables telling what sort of game we have
  190. unsigned NumNetPlayerPositions;
  191. int     Do_appearance_effect=0;
  192.  
  193. template <object_type_t type>
  194. static bool is_object_of_type(const object_base &o)
  195. {
  196.         return o.type == type;
  197. }
  198.  
  199. static unsigned get_starting_concussion_missile_count()
  200. {
  201.         return 2 + NDL - GameUniqueState.Difficulty_level;
  202. }
  203.  
  204. }
  205.  
  206. namespace dsx {
  207.  
  208. //--------------------------------------------------------------------
  209. static void verify_console_object()
  210. {
  211.         auto &Objects = LevelUniqueObjectState.Objects;
  212.         auto &vmobjptr = Objects.vmptr;
  213.         Assert(Player_num < Players.size());
  214.         const auto &&console = vmobjptr(get_local_player().objnum);
  215.         ConsoleObject = console;
  216.         Assert(console->type == OBJ_PLAYER);
  217.         Assert(get_player_id(console) == Player_num);
  218. }
  219.  
  220. template <object_type_t type>
  221. static unsigned count_number_of_objects_of_type(fvcobjptr &vcobjptr)
  222. {
  223.         return std::count_if(vcobjptr.begin(), vcobjptr.end(), is_object_of_type<type>);
  224. }
  225.  
  226. #define count_number_of_robots  count_number_of_objects_of_type<OBJ_ROBOT>
  227. #define count_number_of_hostages        count_number_of_objects_of_type<OBJ_HOSTAGE>
  228.  
  229. static bool operator!=(const vms_vector &a, const vms_vector &b)
  230. {
  231.         return a.x != b.x || a.y != b.y || a.z != b.z;
  232. }
  233.  
  234. static unsigned generate_extra_starts_by_displacement_within_segment(const unsigned preplaced_starts, const unsigned total_required_num_starts)
  235. {
  236.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  237.         auto &Objects = LevelUniqueObjectState.Objects;
  238.         auto &Vertices = LevelSharedVertexState.get_vertices();
  239.         auto &vcobjptr = Objects.vcptr;
  240.         auto &vmobjptr = Objects.vmptr;
  241.         std::array<uint8_t, MAX_PLAYERS> player_init_segment_capacity_flag{};
  242.         DXX_MAKE_VAR_UNDEFINED(player_init_segment_capacity_flag);
  243.         static_assert(WRIGHT + 1 == WBOTTOM, "side ordering error");
  244.         static_assert(WBOTTOM + 1 == WBACK, "side ordering error");
  245.         constexpr uint8_t capacity_x = 1 << WRIGHT;
  246.         constexpr uint8_t capacity_y = 1 << WBOTTOM;
  247.         constexpr uint8_t capacity_z = 1 << WBACK;
  248.         /* When players are displaced, they are moved by their size
  249.          * multiplied by this constant.  Larger values provide more
  250.          * separation between the player starts, but increase the chance
  251.          * that the player will be too close to a wall or that the segment
  252.          * will be deemed too small to support displacement.
  253.          */
  254.         constexpr fix size_scalar = 0x18000;    // 1.5 in fixed point
  255.         unsigned segments_with_spare_capacity = 0;
  256.         auto &vcvertptr = Vertices.vcptr;
  257.         for (unsigned i = 0; i < preplaced_starts; ++i)
  258.         {
  259.                 /* For each existing Player_init, compute whether the segment is
  260.                  * large enough in each dimension to support adding more ships.
  261.                  */
  262.                 const auto &pi = Player_init[i];
  263.                 const auto segnum = pi.segnum;
  264.                 const shared_segment &seg = *vcsegptr(segnum);
  265.                 auto &plr = *Players.vcptr(i);
  266.                 auto &old_player_obj = *vcobjptr(plr.objnum);
  267.                 const vm_distance_squared size2(fixmul64(old_player_obj.size * old_player_obj.size, size_scalar));
  268.                 auto &v0 = *vcvertptr(seg.verts[0]);
  269.                 uint8_t capacity_flag = 0;
  270.                 if (vm_vec_dist2(v0, vcvertptr(seg.verts[1])) > size2)
  271.                         capacity_flag |= capacity_x;
  272.                 if (vm_vec_dist2(v0, vcvertptr(seg.verts[3])) > size2)
  273.                         capacity_flag |= capacity_y;
  274.                 if (vm_vec_dist2(v0, vcvertptr(seg.verts[4])) > size2)
  275.                         capacity_flag |= capacity_z;
  276.                 player_init_segment_capacity_flag[i] = capacity_flag;
  277.                 con_printf(CON_NORMAL, "Original player %u has size %u, starts in segment #%hu, and has segment capacity flags %x.", i, old_player_obj.size, static_cast<segnum_t>(segnum), capacity_flag);
  278.                 if (capacity_flag)
  279.                         ++segments_with_spare_capacity;
  280.         }
  281.         if (!segments_with_spare_capacity)
  282.                 return preplaced_starts;
  283.         unsigned k = preplaced_starts;
  284.         for (unsigned old_player_idx = -1, side = WRIGHT; ++ old_player_idx != preplaced_starts || (old_player_idx = 0, side ++ != WBACK);)
  285.         {
  286.                 auto &old_player_ref = *Players.vcptr(old_player_idx);
  287.                 const auto &&old_player_ptridx = Objects.vcptridx(old_player_ref.objnum);
  288.                 auto &old_player_obj = *old_player_ptridx;
  289.                 if (player_init_segment_capacity_flag[old_player_idx] & (1 << side))
  290.                 {
  291.                         auto &&segp = vmsegptridx(old_player_obj.segnum);
  292.                         /* Copy the start exactly.  The next loop will fix the
  293.                          * collisions caused by placing the clone on top of the
  294.                          * original.
  295.                          *
  296.                          * Currently, there is no handling for the case that the
  297.                          * level author already put two players too close together.
  298.                          * If this is a problem, more logic can be added to suppress
  299.                          * cloning in that case.
  300.                          */
  301.                         const auto &&extra_player_ptridx = obj_create_copy(old_player_obj, segp);
  302.                         if (extra_player_ptridx == object_none)
  303.                         {
  304.                                 con_printf(CON_URGENT, "%s:%u: warning: failed to copy start object %hu", __FILE__, __LINE__, old_player_ptridx.get_unchecked_index());
  305.                                 continue;
  306.                         }
  307.                         Players.vmptr(k)->objnum = extra_player_ptridx;
  308.                         auto &extra_player_obj = *extra_player_ptridx;
  309.                         set_player_id(extra_player_obj, k);
  310.                         con_printf(CON_NORMAL, "Copied player %u (object %hu at {%i, %i, %i}) to create player %u (object %hu).", old_player_idx, old_player_ptridx.get_unchecked_index(), old_player_obj.pos.x, old_player_obj.pos.y, old_player_obj.pos.z, k, extra_player_ptridx.get_unchecked_index());
  311.                         if (++ k >= total_required_num_starts)
  312.                                 break;
  313.                 }
  314.         }
  315.         for (unsigned old_player_idx = 0; old_player_idx < preplaced_starts; ++old_player_idx)
  316.         {
  317.                 auto &old_player_init = Player_init[old_player_idx];
  318.                 const auto old_player_pos = old_player_init.pos;
  319.                 auto &old_player_obj = *vmobjptr(Players.vcptr(old_player_idx)->objnum);
  320.                 std::array<vms_vector, 3> vec_displacement{};
  321.                 DXX_MAKE_VAR_UNDEFINED(vec_displacement);
  322.                 const shared_segment &seg = *vcsegptr(old_player_init.segnum);
  323.                 /* For each of [right, bottom, back], compute the vector between
  324.                  * the center of that side and the reference player's start
  325.                  * point.  This will be used in the next loop.
  326.                  */
  327.                 for (unsigned side = WRIGHT; side != WBACK + 1; ++side)
  328.                 {
  329.                         const auto &&center_on_side = compute_center_point_on_side(vcvertptr, seg, side);
  330.                         const auto &&vec_pos_to_center_on_side = vm_vec_sub(center_on_side, old_player_init.pos);
  331.                         const unsigned idxside = side - WRIGHT;
  332.                         assert(idxside < vec_displacement.size());
  333.                         vec_displacement[idxside] = vec_pos_to_center_on_side;
  334.                 }
  335.                 const auto displace_player = [&](const unsigned plridx, object_base &plrobj, const unsigned displacement_direction) {
  336.                         vms_vector disp{};
  337.                         unsigned dimensions = 0;
  338.                         for (unsigned i = 0, side = WRIGHT; side != WBACK + 1; ++side, ++i)
  339.                         {
  340.                                 if (!(player_init_segment_capacity_flag[old_player_idx] & (1 << side)))
  341.                                 {
  342.                                         con_printf(CON_NORMAL, "Cannot displace player %u at {%i, %i, %i}: not enough room in dimension %u.", plridx, plrobj.pos.x, plrobj.pos.y, plrobj.pos.z, side);
  343.                                         continue;
  344.                                 }
  345.                                 const auto &v = vec_displacement[i];
  346.                                 const auto &va = (displacement_direction & (1 << i)) ? v : vm_vec_negated(v);
  347.                                 con_printf(CON_NORMAL, "Add displacement of {%i, %i, %i} for dimension %u for player %u.", va.x, va.y, va.z, side, plridx);
  348.                                 ++ dimensions;
  349.                                 vm_vec_add2(disp, va);
  350.                         }
  351.                         if (!dimensions)
  352.                                 return;
  353.                         vm_vec_normalize(disp);
  354.                         vm_vec_scale(disp, fixmul(old_player_obj.size, size_scalar >> 1));
  355.                         const auto target_position = vm_vec_add(Player_init[plridx].pos, disp);
  356.                         if (const auto sidemask = get_seg_masks(vcvertptr, target_position, vcsegptr(plrobj.segnum), 1).sidemask)
  357.                         {
  358.                                 con_printf(CON_NORMAL, "Cannot displace player %u at {%i, %i, %i} to {%i, %i, %i}: would be outside segment for sides %x.", plridx, plrobj.pos.x, plrobj.pos.y, plrobj.pos.z, target_position.x, target_position.y, target_position.z, sidemask);
  359.                                 return;
  360.                         }
  361.                         con_printf(CON_NORMAL, "Displace player %u at {%i, %i, %i} by {%i, %i, %i} to {%i, %i, %i}.", plridx, plrobj.pos.x, plrobj.pos.y, plrobj.pos.z, disp.x, disp.y, disp.z, target_position.x, target_position.y, target_position.z);
  362.                         Player_init[plridx].pos = target_position;
  363.                         plrobj.pos = Player_init[plridx].pos;
  364.                 };
  365.                 for (unsigned extra_player_idx = preplaced_starts, displacements = 0; extra_player_idx < k; ++extra_player_idx)
  366.                 {
  367.                         auto &extra_player_obj = *vmobjptr(Players.vcptr(extra_player_idx)->objnum);
  368.                         if (old_player_pos != extra_player_obj.pos)
  369.                                 /* This clone is associated with some other player.
  370.                                  * Skip it here.  It will be handled in a different pass
  371.                                  * of the loop.
  372.                                  */
  373.                                 continue;
  374.                         auto &extra_player_init = Player_init[extra_player_idx];
  375.                         extra_player_init = old_player_init;
  376.                         if (!displacements++)
  377.                                 displace_player(old_player_idx, old_player_obj, 0);
  378.                         displace_player(extra_player_idx, extra_player_obj, displacements);
  379.                 }
  380.         }
  381.         return k;
  382. }
  383.  
  384. //added 10/12/95: delete buddy bot if coop game.  Probably doesn't really belong here. -MT
  385. static void gameseq_init_network_players(object_array &Objects)
  386. {
  387.  
  388.         // Initialize network player start locations and object numbers
  389.  
  390.         ConsoleObject = &Objects.front();
  391.         unsigned j = 0, k = 0;
  392.         const auto multiplayer_coop = Game_mode & GM_MULTI_COOP;
  393. #if defined(DXX_BUILD_DESCENT_II)
  394.         const auto remove_thief = Netgame.ThiefModifierFlags & ThiefModifier::Absent;
  395.         const auto multiplayer = Game_mode & GM_MULTI;
  396.         const auto retain_guidebot = Netgame.AllowGuidebot;
  397.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  398. #endif
  399.         auto &vmobjptridx = Objects.vmptridx;
  400.         range_for (const auto &&o, vmobjptridx)
  401.         {
  402.                 const auto type = o->type;
  403.                 if (type == OBJ_PLAYER || type == OBJ_GHOST || type == OBJ_COOP)
  404.                 {
  405.                         if (likely(k < Player_init.size()) &&
  406.                                 multiplayer_coop
  407.                                 ? (j == 0 || type == OBJ_COOP)
  408.                                 : (type == OBJ_PLAYER || type == OBJ_GHOST)
  409.                         )
  410.                         {
  411.                                 o->type=OBJ_PLAYER;
  412.                                 auto &pi = Player_init[k];
  413.                                 pi.pos = o->pos;
  414.                                 pi.orient = o->orient;
  415.                                 pi.segnum = o->segnum;
  416.                                 vmplayerptr(k)->objnum = o;
  417.                                 set_player_id(o, k);
  418.                                 k++;
  419.                         }
  420.                         else
  421.                                 obj_delete(LevelUniqueObjectState, Segments, o);
  422.                         j++;
  423.                 }
  424. #if defined(DXX_BUILD_DESCENT_II)
  425.                 else if (type == OBJ_ROBOT && multiplayer)
  426.                 {
  427.                         auto &ri = Robot_info[get_robot_id(o)];
  428.                         if ((!retain_guidebot && robot_is_companion(ri)) ||
  429.                                 (remove_thief && robot_is_thief(ri)))
  430.                         {
  431.                                 object_create_robot_egg(o);
  432.                                 obj_delete(LevelUniqueObjectState, Segments, o);                //kill the buddy in netgames
  433.                         }
  434.                 }
  435. #endif
  436.         }
  437.         if (multiplayer_coop)
  438.         {
  439.         const unsigned total_required_num_starts = Netgame.max_numplayers;
  440.         if (k < total_required_num_starts)
  441.         {
  442.                 con_printf(CON_NORMAL, "Insufficient cooperative starts found in mission \"%s\" level %u (need %u, found %u).  Generating extra starts...", Current_mission->path.c_str(), Current_level_num, total_required_num_starts, k);
  443.                 /*
  444.                  * First, try displacing the starts within the existing segment.
  445.                  */
  446.                 const unsigned preplaced_starts = k;
  447.                 k = generate_extra_starts_by_displacement_within_segment(preplaced_starts, total_required_num_starts);
  448.                 con_printf(CON_NORMAL, "Generated %u starts by displacement within the original segment.", k - preplaced_starts);
  449.         }
  450.         else
  451.                 con_printf(CON_NORMAL, "Found %u cooperative starts in mission \"%s\" level %u.", k, Current_mission->path.c_str(), Current_level_num);
  452.         }
  453.         NumNetPlayerPositions = k;
  454. }
  455.  
  456. void gameseq_remove_unused_players()
  457. {
  458.         auto &Objects = LevelUniqueObjectState.Objects;
  459.         auto &vmobjptridx = Objects.vmptridx;
  460.         // 'Remove' the unused players
  461.  
  462.         if (Game_mode & GM_MULTI)
  463.         {
  464.                 for (unsigned i = 0; i < NumNetPlayerPositions; ++i)
  465.                 {
  466.                         if ((!vcplayerptr(i)->connected) || (i >= N_players))
  467.                         {
  468.                                 multi_make_player_ghost(i);
  469.                         }
  470.                 }
  471.         }
  472.         else
  473.         {               // Note link to above if!!!
  474.                 range_for (auto &i, partial_const_range(Players, 1u, NumNetPlayerPositions))
  475.                 {
  476.                         obj_delete(LevelUniqueObjectState, Segments, vmobjptridx(i.objnum));
  477.                 }
  478. #if defined(DXX_BUILD_DESCENT_II)
  479.                 auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  480.                 if (PlayerCfg.ThiefModifierFlags & ThiefModifier::Absent)
  481.                 {
  482.                         range_for (const auto &&o, vmobjptridx)
  483.                         {
  484.                                 const auto type = o->type;
  485.                                 if (type == OBJ_ROBOT)
  486.                                 {
  487.                                         auto &ri = Robot_info[get_robot_id(o)];
  488.                                         if (robot_is_thief(ri))
  489.                                         {
  490.                                                 object_create_robot_egg(o);
  491.                                                 obj_delete(LevelUniqueObjectState, Segments, o);
  492.                                         }
  493.                                 }
  494.                         }
  495.                 }
  496. #endif
  497.         }
  498. }
  499.  
  500. // Setup player for new game
  501. void init_player_stats_game(const playernum_t pnum)
  502. {
  503.         auto &Objects = LevelUniqueObjectState.Objects;
  504.         auto &vmobjptr = Objects.vmptr;
  505.         auto &plr = *vmplayerptr(pnum);
  506.         plr.lives = INITIAL_LIVES;
  507.         plr.level = 1;
  508.         plr.time_level = 0;
  509.         plr.time_total = 0;
  510.         plr.hours_level = 0;
  511.         plr.hours_total = 0;
  512.         plr.num_kills_level = 0;
  513.         plr.num_kills_total = 0;
  514.         const auto &&plobj = vmobjptr(plr.objnum);
  515.         auto &player_info = plobj->ctype.player_info;
  516.         player_info.powerup_flags = {};
  517.         player_info.net_killed_total = 0;
  518.         player_info.net_kills_total = 0;
  519.         player_info.KillGoalCount = 0;
  520.         player_info.mission.score = 0;
  521.         player_info.mission.last_score = 0;
  522.         player_info.mission.hostages_rescued_total = 0;
  523.  
  524.         init_player_stats_new_ship(pnum);
  525. #if defined(DXX_BUILD_DESCENT_II)
  526.         if (pnum == Player_num)
  527.                 First_secret_visit = 1;
  528. #endif
  529. }
  530.  
  531. static void init_ammo_and_energy(object &plrobj)
  532. {
  533.         auto &player_info = plrobj.ctype.player_info;
  534.         {
  535.                 auto &energy = player_info.energy;
  536. #if defined(DXX_BUILD_DESCENT_II)
  537.                 if (player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(OMEGA_INDEX))
  538.                 {
  539.                         const auto old_omega_charge = player_info.Omega_charge;
  540.                         if (old_omega_charge < MAX_OMEGA_CHARGE)
  541.                         {
  542.                                 const auto energy_used = get_omega_energy_consumption((player_info.Omega_charge = MAX_OMEGA_CHARGE) - old_omega_charge);
  543.                                 energy -= energy_used;
  544.                         }
  545.                 }
  546. #endif
  547.                 if (energy < INITIAL_ENERGY)
  548.                         energy = INITIAL_ENERGY;
  549.         }
  550.         {
  551.                 auto &shields = plrobj.shields;
  552.                 if (shields < StartingShields)
  553.                         shields = StartingShields;
  554.         }
  555.         const unsigned minimum_missiles = get_starting_concussion_missile_count();
  556.         auto &concussion = player_info.secondary_ammo[CONCUSSION_INDEX];
  557.         if (concussion < minimum_missiles)
  558.                 concussion = minimum_missiles;
  559. }
  560.  
  561. #if defined(DXX_BUILD_DESCENT_II)
  562. extern  ubyte   Last_afterburner_state;
  563. #endif
  564.  
  565. // Setup player for new level (After completion of previous level)
  566. static void init_player_stats_level(player &plr, object &plrobj, const secret_restore secret_flag)
  567. {
  568. #if defined(DXX_BUILD_DESCENT_II)
  569.         auto &Objects = LevelUniqueObjectState.Objects;
  570.         auto &vcobjptridx = Objects.vcptridx;
  571. #endif
  572.  
  573.         plr.level = Current_level_num;
  574.  
  575.         if (!Network_rejoined) {
  576.                 plr.time_level = 0;
  577.                 plr.hours_level = 0;
  578.         }
  579.  
  580.         auto &player_info = plrobj.ctype.player_info;
  581.         player_info.mission.last_score = player_info.mission.score;
  582.  
  583.         plr.num_kills_level = 0;
  584.  
  585.         if (secret_flag == secret_restore::none) {
  586.                 init_ammo_and_energy(plrobj);
  587.  
  588.                 auto &powerup_flags = player_info.powerup_flags;
  589.                 powerup_flags &= ~(PLAYER_FLAGS_INVULNERABLE | PLAYER_FLAGS_CLOAKED);
  590. #if defined(DXX_BUILD_DESCENT_II)
  591.                 powerup_flags &= ~(PLAYER_FLAGS_MAP_ALL);
  592. #endif
  593.  
  594.                 DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
  595.                 DXX_MAKE_VAR_UNDEFINED(player_info.invulnerable_time);
  596.  
  597.                 const auto all_keys = PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY;
  598.                 if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
  599.                         powerup_flags |= all_keys;
  600.                 else
  601.                         powerup_flags &= ~all_keys;
  602.         }
  603.  
  604.         Player_dead_state = player_dead_state::no; // Added by RH
  605.         Dead_player_camera = NULL;
  606.  
  607. #if defined(DXX_BUILD_DESCENT_II)
  608.         Controls.state.afterburner = 0;
  609.         Last_afterburner_state = 0;
  610.  
  611.         digi_kill_sound_linked_to_object(vcobjptridx(plr.objnum));
  612. #endif
  613.         init_gauges();
  614. #if defined(DXX_BUILD_DESCENT_II)
  615.         Missile_viewer = NULL;
  616. #endif
  617.         init_player_stats_ship(plrobj, GameTime64);
  618. }
  619.  
  620. // Setup player for a brand-new ship
  621. void init_player_stats_new_ship(const playernum_t pnum)
  622. {
  623.         auto &Objects = LevelUniqueObjectState.Objects;
  624.         auto &vmobjptridx = Objects.vmptridx;
  625.         auto &plr = *vcplayerptr(pnum);
  626.         const auto &&plrobj = vmobjptridx(plr.objnum);
  627.         plrobj->shields = StartingShields;
  628.         auto &player_info = plrobj->ctype.player_info;
  629.         player_info.energy = INITIAL_ENERGY;
  630.         player_info.secondary_ammo = {{
  631.                 static_cast<uint8_t>(get_starting_concussion_missile_count())
  632.         }};
  633.         const auto GrantedItems = (Game_mode & GM_MULTI) ? Netgame.SpawnGrantedItems : 0;
  634.         player_info.vulcan_ammo = map_granted_flags_to_vulcan_ammo(GrantedItems);
  635.         const auto granted_laser_level = map_granted_flags_to_laser_level(GrantedItems);
  636.         player_info.laser_level = granted_laser_level;
  637.         const auto granted_primary_weapon_flags = HAS_LASER_FLAG | map_granted_flags_to_primary_weapon_flags(GrantedItems);
  638.         player_info.primary_weapon_flags = granted_primary_weapon_flags;
  639.         player_info.powerup_flags &= ~(PLAYER_FLAGS_QUAD_LASERS | PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE);
  640. #if defined(DXX_BUILD_DESCENT_II)
  641.         player_info.powerup_flags &= ~(PLAYER_FLAGS_AFTERBURNER | PLAYER_FLAGS_MAP_ALL | PLAYER_FLAGS_CONVERTER | PLAYER_FLAGS_AMMO_RACK | PLAYER_FLAGS_HEADLIGHT | PLAYER_FLAGS_HEADLIGHT_ON | PLAYER_FLAGS_FLAG);
  642.         player_info.Omega_charge = (granted_primary_weapon_flags & HAS_OMEGA_FLAG)
  643.                 ? MAX_OMEGA_CHARGE
  644.                 : 0;
  645.         player_info.Omega_recharge_delay = 0;
  646. #endif
  647.         player_info.powerup_flags |= map_granted_flags_to_player_flags(GrantedItems);
  648.         DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
  649.         DXX_MAKE_VAR_UNDEFINED(player_info.invulnerable_time);
  650.         if (pnum == Player_num)
  651.         {
  652.                 if (Game_mode & GM_MULTI && Netgame.InvulAppear)
  653.                 {
  654.                         player_info.powerup_flags |= PLAYER_FLAGS_INVULNERABLE;
  655.                         player_info.invulnerable_time = GameTime64 - (i2f(58 - Netgame.InvulAppear) >> 1);
  656.                         player_info.FakingInvul = 1;
  657.                 }
  658.                 set_primary_weapon(player_info, [=]{
  659.                         range_for (auto i, PlayerCfg.PrimaryOrder)
  660.                         {
  661.                                 if (i >= MAX_PRIMARY_WEAPONS)
  662.                                         break;
  663.                                 if (i == primary_weapon_index_t::LASER_INDEX)
  664.                                         break;
  665. #if defined(DXX_BUILD_DESCENT_II)
  666.                                 if (i == primary_weapon_index_t::SUPER_LASER_INDEX)
  667.                                 {
  668.                                         if (granted_laser_level <= LASER_LEVEL_4)
  669.                                                 /* Granted lasers are not super lasers */
  670.                                                 continue;
  671.                                         /* Super lasers still set LASER_INDEX, not
  672.                                          * SUPER_LASER_INDEX
  673.                                          */
  674.                                         break;
  675.                                 }
  676. #endif
  677.                                 if (HAS_PRIMARY_FLAG(i) & static_cast<unsigned>(granted_primary_weapon_flags))
  678.                                         return static_cast<primary_weapon_index_t>(i);
  679.                         }
  680.                         return primary_weapon_index_t::LASER_INDEX;
  681.                 }());
  682. #if defined(DXX_BUILD_DESCENT_II)
  683.                 auto primary_last_was_super = player_info.Primary_last_was_super;
  684.                 for (uint_fast32_t i = primary_weapon_index_t::VULCAN_INDEX, mask = 1 << i; i != primary_weapon_index_t::SUPER_LASER_INDEX; ++i, mask <<= 1)
  685.                 {
  686.                         /* If no super granted, force to non-super. */
  687.                         if (!(HAS_PRIMARY_FLAG(i + 5) & granted_primary_weapon_flags))
  688.                                 primary_last_was_super &= ~mask;
  689.                         /* If only super granted, force to super. */
  690.                         else if (!(HAS_PRIMARY_FLAG(i) & granted_primary_weapon_flags))
  691.                                 primary_last_was_super |= mask;
  692.                         /* else both granted, so leave as-is. */
  693.                         else
  694.                                 continue;
  695.                 }
  696.                 player_info.Primary_last_was_super = primary_last_was_super;
  697. #endif
  698.                 if (Newdemo_state == ND_STATE_RECORDING)
  699.                 {
  700.                         newdemo_record_laser_level(player_info.laser_level, 0);
  701.                 }
  702.                 set_secondary_weapon_to_concussion(player_info);
  703.                 dead_player_end(); //player no longer dead
  704.                 Player_dead_state = player_dead_state::no;
  705.                 player_info.Player_eggs_dropped = false;
  706.                 Dead_player_camera = 0;
  707. #if defined(DXX_BUILD_DESCENT_II)
  708.                 auto &Secondary_last_was_super = player_info.Secondary_last_was_super;
  709.                 Secondary_last_was_super = {};
  710.                 Afterburner_charge = GrantedItems.has_afterburner() ? F1_0 : 0;
  711.                 Controls.state.afterburner = 0;
  712.                 Last_afterburner_state = 0;
  713.                 Missile_viewer = nullptr; //reset missile camera if out there
  714. #endif
  715.                 init_ai_for_ship();
  716.         }
  717.         digi_kill_sound_linked_to_object(plrobj);
  718.         init_player_stats_ship(plrobj, GameTime64);
  719. }
  720.  
  721. void init_player_stats_ship(object &plrobj, const fix GameTime64)
  722. {
  723.         auto &player_info = plrobj.ctype.player_info;
  724.         player_info.lavafall_hiss_playing = false;
  725.         player_info.missile_gun = 0;
  726.         player_info.Spreadfire_toggle = 0;
  727.         player_info.killer_objnum = object_none;
  728. #if defined(DXX_BUILD_DESCENT_II)
  729.         player_info.Omega_recharge_delay = 0;
  730.         player_info.Helix_orientation = 0;
  731. #endif
  732.         player_info.mission.hostages_on_board = 0;
  733.         player_info.homing_object_dist = -F1_0; // Added by RH
  734.         player_info.Next_flare_fire_time = player_info.Next_laser_fire_time = player_info.Next_missile_fire_time = GameTime64;
  735. }
  736.  
  737. }
  738.  
  739. //do whatever needs to be done when a player dies in multiplayer
  740.  
  741. static void DoGameOver()
  742. {
  743.         if (PLAYING_BUILTIN_MISSION)
  744.                 scores_maybe_add_player();
  745. }
  746.  
  747. //update various information about the player
  748. void update_player_stats()
  749. {
  750.         auto &plr = get_local_player();
  751.         plr.time_level += FrameTime;    //the never-ending march of time...
  752.         if (plr.time_level > i2f(3600))
  753.         {
  754.                 plr.time_level -= i2f(3600);
  755.                 ++ plr.hours_level;
  756.         }
  757.  
  758.         plr.time_total += FrameTime;    //the never-ending march of time...
  759.         if (plr.time_total > i2f(3600))
  760.         {
  761.                 plr.time_total -= i2f(3600);
  762.                 ++ plr.hours_total;
  763.         }
  764. }
  765.  
  766. //go through this level and start any eclip sounds
  767. namespace dsx {
  768.  
  769. static void set_sound_sources(fvcsegptridx &vcsegptridx, fvcvertptr &vcvertptr)
  770. {
  771.         auto &Effects = LevelUniqueEffectsClipState.Effects;
  772.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  773.         digi_init_sounds();             //clear old sounds
  774. #if defined(DXX_BUILD_DESCENT_II)
  775.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  776.         auto &vcwallptr = Walls.vcptr;
  777. #endif
  778.         Dont_start_sound_objects = 1;
  779.  
  780.         const auto get_eclip_for_tmap = [](const d_level_unique_tmap_info_state::TmapInfo_array &TmapInfo, const unique_side &side) {
  781.                 if (const auto tm2 = side.tmap_num2)
  782.                 {
  783.                         const auto ec = TmapInfo[tm2 & 0x3fff].eclip_num;
  784. #if defined(DXX_BUILD_DESCENT_II)
  785.                         if (ec != eclip_none)
  786. #endif
  787.                                 return ec;
  788.                 }
  789. #if defined(DXX_BUILD_DESCENT_I)
  790.                 return eclip_none.value;
  791. #elif defined(DXX_BUILD_DESCENT_II)
  792.                 return TmapInfo[side.tmap_num].eclip_num;
  793. #endif
  794.         };
  795.  
  796.         range_for (const auto &&seg, vcsegptridx)
  797.         {
  798.                 range_for (const uint_fast32_t sidenum, xrange(MAX_SIDES_PER_SEGMENT))
  799.                 {
  800.                         int sn;
  801.  
  802. #if defined(DXX_BUILD_DESCENT_II)
  803.                         const auto wid = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg, sidenum);
  804.                         if (!(wid & WID_RENDER_FLAG))
  805.                                 continue;
  806. #endif
  807.                         const auto ec = get_eclip_for_tmap(TmapInfo, seg->unique_segment::sides[sidenum]);
  808.                         if (ec != eclip_none)
  809.                         {
  810.                                         if ((sn=Effects[ec].sound_num)!=-1) {
  811. #if defined(DXX_BUILD_DESCENT_II)
  812.                                                 auto csegnum = seg->children[sidenum];
  813.  
  814.                                                 //check for sound on other side of wall.  Don't add on
  815.                                                 //both walls if sound travels through wall.  If sound
  816.                                                 //does travel through wall, add sound for lower-numbered
  817.                                                 //segment.
  818.  
  819.                                                 if (IS_CHILD(csegnum) && csegnum < seg) {
  820.                                                         if (wid & (WID_FLY_FLAG|WID_RENDPAST_FLAG)) {
  821.                                                                 const auto &&csegp = vcsegptr(seg->children[sidenum]);
  822.                                                                 auto csidenum = find_connect_side(seg, csegp);
  823.  
  824.                                                                 if (csegp->unique_segment::sides[csidenum].tmap_num2 == seg->unique_segment::sides[sidenum].tmap_num2)
  825.                                                                         continue;               //skip this one
  826.                                                         }
  827.                                                 }
  828. #endif
  829.  
  830.                                                 const auto &&pnt = compute_center_point_on_side(vcvertptr, seg, sidenum);
  831.                                                 digi_link_sound_to_pos(sn, seg, sidenum, pnt, 1, F1_0/2);
  832.                                         }
  833.                         }
  834.                 }
  835.         }
  836.         Dont_start_sound_objects = 0;
  837. }
  838.  
  839. constexpr fix flash_dist=fl2f(.9);
  840.  
  841. //create flash for player appearance
  842. void create_player_appearance_effect(const d_vclip_array &Vclip, const object_base &player_obj)
  843. {
  844.         const auto pos = (&player_obj == Viewer)
  845.                 ? vm_vec_scale_add(player_obj.pos, player_obj.orient.fvec, fixmul(player_obj.size, flash_dist))
  846.                 : player_obj.pos;
  847.  
  848.         const auto &&seg = vmsegptridx(player_obj.segnum);
  849.         const auto &&effect_obj = object_create_explosion(seg, pos, player_obj.size, VCLIP_PLAYER_APPEARANCE);
  850.  
  851.         if (effect_obj) {
  852.                 effect_obj->orient = player_obj.orient;
  853.  
  854.                 const auto sound_num = Vclip[VCLIP_PLAYER_APPEARANCE].sound_num;
  855.                 if (sound_num > -1)
  856.                         digi_link_sound_to_pos(sound_num, seg, 0, effect_obj->pos, 0, F1_0);
  857.         }
  858. }
  859. }
  860.  
  861. //
  862. // New Game sequencing functions
  863. //
  864.  
  865. //get level filename. level numbers start at 1.  Secret levels are -1,-2,-3
  866. static const d_fname &get_level_file(int level_num)
  867. {
  868.         if (level_num<0)                //secret level
  869.                 return Secret_level_names[-level_num-1];
  870.         else                                    //normal level
  871.                 return Level_names[level_num-1];
  872. }
  873.  
  874. // routine to calculate the checksum of the segments.
  875. static void do_checksum_calc(const uint8_t *b, int len, unsigned int *s1, unsigned int *s2)
  876. {
  877.  
  878.         while(len--) {
  879.                 *s1 += *b++;
  880.                 if (*s1 >= 255) *s1 -= 255;
  881.                 *s2 += *s1;
  882.         }
  883. }
  884.  
  885. namespace dsx {
  886. static ushort netmisc_calc_checksum()
  887. {
  888.         unsigned int sum1,sum2;
  889.         short s;
  890.         int t;
  891.  
  892.         sum1 = sum2 = 0;
  893.         range_for (auto &&segp, vcsegptr)
  894.         {
  895.                 const cscusegment i = *segp;
  896.                 for (auto &&[sside, uside] : zip(i.s.sides, i.u.sides))
  897.                 {
  898.                         do_checksum_calc(reinterpret_cast<const uint8_t *>(&(sside.get_type())), 1, &sum1, &sum2);
  899.                         s = INTEL_SHORT(sside.wall_num);
  900.                         do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
  901.                         s = INTEL_SHORT(uside.tmap_num);
  902.                         do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
  903.                         s = INTEL_SHORT(uside.tmap_num2);
  904.                         do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
  905.                         range_for (auto &k, uside.uvls)
  906.                         {
  907.                                 t = INTEL_INT(k.u);
  908.                                 do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
  909.                                 t = INTEL_INT(k.v);
  910.                                 do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
  911.                                 t = INTEL_INT(k.l);
  912.                                 do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
  913.                         }
  914.                         range_for (auto &k, sside.normals)
  915.                         {
  916.                                 t = INTEL_INT(k.x);
  917.                                 do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
  918.                                 t = INTEL_INT(k.y);
  919.                                 do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
  920.                                 t = INTEL_INT(k.z);
  921.                                 do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
  922.                         }
  923.                 }
  924.                 range_for (auto &j, i.s.children)
  925.                 {
  926.                         s = INTEL_SHORT(j);
  927.                         do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
  928.                 }
  929.                 range_for (const uint16_t j, i.s.verts)
  930.                 {
  931.                         s = INTEL_SHORT(j);
  932.                         do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
  933.                 }
  934.                 s = INTEL_SHORT(i.u.objects);
  935.                 do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
  936. #if defined(DXX_BUILD_DESCENT_I)
  937.                 do_checksum_calc(&i.s.special, 1, &sum1, &sum2);
  938.                 do_checksum_calc(reinterpret_cast<const uint8_t *>(&i.s.matcen_num), 1, &sum1, &sum2);
  939.                 t = INTEL_INT(i.u.static_light);
  940.                 do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
  941. #endif
  942.                 do_checksum_calc(&i.s.station_idx, 1, &sum1, &sum2);
  943.         }
  944.         sum2 %= 255;
  945.         return ((sum1<<8)+ sum2);
  946. }
  947. }
  948.  
  949. #if defined(DXX_BUILD_DESCENT_II)
  950. namespace dsx {
  951. // load just the hxm file
  952. void load_level_robots(int level_num)
  953. {
  954.         Assert(level_num <= Last_level  && level_num >= Last_secret_level  && level_num != 0);
  955.         const d_fname &level_name = get_level_file(level_num);
  956.         if (Robot_replacements_loaded) {
  957.                 free_polygon_models();
  958.                 load_mission_ham();
  959.                 Robot_replacements_loaded = 0;
  960.         }
  961.         load_robot_replacements(level_name);
  962. }
  963. }
  964. #endif
  965.  
  966. //load a level off disk. level numbers start at 1.  Secret levels are -1,-2,-3
  967. namespace dsx {
  968. void LoadLevel(int level_num,int page_in_textures)
  969. {
  970.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  971.         auto &Objects = LevelUniqueObjectState.Objects;
  972.         auto &Vertices = LevelSharedVertexState.get_vertices();
  973.         auto &vcobjptr = Objects.vcptr;
  974.         auto &vmobjptr = Objects.vmptr;
  975.         preserve_player_object_info p(vcobjptr, vcplayerptr(Player_num)->objnum);
  976.  
  977.         auto &plr = get_local_player();
  978.         auto save_player = plr;
  979.  
  980.         Assert(level_num <= Last_level  && level_num >= Last_secret_level  && level_num != 0);
  981.         const d_fname &level_name = get_level_file(level_num);
  982. #if defined(DXX_BUILD_DESCENT_I)
  983.         if (!load_level(level_name))
  984.                 Current_level_num=level_num;
  985.  
  986.         gr_use_palette_table( "palette.256" );
  987. #elif defined(DXX_BUILD_DESCENT_II)
  988.         gr_set_default_canvas();
  989.         gr_clear_canvas(*grd_curcanv, BM_XRGB(0, 0, 0));                //so palette switching is less obvious
  990.  
  991.         load_level_robots(level_num);
  992.  
  993.         auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
  994.         int load_ret = load_level(LevelSharedDestructibleLightState, level_name);               //actually load the data from disk!
  995.  
  996.         if (load_ret)
  997.                 Error("Could not load level file <%s>, error = %d",static_cast<const char *>(level_name),load_ret);
  998.  
  999.         Current_level_num=level_num;
  1000.  
  1001.         load_palette(Current_level_palette,1,1);                //don't change screen
  1002. #endif
  1003.  
  1004. #if DXX_USE_EDITOR
  1005.         if (!EditorWindow)
  1006. #endif
  1007.                 show_boxed_message(TXT_LOADING, 0);
  1008. #ifdef RELEASE
  1009.         timer_delay(F1_0);
  1010. #endif
  1011.  
  1012.         load_endlevel_data(level_num);
  1013. #if defined(DXX_BUILD_DESCENT_I)
  1014.         load_custom_data(level_name);
  1015. #elif defined(DXX_BUILD_DESCENT_II)
  1016.         if (EMULATING_D1)
  1017.                 load_d1_bitmap_replacements();
  1018.         else
  1019.                 load_bitmap_replacements(level_name);
  1020.  
  1021.         if ( page_in_textures )
  1022.                 piggy_load_level_data();
  1023. #endif
  1024.  
  1025.         my_segments_checksum = netmisc_calc_checksum();
  1026.  
  1027.         reset_network_objects();
  1028.  
  1029.         plr = save_player;
  1030.  
  1031.         auto &vcvertptr = Vertices.vcptr;
  1032.         set_sound_sources(vcsegptridx, vcvertptr);
  1033.  
  1034. #if DXX_USE_EDITOR
  1035.         if (!EditorWindow)
  1036. #endif
  1037.                 songs_play_level_song( Current_level_num, 0 );
  1038.  
  1039.         gr_palette_load(gr_palette);            //actually load the palette
  1040. #if defined(DXX_BUILD_DESCENT_I)
  1041.         if ( page_in_textures )
  1042.                 piggy_load_level_data();
  1043. #endif
  1044.  
  1045.         gameseq_init_network_players(Objects);
  1046.         p.restore(vmobjptr);
  1047. }
  1048. }
  1049.  
  1050. //sets up Player_num & ConsoleObject
  1051. void InitPlayerObject()
  1052. {
  1053.         auto &Objects = LevelUniqueObjectState.Objects;
  1054.         auto &vmobjptr = Objects.vmptr;
  1055.         Assert(Player_num<MAX_PLAYERS);
  1056.  
  1057.         if (Player_num != 0 )   {
  1058.                 Players[0u] = get_local_player();
  1059.                 Player_num = 0;
  1060.         }
  1061.  
  1062.         auto &plr = get_local_player();
  1063.         plr.objnum = object_first;
  1064.         const auto &&console = vmobjptr(plr.objnum);
  1065.         ConsoleObject = console;
  1066.         console->type                           = OBJ_PLAYER;
  1067.         set_player_id(console, Player_num);
  1068.         console->control_type   = CT_FLYING;
  1069.         console->movement_type  = MT_PHYSICS;
  1070. }
  1071.  
  1072. //starts a new game on the given level
  1073. namespace dsx {
  1074.  
  1075. void StartNewGame(const int start_level)
  1076. {
  1077.         GameUniqueState.quicksave_selection = d_game_unique_state::save_slot::None;     // for first blind save, pick slot to save in
  1078.         reset_globals_for_new_game();
  1079.  
  1080.         Game_mode = GM_NORMAL;
  1081.  
  1082.         Next_level_num = 0;
  1083.  
  1084.         InitPlayerObject();                             //make sure player's object set up
  1085.  
  1086.         LevelUniqueObjectState.accumulated_robots = 0;
  1087.         LevelUniqueObjectState.total_hostages = 0;
  1088.         GameUniqueState.accumulated_robots = 0;
  1089.         GameUniqueState.total_hostages = 0;
  1090.         init_player_stats_game(Player_num);             //clear all stats
  1091.  
  1092.         N_players = 1;
  1093.  
  1094. #if defined(DXX_BUILD_DESCENT_II)
  1095.         if (start_level < 0)
  1096.         {
  1097.                 /* Allow an autosave as soon as the user exits the secret level.
  1098.                  */
  1099.                 state_set_immediate_autosave(GameUniqueState);
  1100.                 StartNewLevelSecret(start_level, 0);
  1101.         }
  1102.         else
  1103. #endif
  1104.         {
  1105.                 StartNewLevel(start_level);
  1106.                 /* Override Next_autosave to avoid creating an autosave
  1107.                  * immediately after starting a new game.  No state can be lost
  1108.                  * at that point, so there is no reason to save.
  1109.                  */
  1110.                 state_set_next_autosave(GameUniqueState, PlayerCfg.SPGameplayOptions.AutosaveInterval);
  1111.         }
  1112.  
  1113.         auto &plr = get_local_player();
  1114.         plr.starting_level = start_level;               // Mark where they started
  1115.         plr.callsign = InterfaceUniqueState.PilotName;
  1116.  
  1117.         game_disable_cheats();
  1118. #if defined(DXX_BUILD_DESCENT_II)
  1119.         init_seismic_disturbances();
  1120. #endif
  1121. }
  1122.  
  1123. //      -----------------------------------------------------------------------------
  1124. //      Does the bonus scoring.
  1125. //      Call with dead_flag = 1 if player died, but deserves some portion of bonus (only skill points), anyway.
  1126. static void DoEndLevelScoreGlitz()
  1127. {
  1128.         auto &Objects = LevelUniqueObjectState.Objects;
  1129.         auto &vmobjptr = Objects.vmptr;
  1130.         int level_points, skill_points, energy_points, shield_points, hostage_points;
  1131.         #define N_GLITZITEMS 9
  1132.         char                            m_str[N_GLITZITEMS][32];
  1133.         newmenu_item    m[N_GLITZITEMS];
  1134.         int                             i,c;
  1135.         char                            title[128];
  1136. #if defined(DXX_BUILD_DESCENT_I)
  1137.         gr_palette_load( gr_palette );
  1138. #elif defined(DXX_BUILD_DESCENT_II)
  1139.         int                             mine_level;
  1140.  
  1141.         //      Compute level player is on, deal with secret levels (negative numbers)
  1142.         mine_level = get_local_player().level;
  1143.         if (mine_level < 0)
  1144.                 mine_level *= -(Last_level/N_secret_levels);
  1145. #endif
  1146.  
  1147.         auto &plrobj = get_local_plrobj();
  1148.         auto &player_info = plrobj.ctype.player_info;
  1149.         level_points = player_info.mission.score - player_info.mission.last_score;
  1150.  
  1151.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1152.         if (!cheats.enabled) {
  1153.                 if (Difficulty_level > 1) {
  1154. #if defined(DXX_BUILD_DESCENT_I)
  1155.                         skill_points = level_points*(Difficulty_level-1)/2;
  1156. #elif defined(DXX_BUILD_DESCENT_II)
  1157.                         skill_points = level_points*(Difficulty_level)/4;
  1158. #endif
  1159.                         skill_points -= skill_points % 100;
  1160.                 } else
  1161.                         skill_points = 0;
  1162.  
  1163.                 hostage_points = player_info.mission.hostages_on_board * 500 * (Difficulty_level+1);
  1164. #if defined(DXX_BUILD_DESCENT_I)
  1165.                 shield_points = f2i(plrobj.shields) * 10 * (Difficulty_level+1);
  1166.                 energy_points = f2i(player_info.energy) * 5 * (Difficulty_level+1);
  1167. #elif defined(DXX_BUILD_DESCENT_II)
  1168.                 shield_points = f2i(plrobj.shields) * 5 * mine_level;
  1169.                 energy_points = f2i(player_info.energy) * 2 * mine_level;
  1170.  
  1171.                 shield_points -= shield_points % 50;
  1172.                 energy_points -= energy_points % 50;
  1173. #endif
  1174.         } else {
  1175.                 skill_points = 0;
  1176.                 shield_points = 0;
  1177.                 energy_points = 0;
  1178.                 hostage_points = 0;
  1179.         }
  1180.  
  1181.         auto &plr = get_local_player();
  1182.  
  1183.         c = 0;
  1184.         snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_SHIELD_BONUS, shield_points);                // Return at start to lower menu...
  1185.         snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_ENERGY_BONUS, energy_points);
  1186.         snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_HOSTAGE_BONUS, hostage_points);
  1187.         snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_SKILL_BONUS, skill_points);
  1188.  
  1189.         const unsigned hostages_on_board = player_info.mission.hostages_on_board;
  1190.         unsigned all_hostage_points = 0;
  1191.         unsigned endgame_points = 0;
  1192.         uint8_t is_last_level = 0;
  1193.         auto &hostage_text = m_str[c++];
  1194.         if (cheats.enabled)
  1195.                 snprintf(hostage_text, sizeof(hostage_text), "Hostages saved:   \t%u", hostages_on_board);
  1196.         else if (const auto hostages_lost = LevelUniqueObjectState.total_hostages - hostages_on_board)
  1197.                 snprintf(hostage_text, sizeof(hostage_text), "Hostages lost:    \t%u", hostages_lost);
  1198.         else
  1199.         {
  1200.                 all_hostage_points = hostages_on_board * 1000 * (Difficulty_level + 1);
  1201.                 snprintf(hostage_text, sizeof(hostage_text), "%s%i\n", TXT_FULL_RESCUE_BONUS, all_hostage_points);
  1202.         }
  1203.  
  1204.         auto &endgame_text = m_str[c++];
  1205.         endgame_text[0] = 0;
  1206.         if (cheats.enabled)
  1207.         {
  1208.                 /* Nothing */
  1209.         }
  1210.         else if (!(Game_mode & GM_MULTI) && plr.lives && Current_level_num == Last_level)
  1211.         {               //player has finished the game!
  1212.                 endgame_points = plr.lives * 10000;
  1213.                 snprintf(endgame_text, sizeof(endgame_text), "%s%i\n", TXT_SHIP_BONUS, endgame_points);
  1214.                 is_last_level=1;
  1215.         }
  1216.  
  1217.         add_bonus_points_to_score(player_info, skill_points + energy_points + shield_points + hostage_points + all_hostage_points + endgame_points);
  1218.  
  1219.         snprintf(m_str[c++], sizeof(m_str[0]), "%s%i\n", TXT_TOTAL_BONUS, shield_points + energy_points + hostage_points + skill_points + all_hostage_points + endgame_points);
  1220.         snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_TOTAL_SCORE, player_info.mission.score);
  1221.  
  1222.         for (i=0; i<c; i++) {
  1223.                 nm_set_item_text(m[i], m_str[i]);
  1224.         }
  1225.  
  1226.         auto current_level_num = Current_level_num;
  1227.         const auto txt_level = (current_level_num < 0) ? (current_level_num = -current_level_num, TXT_SECRET_LEVEL) : TXT_LEVEL;
  1228.         snprintf(title, sizeof(title), "%s%s %d %s\n%s %s", is_last_level?"\n\n\n":"\n", txt_level, current_level_num, TXT_COMPLETE, static_cast<const char *>(Current_level_name), TXT_DESTROYED);
  1229.  
  1230.         Assert(c <= N_GLITZITEMS);
  1231.  
  1232.         newmenu_do2(nullptr, title, c, m, unused_newmenu_subfunction, unused_newmenu_userdata, 0, GLITZ_BACKGROUND);
  1233. }
  1234. }
  1235.  
  1236. #if defined(DXX_BUILD_DESCENT_II)
  1237. //      -----------------------------------------------------------------------------------------------------
  1238. //called when the player is starting a level (new game or new ship)
  1239. static void StartSecretLevel()
  1240. {
  1241.         auto &Objects = LevelUniqueObjectState.Objects;
  1242.         auto &vmobjptridx = Objects.vmptridx;
  1243.         Assert(Player_dead_state == player_dead_state::no);
  1244.  
  1245.         InitPlayerPosition(vmobjptridx, vmsegptridx, 0);
  1246.  
  1247.         verify_console_object();
  1248.  
  1249.         ConsoleObject->control_type     = CT_FLYING;
  1250.         ConsoleObject->movement_type    = MT_PHYSICS;
  1251.  
  1252.         // -- WHY? -- disable_matcens();
  1253.         clear_transient_objects(0);             //0 means leave proximity bombs
  1254.  
  1255.         // create_player_appearance_effect(ConsoleObject);
  1256.         Do_appearance_effect = 1;
  1257.  
  1258.         ai_reset_all_paths();
  1259.         // -- NO? -- reset_time();
  1260.  
  1261.         reset_rear_view();
  1262.         auto &player_info = ConsoleObject->ctype.player_info;
  1263.         player_info.Auto_fire_fusion_cannon_time = 0;
  1264.         player_info.Fusion_charge = 0;
  1265. }
  1266.  
  1267. //      Returns true if secret level has been destroyed.
  1268. int p_secret_level_destroyed(void)
  1269. {
  1270.         if (First_secret_visit) {
  1271.                 return 0;               //      Never been there, can't have been destroyed.
  1272.         } else {
  1273.                 if (PHYSFSX_exists(SECRETC_FILENAME,0))
  1274.                 {
  1275.                         return 0;
  1276.                 } else {
  1277.                         return 1;
  1278.                 }
  1279.         }
  1280. }
  1281.  
  1282. //      -----------------------------------------------------------------------------------------------------
  1283. #define TXT_SECRET_RETURN  "Returning to level %i", Entered_from_level
  1284. #define TXT_SECRET_ADVANCE "Base level destroyed.\nAdvancing to level %i", Entered_from_level+1
  1285. #endif
  1286.  
  1287. static int draw_endlevel_background(newmenu *,const d_event &event, grs_bitmap *background)
  1288. {
  1289.         switch (event.type)
  1290.         {
  1291.                 case EVENT_WINDOW_DRAW:
  1292.                         gr_set_default_canvas();
  1293.                         show_fullscr(*grd_curcanv, *background);
  1294.                         break;
  1295.                        
  1296.                 default:
  1297.                         break;
  1298.         }
  1299.        
  1300.         return 0;
  1301. }
  1302.  
  1303. static void do_screen_message(const char *msg) __attribute_nonnull();
  1304. static void do_screen_message(const char *msg)
  1305. {
  1306.        
  1307.         if (Game_mode & GM_MULTI)
  1308.                 return;
  1309.         grs_main_bitmap background;
  1310.         if (pcx_read_bitmap(GLITZ_BACKGROUND, background, gr_palette) != pcx_result::SUCCESS)
  1311.                 return;
  1312.  
  1313.         gr_palette_load(gr_palette);
  1314.         std::array<newmenu_item, 1> nm_message_items{{
  1315.                 nm_item_menu(TXT_OK),
  1316.         }};
  1317.         newmenu_do( NULL, msg, nm_message_items, draw_endlevel_background, static_cast<grs_bitmap *>(&background));
  1318. }
  1319.  
  1320. namespace dsx {
  1321. #if defined(DXX_BUILD_DESCENT_II)
  1322. static void do_screen_message_fmt(const char *fmt, ...) __attribute_format_printf(1, 2);
  1323. static void do_screen_message_fmt(const char *fmt, ...)
  1324. {
  1325.         va_list arglist;
  1326.         char msg[1024];
  1327.         va_start(arglist, fmt);
  1328.         vsnprintf(msg, sizeof(msg), fmt, arglist);
  1329.         va_end(arglist);
  1330.         do_screen_message(msg);
  1331. }
  1332. #define do_screen_message(F,...)        dxx_call_printf_checked(do_screen_message_fmt,do_screen_message,(),F,##__VA_ARGS__)
  1333.  
  1334. //      -----------------------------------------------------------------------------------------------------
  1335. // called when the player is starting a new level for normal game mode and restore state
  1336. //      Need to deal with whether this is the first time coming to this level or not.  If not the
  1337. //      first time, instead of initializing various things, need to do a game restore for all the
  1338. //      robots, powerups, walls, doors, etc.
  1339. static void StartNewLevelSecret(int level_num, int page_in_textures)
  1340. {
  1341.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1342.         auto &Objects = LevelUniqueObjectState.Objects;
  1343.         auto &vmobjptr = Objects.vmptr;
  1344.  
  1345.         last_drawn_cockpit = -1;
  1346.  
  1347.         if (Newdemo_state == ND_STATE_PAUSED)
  1348.                 Newdemo_state = ND_STATE_RECORDING;
  1349.  
  1350.         if (Newdemo_state == ND_STATE_RECORDING) {
  1351.                 newdemo_set_new_level(level_num);
  1352.                 newdemo_record_start_frame(FrameTime );
  1353.         } else if (Newdemo_state != ND_STATE_PLAYBACK) {
  1354.  
  1355.                 set_screen_mode(SCREEN_MENU);
  1356.  
  1357.                 if (First_secret_visit) {
  1358.                         do_screen_message(TXT_SECRET_EXIT);
  1359.                 } else {
  1360.                         if (PHYSFSX_exists(SECRETC_FILENAME,0))
  1361.                         {
  1362.                                 do_screen_message(TXT_SECRET_EXIT);
  1363.                         } else {
  1364.                                 do_screen_message("Secret level already destroyed.\nAdvancing to level %i.", Current_level_num+1);
  1365.                         }
  1366.                 }
  1367.         }
  1368.  
  1369.         LoadLevel(level_num,page_in_textures);
  1370.  
  1371.         Assert(Current_level_num == level_num); // make sure level set right
  1372.  
  1373.         HUD_clear_messages();
  1374.  
  1375.         automap_clear_visited();
  1376.  
  1377.         Viewer = &get_local_plrobj();
  1378.  
  1379.         gameseq_remove_unused_players();
  1380.  
  1381.         Game_suspended = 0;
  1382.  
  1383.         LevelUniqueControlCenterState.Control_center_destroyed = 0;
  1384.  
  1385.         init_cockpit();
  1386.         reset_palette_add();
  1387.  
  1388.         if (First_secret_visit || (Newdemo_state == ND_STATE_PLAYBACK)) {
  1389.                 init_robots_for_level();
  1390.                 init_ai_objects();
  1391.                 init_smega_detonates();
  1392.                 init_morphs();
  1393.                 init_all_matcens();
  1394.                 reset_special_effects();
  1395.                 StartSecretLevel();
  1396.         } else {
  1397.                 if (PHYSFSX_exists(SECRETC_FILENAME,0))
  1398.                 {
  1399.                         auto &player_info = get_local_plrobj().ctype.player_info;
  1400.                         const auto pw_save = player_info.Primary_weapon;
  1401.                         const auto sw_save = player_info.Secondary_weapon;
  1402.                         state_restore_all(1, secret_restore::survived, SECRETC_FILENAME, blind_save::no);
  1403.                         player_info.Primary_weapon = pw_save;
  1404.                         player_info.Secondary_weapon = sw_save;
  1405.                         reset_special_effects();
  1406.                         StartSecretLevel();
  1407.                         // -- No: This is only for returning to base level: set_pos_from_return_segment();
  1408.                 } else {
  1409.                         do_screen_message("Secret level already destroyed.\nAdvancing to level %i.", Current_level_num+1);
  1410.                         return;
  1411.                 }
  1412.         }
  1413.  
  1414.         if (First_secret_visit) {
  1415.                 copy_defaults_to_robot_all();
  1416.         }
  1417.  
  1418.         init_controlcen_for_level();
  1419.  
  1420.         // Say player can use FLASH cheat to mark path to exit.
  1421.         Last_level_path_created = -1;
  1422.  
  1423.         First_secret_visit = 0;
  1424. }
  1425.  
  1426. static int Entered_from_level;
  1427.  
  1428. // ---------------------------------------------------------------------------------------------------------------
  1429. //      Called from switch.c when player is on a secret level and hits exit to return to base level.
  1430. window_event_result ExitSecretLevel()
  1431. {
  1432.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1433.         auto &Objects = LevelUniqueObjectState.Objects;
  1434.         auto &vmobjptr = Objects.vmptr;
  1435.         auto result = window_event_result::handled;
  1436.  
  1437.         if (Newdemo_state == ND_STATE_PLAYBACK)
  1438.                 return window_event_result::ignored;
  1439.  
  1440.         if (Game_wind)
  1441.                 window_set_visible(Game_wind, 0);
  1442.  
  1443.         if (!LevelUniqueControlCenterState.Control_center_destroyed)
  1444.         {
  1445.                 state_save_all(secret_save::c, blind_save::no);
  1446.         }
  1447.  
  1448.         if (PHYSFSX_exists(SECRETB_FILENAME,0))
  1449.         {
  1450.                 do_screen_message(TXT_SECRET_RETURN);
  1451.                 auto &player_info = get_local_plrobj().ctype.player_info;
  1452.                 const auto pw_save = player_info.Primary_weapon;
  1453.                 const auto sw_save = player_info.Secondary_weapon;
  1454.                 state_restore_all(1, secret_restore::survived, SECRETB_FILENAME, blind_save::no);
  1455.                 player_info.Primary_weapon = pw_save;
  1456.                 player_info.Secondary_weapon = sw_save;
  1457.         } else {
  1458.                 // File doesn't exist, so can't return to base level.  Advance to next one.
  1459.                 if (Entered_from_level == Last_level)
  1460.                 {
  1461.                         DoEndGame();
  1462.                         result = window_event_result::close;
  1463.                 }
  1464.                 else {
  1465.                         do_screen_message(TXT_SECRET_ADVANCE);
  1466.                         StartNewLevel(Entered_from_level+1);
  1467.                 }
  1468.         }
  1469.  
  1470.         if (Game_wind)
  1471.                 window_set_visible(Game_wind, 1);
  1472.         reset_time();
  1473.  
  1474.         return result;
  1475. }
  1476.  
  1477. // ---------------------------------------------------------------------------------------------------------------
  1478. //      Set invulnerable_time and cloak_time in player struct to preserve amount of time left to
  1479. //      be invulnerable or cloaked.
  1480. void do_cloak_invul_secret_stuff(fix64 old_gametime, player_info &player_info)
  1481. {
  1482.         auto &pl_flags = player_info.powerup_flags;
  1483.         if (pl_flags & PLAYER_FLAGS_INVULNERABLE)
  1484.         {
  1485.                 fix64   time_used;
  1486.  
  1487.                 auto &t = player_info.invulnerable_time;
  1488.                 time_used = old_gametime - t;
  1489.                 t = GameTime64 - time_used;
  1490.         }
  1491.  
  1492.         if (pl_flags & PLAYER_FLAGS_CLOAKED)
  1493.         {
  1494.                 fix     time_used;
  1495.  
  1496.                 auto &t = player_info.cloak_time;
  1497.                 time_used = old_gametime - t;
  1498.                 t = GameTime64 - time_used;
  1499.         }
  1500. }
  1501.  
  1502. // ---------------------------------------------------------------------------------------------------------------
  1503. //      Called from switch.c when player passes through secret exit.  That means he was on a non-secret level and he
  1504. //      is passing to the secret level.
  1505. //      Do a savegame.
  1506. void EnterSecretLevel(void)
  1507. {
  1508.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1509.         int i;
  1510.  
  1511.         Assert(! (Game_mode & GM_MULTI) );
  1512.  
  1513.         if (Game_wind)
  1514.                 window_set_visible(Game_wind, 0);
  1515.  
  1516.         digi_play_sample( SOUND_SECRET_EXIT, F1_0 );    // after above call which stops all sounds
  1517.        
  1518.         Entered_from_level = Current_level_num;
  1519.  
  1520.         if (LevelUniqueControlCenterState.Control_center_destroyed)
  1521.                 DoEndLevelScoreGlitz();
  1522.  
  1523.         if (Newdemo_state != ND_STATE_PLAYBACK)
  1524.                 state_save_all(secret_save::b, blind_save::no); //      Not between levels (ie, save all), IS a secret level, NO filename override
  1525.  
  1526.         //      Find secret level number to go to, stuff in Next_level_num.
  1527.         for (i=0; i<-Last_secret_level; i++)
  1528.                 if (Secret_level_table[i]==Current_level_num) {
  1529.                         Next_level_num = -(i+1);
  1530.                         break;
  1531.                 } else if (Secret_level_table[i] > Current_level_num) { //      Allows multiple exits in same group.
  1532.                         Next_level_num = -i;
  1533.                         break;
  1534.                 }
  1535.  
  1536.         if (! (i<-Last_secret_level))           //didn't find level, so must be last
  1537.                 Next_level_num = Last_secret_level;
  1538.  
  1539.         // NMN 04/09/07  Do a REAL start level routine if we are playing a D1 level so we have
  1540.         //               briefings
  1541.         if (EMULATING_D1)
  1542.         {
  1543.                 set_screen_mode(SCREEN_MENU);
  1544.                 do_screen_message("Alternate Exit Found!\n\nProceeding to Secret Level!");
  1545.                 StartNewLevel(Next_level_num);
  1546.         } else {
  1547.                 StartNewLevelSecret(Next_level_num, 1);
  1548.         }
  1549.         // END NMN
  1550.  
  1551.         // do_cloak_invul_stuff();
  1552.         if (Game_wind)
  1553.                 window_set_visible(Game_wind, 1);
  1554.         reset_time();
  1555. }
  1556. #endif
  1557.  
  1558. //called when the player has finished a level
  1559. window_event_result PlayerFinishedLevel(int secret_flag)
  1560. {
  1561.         auto &Objects = LevelUniqueObjectState.Objects;
  1562.         auto &vmobjptr = Objects.vmptr;
  1563.         if (Game_wind)
  1564.                 window_set_visible(Game_wind, 0);
  1565.  
  1566.         //credit the player for hostages
  1567.         auto &player_info = get_local_plrobj().ctype.player_info;
  1568.         player_info.mission.hostages_rescued_total += player_info.mission.hostages_on_board;
  1569. #if defined(DXX_BUILD_DESCENT_I)
  1570.         if (!(Game_mode & GM_MULTI) && (secret_flag)) {
  1571.                 std::array<newmenu_item, 1> m{{
  1572.                         nm_item_text(" "),                      //TXT_SECRET_EXIT;
  1573.                 }};
  1574.                 newmenu_do2(NULL, TXT_SECRET_EXIT, m.size(), m.data(), unused_newmenu_subfunction, unused_newmenu_userdata, 0, Menu_pcx_name);
  1575.         }
  1576. #elif defined(DXX_BUILD_DESCENT_II)
  1577.         Assert(!secret_flag);
  1578. #endif
  1579.         if (Game_mode & GM_NETWORK)
  1580.                 get_local_player().connected = CONNECT_WAITING; // Finished but did not die
  1581.  
  1582.         last_drawn_cockpit = -1;
  1583.  
  1584.         auto result = AdvanceLevel(secret_flag);                                //now go on to the next one (if one)
  1585.  
  1586.         if (Game_wind)
  1587.                 window_set_visible(Game_wind, 1);
  1588.         reset_time();
  1589.  
  1590.         return result;
  1591. }
  1592.  
  1593. #if defined(DXX_BUILD_DESCENT_II)
  1594. #define MOVIE_REQUIRED 1
  1595. #define ENDMOVIE "end"
  1596. #endif
  1597.  
  1598. //called when the player has finished the last level
  1599. static void DoEndGame()
  1600. {
  1601.         if ((Newdemo_state == ND_STATE_RECORDING) || (Newdemo_state == ND_STATE_PAUSED))
  1602.                 newdemo_stop_recording();
  1603.  
  1604.         set_screen_mode( SCREEN_MENU );
  1605.  
  1606.         gr_set_default_canvas();
  1607.  
  1608.         key_flush();
  1609.  
  1610.         if (PLAYING_BUILTIN_MISSION && !(Game_mode & GM_MULTI))
  1611.         { //only built-in mission, & not multi
  1612. #if defined(DXX_BUILD_DESCENT_II)
  1613.                 int played=MOVIE_NOT_PLAYED;    //default is not played
  1614.  
  1615.                 played = PlayMovie(ENDMOVIE ".tex", ENDMOVIE ".mve",MOVIE_REQUIRED);
  1616.                 if (!played)
  1617. #endif
  1618.                 {
  1619.                         do_end_briefing_screens(Ending_text_filename);
  1620.                 }
  1621.         }
  1622.         else if (!(Game_mode & GM_MULTI))    //not multi
  1623.         {
  1624.                 char tname[FILENAME_LEN];
  1625.  
  1626.                 do_end_briefing_screens (Ending_text_filename);
  1627.  
  1628.                 //try doing special credits
  1629.                 snprintf(tname, sizeof(tname), "%s.ctb", &*Current_mission->filename);
  1630.                 credits_show(tname);
  1631.         }
  1632.  
  1633.         key_flush();
  1634.  
  1635.         if (Game_mode & GM_MULTI)
  1636.                 multi_endlevel_score();
  1637.         else
  1638.                 // NOTE LINK TO ABOVE
  1639.                 DoEndLevelScoreGlitz();
  1640.  
  1641.         if (PLAYING_BUILTIN_MISSION && !((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))) {
  1642.                 gr_set_default_canvas();
  1643.                 gr_clear_canvas(*grd_curcanv, BM_XRGB(0,0,0));
  1644. #if defined(DXX_BUILD_DESCENT_II)
  1645.                 load_palette(D2_DEFAULT_PALETTE,0,1);
  1646. #endif
  1647.                 scores_maybe_add_player();
  1648.         }
  1649. }
  1650.  
  1651. //called to go to the next level (if there is one)
  1652. //if secret_flag is true, advance to secret level, else next normal one
  1653. //      Return true if game over.
  1654. static window_event_result AdvanceLevel(int secret_flag)
  1655. {
  1656.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1657.         auto rval = window_event_result::handled;
  1658.  
  1659. #if defined(DXX_BUILD_DESCENT_II)
  1660.         Assert(!secret_flag);
  1661.  
  1662.         // Loading a level can write over homing_flag.
  1663.         // So for simplicity, reset the homing weapon cheat here.
  1664.         if (cheats.homingfire)
  1665.         {
  1666.                 cheats.homingfire = 0;
  1667.                 weapons_homing_all_reset();
  1668.         }
  1669. #endif
  1670.         if (Current_level_num != Last_level)
  1671.         {
  1672.                 if (Game_mode & GM_MULTI)
  1673.                 {
  1674.                         const auto result = multi_endlevel_score();
  1675.                         if (result == kmatrix_result::abort)
  1676.                                 return window_event_result::close;      // Exit out of game loop
  1677.                 }
  1678.                 else
  1679.                         // NOTE LINK TO ABOVE!!!
  1680.                         DoEndLevelScoreGlitz();         //give bonuses
  1681.         }
  1682.  
  1683.         LevelUniqueControlCenterState.Control_center_destroyed = 0;
  1684.  
  1685.         if (Game_mode & GM_MULTI)
  1686.         {
  1687.                 int result;
  1688.                 result = multi_endlevel(&secret_flag); // Wait for other players to reach this point
  1689.                 if (result) // failed to sync
  1690.                 {
  1691.                         // check if player has finished the game
  1692.                         return Current_level_num == Last_level ? window_event_result::close : rval;
  1693.                 }
  1694.         }
  1695.  
  1696.         if (Current_level_num == Last_level) {          //player has finished the game!
  1697.  
  1698.                 DoEndGame();
  1699.                 rval = window_event_result::close;
  1700.  
  1701.         } else {
  1702. #if defined(DXX_BUILD_DESCENT_I)
  1703.                 Next_level_num = Current_level_num+1;           //assume go to next normal level
  1704.  
  1705.                 if (secret_flag) {                      //go to secret level instead
  1706.                         int i;
  1707.  
  1708.                         for (i=0;i<-Last_secret_level;i++)
  1709.                                 if (Secret_level_table[i]==Current_level_num) {
  1710.                                         Next_level_num = -(i+1);
  1711.                                         break;
  1712.                                 }
  1713.                         Assert(i<-Last_secret_level);           //couldn't find which secret level
  1714.                 }
  1715.  
  1716.                 if (Current_level_num < 0)      {                       //on secret level, where to go?
  1717.  
  1718.                         Assert(!secret_flag);                           //shouldn't be going to secret level
  1719.                         Assert(Current_level_num<=-1 && Current_level_num>=Last_secret_level);
  1720.  
  1721.                         Next_level_num = Secret_level_table[(-Current_level_num)-1]+1;
  1722.                 }
  1723. #elif defined(DXX_BUILD_DESCENT_II)
  1724.  
  1725.                 //NMN 04/08/07 If we are in a secret level and playing a D1
  1726.                 //             level, then use Entered_from_level # instead
  1727.                 if (Current_level_num < 0 && EMULATING_D1)
  1728.                 {
  1729.                   Next_level_num = Entered_from_level+1;                //assume go to next normal level
  1730.                 } else {
  1731.                   Next_level_num = Current_level_num+1;         //assume go to next normal level
  1732.                 }
  1733.                 // END NMN
  1734. #endif
  1735.                 rval = std::max(StartNewLevel(Next_level_num), rval);
  1736.         }
  1737.  
  1738.         return rval;
  1739. }
  1740.  
  1741. window_event_result DoPlayerDead()
  1742. {
  1743.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1744.         auto &Objects = LevelUniqueObjectState.Objects;
  1745.         auto &vmobjptr = Objects.vmptr;
  1746.         const bool pause = !(((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)) && (!Endlevel_sequence));
  1747.         auto result = window_event_result::handled;
  1748.  
  1749.         if (pause)
  1750.                 stop_time();
  1751.  
  1752.         reset_palette_add();
  1753.  
  1754.         gr_palette_load (gr_palette);
  1755.  
  1756.         dead_player_end();              //terminate death sequence (if playing)
  1757.  
  1758.         auto &plr = get_local_player();
  1759.         if ( Game_mode&GM_MULTI )
  1760.         {
  1761.                 multi_do_death(plr.objnum);
  1762.         }
  1763.         else
  1764.         {                               //Note link to above else!
  1765.                 -- plr.lives;
  1766.                 if (plr.lives == 0)
  1767.                 {
  1768.                         DoGameOver();
  1769.                         if (pause)
  1770.                                 start_time();
  1771.                         return window_event_result::close;
  1772.                 }
  1773.         }
  1774.  
  1775.         if (LevelUniqueControlCenterState.Control_center_destroyed)
  1776.         {
  1777.                 //clear out stuff so no bonus
  1778.                 auto &plrobj = get_local_plrobj();
  1779.                 auto &player_info = plrobj.ctype.player_info;
  1780.                 player_info.mission.hostages_on_board = 0;
  1781.                 player_info.energy = 0;
  1782.                 plrobj.shields = 0;
  1783.                 plr.connected = CONNECT_DIED_IN_MINE;
  1784.  
  1785.                 do_screen_message(TXT_DIED_IN_MINE); // Give them some indication of what happened
  1786. #if defined(DXX_BUILD_DESCENT_II)
  1787.                 if (Current_level_num < 0) {
  1788.                         if (PHYSFSX_exists(SECRETB_FILENAME,0))
  1789.                         {
  1790.                                 do_screen_message(TXT_SECRET_RETURN);
  1791.                                 state_restore_all(1, secret_restore::died, SECRETB_FILENAME, blind_save::no);                   //      2 means you died
  1792.                                 set_pos_from_return_segment();
  1793.                                 plr.lives--;                                            //      re-lose the life, get_local_player().lives got written over in restore.
  1794.                         } else {
  1795.                                 if (Entered_from_level == Last_level)
  1796.                                 {
  1797.                                         DoEndGame();
  1798.                                         result = window_event_result::close;
  1799.                                 }
  1800.                                 else {
  1801.                                         do_screen_message(TXT_SECRET_ADVANCE);
  1802.                                         StartNewLevel(Entered_from_level+1);
  1803.                                         init_player_stats_new_ship(Player_num); //      New, MK, 05/29/96!, fix bug with dying in secret level, advance to next level, keep powerups!
  1804.                                 }
  1805.                         }
  1806.                 } else
  1807. #endif
  1808.                 {
  1809.  
  1810.                         if (Game_wind)
  1811.                                 window_set_visible(Game_wind, 0);
  1812.                         result = AdvanceLevel(0);                       //if finished, go on to next level
  1813.  
  1814.                         init_player_stats_new_ship(Player_num);
  1815.                         last_drawn_cockpit = -1;
  1816.                         if (Game_wind)
  1817.                                 window_set_visible(Game_wind, 1);
  1818.                 }
  1819. #if defined(DXX_BUILD_DESCENT_II)
  1820.         } else if (Current_level_num < 0) {
  1821.                 if (PHYSFSX_exists(SECRETB_FILENAME,0))
  1822.                 {
  1823.                         do_screen_message(TXT_SECRET_RETURN);
  1824.                         if (!LevelUniqueControlCenterState.Control_center_destroyed)
  1825.                                 state_save_all(secret_save::c, blind_save::no);
  1826.                         state_restore_all(1, secret_restore::died, SECRETB_FILENAME, blind_save::no);
  1827.                         set_pos_from_return_segment();
  1828.                         plr.lives--;                                            //      re-lose the life, get_local_player().lives got written over in restore.
  1829.                 } else {
  1830.                         do_screen_message(TXT_DIED_IN_MINE); // Give them some indication of what happened
  1831.                         if (Entered_from_level == Last_level)
  1832.                         {
  1833.                                 DoEndGame();
  1834.                                 result = window_event_result::close;
  1835.                         }
  1836.                         else {
  1837.                                 do_screen_message(TXT_SECRET_ADVANCE);
  1838.                                 StartNewLevel(Entered_from_level+1);
  1839.                                 init_player_stats_new_ship(Player_num); //      New, MK, 05/29/96!, fix bug with dying in secret level, advance to next level, keep powerups!
  1840.                         }
  1841.                 }
  1842. #endif
  1843.         } else {
  1844.                 init_player_stats_new_ship(Player_num);
  1845.                 StartLevel(1);
  1846.         }
  1847.  
  1848.         digi_sync_sounds();
  1849.  
  1850.         if (pause)
  1851.                 start_time();
  1852.         reset_time();
  1853.  
  1854.         return result;
  1855. }
  1856.  
  1857. //called when the player is starting a new level for normal game mode and restore state
  1858. //      secret_flag set if came from a secret level
  1859. #if defined(DXX_BUILD_DESCENT_I)
  1860. window_event_result StartNewLevelSub(const int level_num, const int page_in_textures)
  1861. #elif defined(DXX_BUILD_DESCENT_II)
  1862. window_event_result StartNewLevelSub(const int level_num, const int page_in_textures, const secret_restore secret_flag)
  1863. #endif
  1864. {
  1865.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1866.         auto &Objects = LevelUniqueObjectState.Objects;
  1867.         auto &vmobjptr = Objects.vmptr;
  1868.         if (!(Game_mode & GM_MULTI)) {
  1869.                 last_drawn_cockpit = -1;
  1870.         }
  1871. #if defined(DXX_BUILD_DESCENT_I)
  1872.         static constexpr std::integral_constant<secret_restore, secret_restore::none> secret_flag{};
  1873. #elif defined(DXX_BUILD_DESCENT_II)
  1874.         BigWindowSwitch=0;
  1875. #endif
  1876.  
  1877.  
  1878.         if (Newdemo_state == ND_STATE_PAUSED)
  1879.                 Newdemo_state = ND_STATE_RECORDING;
  1880.  
  1881.         if (Newdemo_state == ND_STATE_RECORDING) {
  1882.                 newdemo_set_new_level(level_num);
  1883.                 newdemo_record_start_frame(FrameTime );
  1884.         }
  1885.  
  1886.         LoadLevel(level_num,page_in_textures);
  1887.  
  1888.         Assert(Current_level_num == level_num); //make sure level set right
  1889.  
  1890.         Viewer = &get_local_plrobj();
  1891.  
  1892.         Assert(N_players <= NumNetPlayerPositions);
  1893.                 //If this assert fails, there's not enough start positions
  1894.  
  1895.         if (Game_mode & GM_NETWORK)
  1896.         {
  1897.                 multi_prep_level_objects(Vclip);
  1898.                 if (multi_level_sync() == window_event_result::close) // After calling this, Player_num is set
  1899.                 {
  1900.                         songs_play_song( SONG_TITLE, 1 ); // level song already plays but we fail to start level...
  1901.                         return window_event_result::close;
  1902.                 }
  1903.         }
  1904.  
  1905.         HUD_clear_messages();
  1906.  
  1907.         automap_clear_visited();
  1908.  
  1909.         LevelUniqueObjectState.accumulated_robots = count_number_of_robots(Objects.vcptr);
  1910.         GameUniqueState.accumulated_robots += LevelUniqueObjectState.accumulated_robots;
  1911.         LevelUniqueObjectState.total_hostages = count_number_of_hostages(Objects.vcptr);
  1912.         GameUniqueState.total_hostages += LevelUniqueObjectState.total_hostages;
  1913.         init_player_stats_level(get_local_player(), get_local_plrobj(), secret_flag);
  1914.  
  1915. #if defined(DXX_BUILD_DESCENT_I)
  1916.         gr_use_palette_table( "palette.256" );
  1917. #elif defined(DXX_BUILD_DESCENT_II)
  1918.         load_palette(Current_level_palette,0,1);
  1919. #endif
  1920.         gr_palette_load(gr_palette);
  1921.  
  1922.         if ((Game_mode & GM_MULTI_COOP) && Network_rejoined)
  1923.         {
  1924.                 for (playernum_t i = 0; i < N_players; ++i)
  1925.                 {
  1926.                         const auto &&plobj = vmobjptr(vcplayerptr(i)->objnum);
  1927.                         plobj->ctype.player_info.powerup_flags |= Netgame.net_player_flags[i];
  1928.                 }
  1929.         }
  1930.  
  1931.         if (Game_mode & GM_MULTI)
  1932.         {
  1933.                 multi_prep_level_player();
  1934.         }
  1935.  
  1936.         gameseq_remove_unused_players();
  1937.  
  1938.         Game_suspended = 0;
  1939.  
  1940.         LevelUniqueControlCenterState.Control_center_destroyed = 0;
  1941.  
  1942. #if defined(DXX_BUILD_DESCENT_II)
  1943.         set_screen_mode(SCREEN_GAME);
  1944. #endif
  1945.  
  1946.         init_cockpit();
  1947.         init_robots_for_level();
  1948.         init_ai_objects();
  1949.         init_morphs();
  1950.         init_all_matcens();
  1951.         reset_palette_add();
  1952.         LevelUniqueStuckObjectState.init_stuck_objects();
  1953. #if defined(DXX_BUILD_DESCENT_II)
  1954.         init_smega_detonates();
  1955.         init_thief_for_level();
  1956.         if (!(Game_mode & GM_MULTI))
  1957.                 filter_objects_from_level(vmobjptr);
  1958. #endif
  1959.  
  1960.         if (!(Game_mode & GM_MULTI) && !cheats.enabled)
  1961.                 set_highest_level(Current_level_num);
  1962.         else
  1963.                 read_player_file();             //get window sizes
  1964.  
  1965.         reset_special_effects();
  1966.  
  1967. #if DXX_USE_OGL
  1968.         ogl_cache_level_textures();
  1969. #endif
  1970.  
  1971.  
  1972.         if (Network_rejoined == 1)
  1973.         {
  1974.                 Network_rejoined = 0;
  1975.                 StartLevel(1);
  1976.         }
  1977.         else
  1978.                 StartLevel(0);          // Note link to above if!
  1979.  
  1980.         copy_defaults_to_robot_all();
  1981.         init_controlcen_for_level();
  1982.  
  1983.         //      Say player can use FLASH cheat to mark path to exit.
  1984.         Last_level_path_created = -1;
  1985.  
  1986.         // Initialise for palette_restore()
  1987.         // Also takes care of nm_draw_background() possibly being called
  1988.         if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
  1989.                 full_palette_save();
  1990.  
  1991.         if (!Game_wind)
  1992.                 game();
  1993.  
  1994.         return window_event_result::handled;
  1995. }
  1996.  
  1997. void bash_to_shield(const d_powerup_info_array &Powerup_info, const d_vclip_array &Vclip, object_base &i)
  1998. {
  1999.         set_powerup_id(Powerup_info, Vclip, i, POW_SHIELD_BOOST);
  2000. }
  2001.  
  2002. #if defined(DXX_BUILD_DESCENT_II)
  2003.  
  2004. static void filter_objects_from_level(fvmobjptr &vmobjptr)
  2005.  {
  2006.         range_for (const auto &&objp, vmobjptr)
  2007.         {
  2008.                 if (objp->type==OBJ_POWERUP)
  2009.                 {
  2010.                         const auto powerup_id = get_powerup_id(objp);
  2011.                         if (powerup_id == POW_FLAG_RED || powerup_id == POW_FLAG_BLUE)
  2012.                                 bash_to_shield(Powerup_info, Vclip, objp);
  2013.                 }
  2014.    }
  2015.  
  2016.  }
  2017.  
  2018. namespace {
  2019.  
  2020. struct intro_movie_t {
  2021.         int     level_num;
  2022.         char    movie_name[4];
  2023. };
  2024.  
  2025. const std::array<intro_movie_t, 7> intro_movie{{
  2026.         { 1, "PLA"},
  2027.         { 5, "PLB"},
  2028.         { 9, "PLC"},
  2029.         {13, "PLD"},
  2030.         {17, "PLE"},
  2031.         {21, "PLF"},
  2032.         {24, "PLG"}
  2033. }};
  2034.  
  2035. }
  2036.  
  2037. static void ShowLevelIntro(int level_num)
  2038. {
  2039.         //if shareware, show a briefing?
  2040.  
  2041.         if (!(Game_mode & GM_MULTI)) {
  2042.                 palette_array_t save_pal;
  2043.  
  2044.                 save_pal = gr_palette;
  2045.  
  2046.                 if (PLAYING_BUILTIN_MISSION) {
  2047.  
  2048.                         if (is_SHAREWARE || is_MAC_SHARE)
  2049.                         {
  2050.                                 if (level_num==1)
  2051.                                         do_briefing_screens (Briefing_text_filename, 1);
  2052.                         }
  2053.                         else if (is_D2_OEM)
  2054.                         {
  2055.                                 if (level_num == 1 && !intro_played)
  2056.                                         do_briefing_screens(Briefing_text_filename, 1);
  2057.                         }
  2058.                         else // full version
  2059.                         {
  2060.                                 range_for (auto &i, intro_movie)
  2061.                                 {
  2062.                                         if (i.level_num == level_num)
  2063.                                         {
  2064.                                                 Screen_mode = -1;
  2065.                                                 PlayMovie(NULL, i.movie_name, MOVIE_REQUIRED);
  2066.                                                 break;
  2067.                                         }
  2068.                                 }
  2069.  
  2070.                                 do_briefing_screens (Briefing_text_filename,level_num);
  2071.                         }
  2072.                 }
  2073.                 else    //not the built-in mission (maybe d1, too).  check for add-on briefing
  2074.                 {
  2075.                         do_briefing_screens(Briefing_text_filename, level_num);
  2076.                 }
  2077.  
  2078.                 gr_palette = save_pal;
  2079.         }
  2080. }
  2081.  
  2082. //      ---------------------------------------------------------------------------
  2083. //      If starting a level which appears in the Secret_level_table, then set First_secret_visit.
  2084. //      Reason: On this level, if player goes to a secret level, he will be going to a different
  2085. //      secret level than he's ever been to before.
  2086. //      Sets the global First_secret_visit if necessary.  Otherwise leaves it unchanged.
  2087. static void maybe_set_first_secret_visit(int level_num)
  2088. {
  2089.         range_for (auto &i, unchecked_partial_range(Secret_level_table.get(), N_secret_levels))
  2090.         {
  2091.                 if (i == level_num)
  2092.                 {
  2093.                         First_secret_visit = 1;
  2094.                         break;
  2095.                 }
  2096.         }
  2097. }
  2098. #endif
  2099.  
  2100. //called when the player is starting a new level for normal game model
  2101. //      secret_flag if came from a secret level
  2102. window_event_result StartNewLevel(int level_num)
  2103. {
  2104.         hide_menus();
  2105.  
  2106.         GameTime64 = 0;
  2107.         /* Autosave is permitted immediately on entering a new level */
  2108.         state_set_immediate_autosave(GameUniqueState);
  2109.         ThisLevelTime = {};
  2110.  
  2111. #if defined(DXX_BUILD_DESCENT_I)
  2112.         if (!(Game_mode & GM_MULTI)) {
  2113.                 do_briefing_screens(Briefing_text_filename, level_num);
  2114.         }
  2115. #elif defined(DXX_BUILD_DESCENT_II)
  2116.         if (level_num > 0) {
  2117.                 maybe_set_first_secret_visit(level_num);
  2118.         }
  2119.  
  2120.         ShowLevelIntro(level_num);
  2121. #endif
  2122.  
  2123.         return StartNewLevelSub(level_num, 1, secret_restore::none);
  2124.  
  2125. }
  2126.  
  2127. namespace
  2128. {
  2129.  
  2130. class respawn_locations
  2131. {
  2132.         typedef std::pair<int, fix> site;
  2133.         unsigned max_usable_spawn_sites;
  2134.         std::array<site, MAX_PLAYERS> sites;
  2135. public:
  2136.         respawn_locations(fvmobjptr &vmobjptr, fvcsegptridx &vcsegptridx)
  2137.         {
  2138.                 const auto player_num = Player_num;
  2139.                 const auto find_closest_player = [player_num, &vmobjptr, &vcsegptridx](const obj_position &candidate) {
  2140.                         fix closest_dist = INT32_MAX;
  2141.                         const auto &&candidate_segp = vcsegptridx(candidate.segnum);
  2142.                         for (playernum_t i = N_players; i--;)
  2143.                         {
  2144.                                 if (i == player_num)
  2145.                                         continue;
  2146.                                 const auto &&objp = vmobjptr(vcplayerptr(i)->objnum);
  2147.                                 if (objp->type != OBJ_PLAYER)
  2148.                                         continue;
  2149.                                 const auto dist = find_connected_distance(objp->pos, candidate_segp.absolute_sibling(objp->segnum), candidate.pos, candidate_segp, -1, WALL_IS_DOORWAY_FLAG<0>());
  2150.                                 if (dist >= 0 && closest_dist > dist)
  2151.                                         closest_dist = dist;
  2152.                         }
  2153.                         return closest_dist;
  2154.                 };
  2155.                 const auto max_spawn_sites = std::min<unsigned>(NumNetPlayerPositions, sites.size());
  2156.                 for (uint_fast32_t i = max_spawn_sites; i--;)
  2157.                 {
  2158.                         auto &s = sites[i];
  2159.                         s.first = i;
  2160.                         s.second = find_closest_player(Player_init[i]);
  2161.                 }
  2162.                 const unsigned SecludedSpawns = Netgame.SecludedSpawns + 1;
  2163.                 if (max_spawn_sites > SecludedSpawns)
  2164.                 {
  2165.                         max_usable_spawn_sites = SecludedSpawns;
  2166.                         const auto &&predicate = [](const site &a, const site &b) {
  2167.                                 return a.second > b.second;
  2168.                         };
  2169.                         const auto b = sites.begin();
  2170.                         const auto m = std::next(b, SecludedSpawns);
  2171.                         const auto e = std::next(b, max_spawn_sites);
  2172.                         std::partial_sort(b, m, e, predicate);
  2173.                 }
  2174.                 else
  2175.                         max_usable_spawn_sites = max_spawn_sites;
  2176.         }
  2177.         unsigned get_usable_sites() const
  2178.         {
  2179.                 return max_usable_spawn_sites;
  2180.         }
  2181.         const site &operator[](std::size_t i) const
  2182.         {
  2183.                 return sites[i];
  2184.         }
  2185. };
  2186.  
  2187. }
  2188.  
  2189. //initialize the player object position & orientation (at start of game, or new ship)
  2190. static void InitPlayerPosition(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, int random_flag)
  2191. {
  2192.         auto &Objects = LevelUniqueObjectState.Objects;
  2193.         auto &vmobjptr = Objects.vmptr;
  2194.         reset_cruise();
  2195.         int NewPlayer=0;
  2196.  
  2197.         if (! ((Game_mode & GM_MULTI) && !(Game_mode&GM_MULTI_COOP)) ) // If not deathmatch
  2198.                 NewPlayer = Player_num;
  2199.         else if (random_flag == 1)
  2200.         {
  2201.                 const respawn_locations locations(vmobjptr, vcsegptridx);
  2202.                 if (!locations.get_usable_sites())
  2203.                         return;
  2204.                 uint_fast32_t trys=0;
  2205.                 d_srand(static_cast<fix>(timer_update()));
  2206.                 do {
  2207.                         trys++;
  2208.                         NewPlayer = d_rand() % locations.get_usable_sites();
  2209.                         const auto closest_dist = locations[NewPlayer].second;
  2210.                         if (closest_dist >= i2f(15*20))
  2211.                                 break;
  2212.                 } while (trys < MAX_PLAYERS * 2);
  2213.                 NewPlayer = locations[NewPlayer].first;
  2214.         }
  2215.         else {
  2216.                 // If deathmatch and not random, positions were already determined by sync packet
  2217.                 reset_player_object();
  2218.                 return;
  2219.         }
  2220.         Assert(NewPlayer >= 0);
  2221.         Assert(NewPlayer < NumNetPlayerPositions);
  2222.         ConsoleObject->pos = Player_init[NewPlayer].pos;
  2223.         ConsoleObject->orient = Player_init[NewPlayer].orient;
  2224.         obj_relink(vmobjptr, vmsegptr, vmobjptridx(ConsoleObject), vmsegptridx(Player_init[NewPlayer].segnum));
  2225.         reset_player_object();
  2226. }
  2227.  
  2228. //      -----------------------------------------------------------------------------------------------------
  2229. //      Initialize default parameters for one robot, copying from Robot_info to *objp.
  2230. //      What about setting size!?  Where does that come from?
  2231. void copy_defaults_to_robot(object_base &objp)
  2232. {
  2233.         assert(objp.type == OBJ_ROBOT);
  2234.         const unsigned objid = get_robot_id(objp);
  2235.         assert(objid < LevelSharedRobotInfoState.N_robot_types);
  2236.  
  2237.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2238.         auto &robptr = Robot_info[objid];
  2239.  
  2240.         //      Boost shield for Thief and Buddy based on level.
  2241.         fix shields = robptr.strength;
  2242.  
  2243. #if defined(DXX_BUILD_DESCENT_II)
  2244.         const auto &Difficulty_level = GameUniqueState.Difficulty_level;
  2245.         if ((robot_is_thief(robptr)) || (robot_is_companion(robptr))) {
  2246.                 shields = (shields * (abs(Current_level_num)+7))/8;
  2247.  
  2248.                 if (robot_is_companion(robptr)) {
  2249.                         //      Now, scale guide-bot hits by skill level
  2250.                         switch (Difficulty_level) {
  2251.                                 case 0: shields = i2f(20000);   break;          //      Trainee, basically unkillable
  2252.                                 case 1: shields *= 3;                           break;          //      Rookie, pretty dang hard
  2253.                                 case 2: shields *= 2;                           break;          //      Hotshot, a bit tough
  2254.                                 default:        break;
  2255.                         }
  2256.                 }
  2257.         } else if (robptr.boss_flag)    //      MK, 01/16/95, make boss shields lower on lower diff levels.
  2258.         {
  2259.         //      Additional wimpification of bosses at Trainee
  2260.                 shields = shields / (NDL + 3) * (Difficulty_level ? Difficulty_level + 4 : 2);
  2261.         }
  2262. #endif
  2263.         objp.shields = shields;
  2264. }
  2265.  
  2266. //      -----------------------------------------------------------------------------------------------------
  2267. //      Copy all values from the robot info structure to all instances of robots.
  2268. //      This allows us to change bitmaps.tbl and have these changes manifested in existing robots.
  2269. //      This function should be called at level load time.
  2270. static void copy_defaults_to_robot_all(void)
  2271. {
  2272.         auto &Objects = LevelUniqueObjectState.Objects;
  2273.         auto &vmobjptr = Objects.vmptr;
  2274.         range_for (object_base &objp, vmobjptr)
  2275.         {
  2276.                 if (objp.type == OBJ_ROBOT)
  2277.                         copy_defaults_to_robot(objp);
  2278.         }
  2279. }
  2280.  
  2281. //      -----------------------------------------------------------------------------------------------------
  2282. //called when the player is starting a level (new game or new ship)
  2283. static void StartLevel(int random_flag)
  2284. {
  2285.         auto &Objects = LevelUniqueObjectState.Objects;
  2286.         auto &vmobjptridx = Objects.vmptridx;
  2287.         assert(Player_dead_state == player_dead_state::no);
  2288.  
  2289.         InitPlayerPosition(vmobjptridx, vmsegptridx, random_flag);
  2290.  
  2291.         verify_console_object();
  2292.  
  2293.         ConsoleObject->control_type     = CT_FLYING;
  2294.         ConsoleObject->movement_type    = MT_PHYSICS;
  2295.  
  2296.         // create_player_appearance_effect(ConsoleObject);
  2297.         Do_appearance_effect = 1;
  2298.  
  2299.         if (Game_mode & GM_MULTI)
  2300.         {
  2301.                 if (Game_mode & GM_MULTI_COOP)
  2302.                         multi_send_score();
  2303.                 multi_send_reappear();
  2304.                 multi_do_protocol_frame(1, 1);
  2305.         }
  2306.         else // in Singleplayer, after we died ...
  2307.         {
  2308.                 disable_matcens(); // ... disable matcens and ...
  2309.                 clear_transient_objects(0); // ... clear all transient objects.
  2310.         }
  2311.  
  2312.         ai_reset_all_paths();
  2313.         ai_init_boss_for_ship();
  2314.  
  2315.         reset_rear_view();
  2316.         auto &player_info = ConsoleObject->ctype.player_info;
  2317.         player_info.Auto_fire_fusion_cannon_time = 0;
  2318.         player_info.Fusion_charge = 0;
  2319. }
  2320. }
  2321.