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.  * Save game information
  23.  *
  24.  */
  25.  
  26. #include <stdexcept>
  27. #include <stdio.h>
  28. #include <string.h>
  29. #include "pstypes.h"
  30. #include "strutil.h"
  31. #include "console.h"
  32. #include "key.h"
  33. #include "gr.h"
  34. #include "palette.h"
  35. #include "newmenu.h"
  36. #include "inferno.h"
  37. #if DXX_USE_EDITOR
  38. #include "editor/editor.h"
  39. #include "editor/esegment.h"
  40. #include "editor/eswitch.h"
  41. #endif
  42. #include "dxxerror.h"
  43. #include "object.h"
  44. #include "game.h"
  45. #include "gameseg.h"
  46. #include "screens.h"
  47. #include "wall.h"
  48. #include "gamemine.h"
  49. #include "robot.h"
  50. #include "bm.h"
  51. #include "menu.h"
  52. #include "fireball.h"
  53. #include "switch.h"
  54. #include "fuelcen.h"
  55. #include "cntrlcen.h"
  56. #include "powerup.h"
  57. #include "hostage.h"
  58. #include "weapon.h"
  59. #include "player.h"
  60. #include "newdemo.h"
  61. #include "gameseq.h"
  62. #include "automap.h"
  63. #include "polyobj.h"
  64. #include "text.h"
  65. #include "gamefont.h"
  66. #include "gamesave.h"
  67. #include "gamepal.h"
  68. #include "physics.h"
  69. #include "laser.h"
  70. #include "multi.h"
  71. #include "makesig.h"
  72. #include "textures.h"
  73. #include "d_enumerate.h"
  74. #include "d_range.h"
  75.  
  76. #include "dxxsconf.h"
  77. #include "compiler-range_for.h"
  78. #include "d_zip.h"
  79. #include "partial_range.h"
  80.  
  81. #if defined(DXX_BUILD_DESCENT_I)
  82. #if DXX_USE_EDITOR
  83. const char Shareware_level_names[NUM_SHAREWARE_LEVELS][12] = {
  84.         "level01.rdl",
  85.         "level02.rdl",
  86.         "level03.rdl",
  87.         "level04.rdl",
  88.         "level05.rdl",
  89.         "level06.rdl",
  90.         "level07.rdl"
  91. };
  92.  
  93. const char Registered_level_names[NUM_REGISTERED_LEVELS][12] = {
  94.         "level08.rdl",
  95.         "level09.rdl",
  96.         "level10.rdl",
  97.         "level11.rdl",
  98.         "level12.rdl",
  99.         "level13.rdl",
  100.         "level14.rdl",
  101.         "level15.rdl",
  102.         "level16.rdl",
  103.         "level17.rdl",
  104.         "level18.rdl",
  105.         "level19.rdl",
  106.         "level20.rdl",
  107.         "level21.rdl",
  108.         "level22.rdl",
  109.         "level23.rdl",
  110.         "level24.rdl",
  111.         "level25.rdl",
  112.         "level26.rdl",
  113.         "level27.rdl",
  114.         "levels1.rdl",
  115.         "levels2.rdl",
  116.         "levels3.rdl"
  117. };
  118. #endif
  119. #endif
  120.  
  121. char Gamesave_current_filename[PATH_MAX];
  122.  
  123. int Gamesave_current_version;
  124.  
  125. #if defined(DXX_BUILD_DESCENT_I)
  126. #define GAME_VERSION                                    25
  127. #elif defined(DXX_BUILD_DESCENT_II)
  128. #define GAME_VERSION            32
  129. #endif
  130. #define GAME_COMPATIBLE_VERSION 22
  131.  
  132. //version 28->29  add delta light support
  133. //version 27->28  controlcen id now is reactor number, not model number
  134. //version 28->29  ??
  135. //version 29->30  changed trigger structure
  136. //version 30->31  changed trigger structure some more
  137. //version 31->32  change segment structure, make it 512 bytes w/o editor, add Segment2s array.
  138.  
  139. #define MENU_CURSOR_X_MIN       MENU_X
  140. #define MENU_CURSOR_X_MAX       MENU_X+6
  141.  
  142. int Gamesave_num_org_robots = 0;
  143. //--unused-- grs_bitmap * Gamesave_saved_bitmap = NULL;
  144.  
  145. #if DXX_USE_EDITOR
  146. // Return true if this level has a name of the form "level??"
  147. // Note that a pathspec can appear at the beginning of the filename.
  148. static int is_real_level(const char *filename)
  149. {
  150.         int len = strlen(filename);
  151.  
  152.         if (len < 6)
  153.                 return 0;
  154.  
  155.         return !d_strnicmp(&filename[len-11], "level");
  156.  
  157. }
  158. #endif
  159.  
  160. //--unused-- vms_angvec zero_angles={0,0,0};
  161.  
  162. int Gamesave_num_players=0;
  163.  
  164. #if defined(DXX_BUILD_DESCENT_I)
  165. #define MAX_POLYGON_MODELS_NEW 167
  166. static std::array<char[FILENAME_LEN], MAX_POLYGON_MODELS_NEW> Save_pof_names;
  167.  
  168. static int convert_vclip(const d_vclip_array &Vclip, int vc)
  169. {
  170.         if (vc < 0)
  171.                 return vc;
  172.         if (vc < Vclip.size() && (Vclip[vc].num_frames != ~0u))
  173.                 return vc;
  174.         return 0;
  175. }
  176. static int convert_wclip(int wc) {
  177.         return (wc < Num_wall_anims) ? wc : wc % Num_wall_anims;
  178. }
  179. int convert_tmap(int tmap)
  180. {
  181.         if (tmap == -1)
  182.                 return tmap;
  183.     return (tmap >= NumTextures) ? tmap % NumTextures : tmap;
  184. }
  185. static int convert_polymod(int polymod) {
  186.     return (polymod >= N_polygon_models) ? polymod % N_polygon_models : polymod;
  187. }
  188. #elif defined(DXX_BUILD_DESCENT_II)
  189. static std::array<char[FILENAME_LEN], MAX_POLYGON_MODELS> Save_pof_names;
  190. #endif
  191.  
  192. namespace dsx {
  193.  
  194. static void verify_object(const d_vclip_array &Vclip, object &obj)
  195. {
  196.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  197.         obj.lifeleft = IMMORTAL_TIME;           //all loaded object are immortal, for now
  198.  
  199.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  200.         if (obj.type == OBJ_ROBOT)
  201.         {
  202.                 Gamesave_num_org_robots++;
  203.  
  204.                 // Make sure valid id...
  205.                 const auto N_robot_types = LevelSharedRobotInfoState.N_robot_types;
  206.                 if (get_robot_id(obj) >= N_robot_types )
  207.                         set_robot_id(obj, get_robot_id(obj) % N_robot_types);
  208.  
  209.                 // Make sure model number & size are correct...
  210.                 if (obj.render_type == RT_POLYOBJ)
  211.                 {
  212.                         auto &ri = Robot_info[get_robot_id(obj)];
  213. #if defined(DXX_BUILD_DESCENT_II)
  214.                         assert(ri.model_num != -1);
  215.                                 //if you fail this assert, it means that a robot in this level
  216.                                 //hasn't been loaded, possibly because he's marked as
  217.                                 //non-shareware.  To see what robot number, print obj.id.
  218.  
  219.                         assert(ri.always_0xabcd == 0xabcd);
  220.                                 //if you fail this assert, it means that the robot_ai for
  221.                                 //a robot in this level hasn't been loaded, possibly because
  222.                                 //it's marked as non-shareware.  To see what robot number,
  223.                                 //print obj.id.
  224. #endif
  225.  
  226.                         obj.rtype.pobj_info.model_num = ri.model_num;
  227.                         obj.size = Polygon_models[obj.rtype.pobj_info.model_num].rad;
  228.  
  229.                         //@@Took out this ugly hack 1/12/96, because Mike has added code
  230.                         //@@that should fix it in a better way.
  231.                         //@@//this is a super-ugly hack.  Since the baby stripe robots have
  232.                         //@@//their firing point on their bounding sphere, the firing points
  233.                         //@@//can poke through a wall if the robots are very close to it. So
  234.                         //@@//we make their radii bigger so the guns can't get too close to
  235.                         //@@//the walls
  236.                         //@@if (Robot_info[obj.id].flags & RIF_BIG_RADIUS)
  237.                         //@@    obj.size = (obj.size*3)/2;
  238.  
  239.                         //@@if (obj.control_type==CT_AI && Robot_info[obj.id].attack_type)
  240.                         //@@    obj.size = obj.size*3/4;
  241.                 }
  242.  
  243. #if defined(DXX_BUILD_DESCENT_II)
  244.                 if (get_robot_id(obj) == 65)                                            //special "reactor" robots
  245.                         obj.movement_type = MT_NONE;
  246. #endif
  247.  
  248.                 if (obj.movement_type == MT_PHYSICS)
  249.                 {
  250.                         auto &ri = Robot_info[get_robot_id(obj)];
  251.                         obj.mtype.phys_info.mass = ri.mass;
  252.                         obj.mtype.phys_info.drag = ri.drag;
  253.                 }
  254.         }
  255.         else {          //Robots taken care of above
  256.                 if (obj.render_type == RT_POLYOBJ)
  257.                 {
  258.                         char *name = Save_pof_names[obj.rtype.pobj_info.model_num];
  259.                         for (uint_fast32_t i = 0;i < N_polygon_models;i++)
  260.                                 if (!d_stricmp(Pof_names[i],name)) {            //found it!    
  261.                                         obj.rtype.pobj_info.model_num = i;
  262.                                         break;
  263.                                 }
  264.                 }
  265.         }
  266.  
  267.         if (obj.type == OBJ_POWERUP)
  268.         {
  269.                 if ( get_powerup_id(obj) >= N_powerup_types )   {
  270.                         set_powerup_id(Powerup_info, Vclip, obj, POW_SHIELD_BOOST);
  271.                         Assert( obj.render_type != RT_POLYOBJ );
  272.                 }
  273.                 obj.control_type = CT_POWERUP;
  274.                 obj.size = Powerup_info[get_powerup_id(obj)].size;
  275.                 obj.ctype.powerup_info.creation_time = 0;
  276.         }
  277.  
  278.         if (obj.type == OBJ_WEAPON)
  279.         {
  280.                 if ( get_weapon_id(obj) >= N_weapon_types )     {
  281.                         set_weapon_id(obj, weapon_id_type::LASER_ID_L1);
  282.                         Assert( obj.render_type != RT_POLYOBJ );
  283.                 }
  284.  
  285. #if defined(DXX_BUILD_DESCENT_II)
  286.                 const auto weapon_id = get_weapon_id(obj);
  287.                 if (weapon_id == weapon_id_type::PMINE_ID)
  288.                 {               //make sure pmines have correct values
  289.                         obj.mtype.phys_info.mass = Weapon_info[weapon_id].mass;
  290.                         obj.mtype.phys_info.drag = Weapon_info[weapon_id].drag;
  291.                         obj.mtype.phys_info.flags |= PF_FREE_SPINNING;
  292.  
  293.                         // Make sure model number & size are correct...        
  294.                         Assert( obj.render_type == RT_POLYOBJ );
  295.  
  296.                         obj.rtype.pobj_info.model_num = Weapon_info[weapon_id].model_num;
  297.                         obj.size = Polygon_models[obj.rtype.pobj_info.model_num].rad;
  298.                 }
  299. #endif
  300.         }
  301.  
  302.         if (obj.type == OBJ_CNTRLCEN)
  303.         {
  304.                 obj.render_type = RT_POLYOBJ;
  305.                 obj.control_type = CT_CNTRLCEN;
  306.  
  307. #if defined(DXX_BUILD_DESCENT_I)
  308.                 // Make model number is correct...     
  309.                 for (int i=0; i<Num_total_object_types; i++ )  
  310.                         if ( ObjType[i] == OL_CONTROL_CENTER )          {
  311.                                 obj.rtype.pobj_info.model_num = ObjId[i];
  312.                                 obj.shields = ObjStrength[i];
  313.                                 break;         
  314.                         }
  315. #elif defined(DXX_BUILD_DESCENT_II)
  316.                 if (Gamesave_current_version <= 1) { // descent 1 reactor
  317.                         set_reactor_id(obj, 0);                         // used to be only one kind of reactor
  318.                         obj.rtype.pobj_info.model_num = Reactors[0].model_num;// descent 1 reactor
  319.                 }
  320.  
  321.                 // Make sure model number is correct...
  322.                 //obj.rtype.pobj_info.model_num = Reactors[obj.id].model_num;
  323. #endif
  324.         }
  325.  
  326.         if (obj.type == OBJ_PLAYER)
  327.         {
  328.                 //int i;
  329.  
  330.                 //Assert(obj == Player);
  331.  
  332.                 if (&obj == ConsoleObject)
  333.                         init_player_object();
  334.                 else
  335.                         if (obj.render_type == RT_POLYOBJ)      //recover from Matt's pof file matchup bug
  336.                                 obj.rtype.pobj_info.model_num = Player_ship->model_num;
  337.  
  338.                 //Make sure orient matrix is orthogonal
  339.                 check_and_fix_matrix(obj.orient);
  340.  
  341.                 set_player_id(obj, Gamesave_num_players++);
  342.         }
  343.  
  344.         if (obj.type == OBJ_HOSTAGE)
  345.         {
  346.                 obj.render_type = RT_HOSTAGE;
  347.                 obj.control_type = CT_POWERUP;
  348.         }
  349. }
  350.  
  351. }
  352.  
  353. //static gs_skip(int len,PHYSFS_File *file)
  354. //{
  355. //
  356. //      PHYSFSX_fseek(file,len,SEEK_CUR);
  357. //}
  358.  
  359. //reads one object of the given version from the given file
  360. namespace dsx {
  361. static void read_object(const vmobjptr_t obj,PHYSFS_File *f,int version)
  362. {
  363.         const auto poison_obj = reinterpret_cast<uint8_t *>(&*obj);
  364.         DXX_POISON_MEMORY(poison_obj, sizeof(*obj), 0xfd);
  365.         obj->signature = object_signature_t{0};
  366.         set_object_type(*obj, PHYSFSX_readByte(f));
  367.         obj->id             = PHYSFSX_readByte(f);
  368.  
  369.         if (obj->type == OBJ_ROBOT)
  370.         {
  371. #if defined(DXX_BUILD_DESCENT_I)
  372.                 const auto id = get_robot_id(obj);
  373.                 if (id > 23)
  374.                         set_robot_id(obj, id % 24);
  375. #endif
  376.                 obj->matcen_creator = 0;
  377.         }
  378.         obj->control_type   = PHYSFSX_readByte(f);
  379.         set_object_movement_type(*obj, PHYSFSX_readByte(f));
  380.         const uint8_t render_type = PHYSFSX_readByte(f);
  381.         if (valid_render_type(render_type))
  382.                 obj->render_type = render_type_t{render_type};
  383.         else
  384.         {
  385.                 LevelError("Level contains bogus render type %#x for object %p; using none instead", render_type, &*obj);
  386.                 obj->render_type = RT_NONE;
  387.         }
  388.         obj->flags          = PHYSFSX_readByte(f);
  389.  
  390.         obj->segnum         = PHYSFSX_readShort(f);
  391.         obj->attached_obj   = object_none;
  392.  
  393.         PHYSFSX_readVector(f, obj->pos);
  394.         PHYSFSX_readMatrix(&obj->orient,f);
  395.  
  396.         obj->size           = PHYSFSX_readFix(f);
  397.         obj->shields        = PHYSFSX_readFix(f);
  398.  
  399.         {
  400.                 vms_vector last_pos;
  401.                 PHYSFSX_readVector(f, last_pos);
  402.         }
  403.  
  404.         obj->contains_type  = PHYSFSX_readByte(f);
  405.         obj->contains_id    = PHYSFSX_readByte(f);
  406.         obj->contains_count = PHYSFSX_readByte(f);
  407.  
  408.         switch (obj->movement_type) {
  409.  
  410.                 case MT_PHYSICS:
  411.  
  412.                         PHYSFSX_readVector(f, obj->mtype.phys_info.velocity);
  413.                         PHYSFSX_readVector(f, obj->mtype.phys_info.thrust);
  414.  
  415.                         obj->mtype.phys_info.mass               = PHYSFSX_readFix(f);
  416.                         obj->mtype.phys_info.drag               = PHYSFSX_readFix(f);
  417.                         PHYSFSX_readFix(f);     /* brakes */
  418.  
  419.                         PHYSFSX_readVector(f, obj->mtype.phys_info.rotvel);
  420.                         PHYSFSX_readVector(f, obj->mtype.phys_info.rotthrust);
  421.  
  422.                         obj->mtype.phys_info.turnroll   = PHYSFSX_readFixAng(f);
  423.                         obj->mtype.phys_info.flags              = PHYSFSX_readShort(f);
  424.  
  425.                         break;
  426.  
  427.                 case MT_SPINNING:
  428.  
  429.                         PHYSFSX_readVector(f, obj->mtype.spin_rate);
  430.                         break;
  431.  
  432.                 case MT_NONE:
  433.                         break;
  434.  
  435.                 default:
  436.                         Int3();
  437.         }
  438.  
  439.         switch (obj->control_type) {
  440.  
  441.                 case CT_AI: {
  442.                         obj->ctype.ai_info.behavior                             = static_cast<ai_behavior>(PHYSFSX_readByte(f));
  443.  
  444.                         range_for (auto &i, obj->ctype.ai_info.flags)
  445.                                 i = PHYSFSX_readByte(f);
  446.  
  447.                         obj->ctype.ai_info.hide_segment                 = PHYSFSX_readShort(f);
  448.                         obj->ctype.ai_info.hide_index                   = PHYSFSX_readShort(f);
  449.                         obj->ctype.ai_info.path_length                  = PHYSFSX_readShort(f);
  450.                         obj->ctype.ai_info.cur_path_index               = PHYSFSX_readShort(f);
  451.  
  452.                         if (version <= 25) {
  453.                                 PHYSFSX_readShort(f);   //                              obj->ctype.ai_info.follow_path_start_seg        =
  454.                                 PHYSFSX_readShort(f);   //                              obj->ctype.ai_info.follow_path_end_seg          =
  455.                         }
  456.  
  457.                         break;
  458.                 }
  459.  
  460.                 case CT_EXPLOSION:
  461.  
  462.                         obj->ctype.expl_info.spawn_time         = PHYSFSX_readFix(f);
  463.                         obj->ctype.expl_info.delete_time                = PHYSFSX_readFix(f);
  464.                         obj->ctype.expl_info.delete_objnum      = PHYSFSX_readShort(f);
  465.                         obj->ctype.expl_info.next_attach = obj->ctype.expl_info.prev_attach = obj->ctype.expl_info.attach_parent = object_none;
  466.  
  467.                         break;
  468.  
  469.                 case CT_WEAPON:
  470.  
  471.                         //do I really need to read these?  Are they even saved to disk?
  472.  
  473.                         obj->ctype.laser_info.parent_type               = PHYSFSX_readShort(f);
  474.                         obj->ctype.laser_info.parent_num                = PHYSFSX_readShort(f);
  475.                         obj->ctype.laser_info.parent_signature  = object_signature_t{static_cast<uint16_t>(PHYSFSX_readInt(f))};
  476. #if defined(DXX_BUILD_DESCENT_II)
  477.                         obj->ctype.laser_info.last_afterburner_time = 0;
  478. #endif
  479.                         obj->ctype.laser_info.clear_hitobj();
  480.  
  481.                         break;
  482.  
  483.                 case CT_LIGHT:
  484.  
  485.                         obj->ctype.light_info.intensity = PHYSFSX_readFix(f);
  486.                         break;
  487.  
  488.                 case CT_POWERUP:
  489.  
  490.                         if (version >= 25)
  491.                                 obj->ctype.powerup_info.count = PHYSFSX_readInt(f);
  492.                         else
  493.                                 obj->ctype.powerup_info.count = 1;
  494.  
  495.                         if (obj->type == OBJ_POWERUP)
  496.                         {
  497.                                 /* Objects loaded from a level file were not ejected by
  498.                                  * the player.
  499.                                  */
  500.                                 obj->ctype.powerup_info.flags = 0;
  501.                                 /* Hostages have control type CT_POWERUP, but object
  502.                                  * type OBJ_HOSTAGE.  Hostages are never weapons, so
  503.                                  * prevent checking their IDs.
  504.                                  */
  505.                         if (get_powerup_id(obj) == POW_VULCAN_WEAPON)
  506.                                         obj->ctype.powerup_info.count = VULCAN_WEAPON_AMMO_AMOUNT;
  507.  
  508. #if defined(DXX_BUILD_DESCENT_II)
  509.                         else if (get_powerup_id(obj) == POW_GAUSS_WEAPON)
  510.                                         obj->ctype.powerup_info.count = VULCAN_WEAPON_AMMO_AMOUNT;
  511.  
  512.                         else if (get_powerup_id(obj) == POW_OMEGA_WEAPON)
  513.                                         obj->ctype.powerup_info.count = MAX_OMEGA_CHARGE;
  514. #endif
  515.                         }
  516.  
  517.                         break;
  518.  
  519.  
  520.                 case CT_NONE:
  521.                 case CT_FLYING:
  522.                 case CT_DEBRIS:
  523.                         break;
  524.  
  525.                 case CT_SLEW:           //the player is generally saved as slew
  526.                         break;
  527.  
  528.                 case CT_CNTRLCEN:
  529.                         break;
  530.  
  531.                 case CT_MORPH:
  532.                 case CT_FLYTHROUGH:
  533.                 case CT_REPAIRCEN:
  534.                 default:
  535.                         Int3();
  536.        
  537.         }
  538.  
  539.         switch (obj->render_type) {
  540.  
  541.                 case RT_NONE:
  542.                         break;
  543.  
  544.                 case RT_MORPH:
  545.                 case RT_POLYOBJ: {
  546.                         int tmo;
  547.  
  548. #if defined(DXX_BUILD_DESCENT_I)
  549.                         obj->rtype.pobj_info.model_num          = convert_polymod(PHYSFSX_readInt(f));
  550. #elif defined(DXX_BUILD_DESCENT_II)
  551.                         obj->rtype.pobj_info.model_num          = PHYSFSX_readInt(f);
  552. #endif
  553.  
  554.                         range_for (auto &i, obj->rtype.pobj_info.anim_angles)
  555.                                 PHYSFSX_readAngleVec(&i, f);
  556.  
  557.                         obj->rtype.pobj_info.subobj_flags       = PHYSFSX_readInt(f);
  558.  
  559.                         tmo = PHYSFSX_readInt(f);
  560.  
  561. #if !DXX_USE_EDITOR
  562. #if defined(DXX_BUILD_DESCENT_I)
  563.                         obj->rtype.pobj_info.tmap_override      = convert_tmap(tmo);
  564. #elif defined(DXX_BUILD_DESCENT_II)
  565.                         obj->rtype.pobj_info.tmap_override      = tmo;
  566. #endif
  567.                         #else
  568.                         if (tmo==-1)
  569.                                 obj->rtype.pobj_info.tmap_override      = -1;
  570.                         else {
  571.                                 int xlated_tmo = tmap_xlate_table[tmo];
  572.                                 if (xlated_tmo < 0)     {
  573.                                         Int3();
  574.                                         xlated_tmo = 0;
  575.                                 }
  576.                                 obj->rtype.pobj_info.tmap_override      = xlated_tmo;
  577.                         }
  578.                         #endif
  579.  
  580.                         obj->rtype.pobj_info.alt_textures       = 0;
  581.  
  582.                         break;
  583.                 }
  584.  
  585.                 case RT_WEAPON_VCLIP:
  586.                 case RT_HOSTAGE:
  587.                 case RT_POWERUP:
  588.                 case RT_FIREBALL:
  589.  
  590. #if defined(DXX_BUILD_DESCENT_I)
  591.                         obj->rtype.vclip_info.vclip_num = convert_vclip(Vclip, PHYSFSX_readInt(f));
  592. #elif defined(DXX_BUILD_DESCENT_II)
  593.                         obj->rtype.vclip_info.vclip_num = PHYSFSX_readInt(f);
  594. #endif
  595.                         obj->rtype.vclip_info.frametime = PHYSFSX_readFix(f);
  596.                         obj->rtype.vclip_info.framenum  = PHYSFSX_readByte(f);
  597.  
  598.                         break;
  599.  
  600.                 case RT_LASER:
  601.                         break;
  602.  
  603.                 default:
  604.                         Int3();
  605.  
  606.         }
  607.  
  608. }
  609. }
  610.  
  611. #if DXX_USE_EDITOR
  612. static int PHYSFSX_writeMatrix(PHYSFS_File *file, const vms_matrix &m)
  613. {
  614.         if (PHYSFSX_writeVector(file, m.rvec) < 1 ||
  615.                 PHYSFSX_writeVector(file, m.uvec) < 1 ||
  616.                 PHYSFSX_writeVector(file, m.fvec) < 1)
  617.                 return 0;
  618.         return 1;
  619. }
  620.  
  621. static int PHYSFSX_writeAngleVec(PHYSFS_File *file, const vms_angvec &v)
  622. {
  623.         if (PHYSFSX_writeFixAng(file, v.p) < 1 ||
  624.                 PHYSFSX_writeFixAng(file, v.b) < 1 ||
  625.                 PHYSFSX_writeFixAng(file, v.h) < 1)
  626.                 return 0;
  627.         return 1;
  628. }
  629.  
  630. //writes one object to the given file
  631. namespace dsx {
  632. static void write_object(const object &obj, short version, PHYSFS_File *f)
  633. {
  634. #if defined(DXX_BUILD_DESCENT_I)
  635.         (void)version;
  636. #endif
  637.         PHYSFSX_writeU8(f, obj.type);
  638.         PHYSFSX_writeU8(f, obj.id);
  639.  
  640.         PHYSFSX_writeU8(f, obj.control_type);
  641.         PHYSFSX_writeU8(f, obj.movement_type);
  642.         PHYSFSX_writeU8(f, obj.render_type);
  643.         PHYSFSX_writeU8(f, obj.flags);
  644.  
  645.         PHYSFS_writeSLE16(f, obj.segnum);
  646.  
  647.         PHYSFSX_writeVector(f, obj.pos);
  648.         PHYSFSX_writeMatrix(f, obj.orient);
  649.  
  650.         PHYSFSX_writeFix(f, obj.size);
  651.         PHYSFSX_writeFix(f, obj.shields);
  652.  
  653.         PHYSFSX_writeVector(f, obj.pos);
  654.  
  655.         PHYSFSX_writeU8(f, obj.contains_type);
  656.         PHYSFSX_writeU8(f, obj.contains_id);
  657.         PHYSFSX_writeU8(f, obj.contains_count);
  658.  
  659.         switch (obj.movement_type) {
  660.  
  661.                 case MT_PHYSICS:
  662.  
  663.                         PHYSFSX_writeVector(f, obj.mtype.phys_info.velocity);
  664.                         PHYSFSX_writeVector(f, obj.mtype.phys_info.thrust);
  665.  
  666.                         PHYSFSX_writeFix(f, obj.mtype.phys_info.mass);
  667.                         PHYSFSX_writeFix(f, obj.mtype.phys_info.drag);
  668.                         PHYSFSX_writeFix(f, 0); /* brakes */
  669.  
  670.                         PHYSFSX_writeVector(f, obj.mtype.phys_info.rotvel);
  671.                         PHYSFSX_writeVector(f, obj.mtype.phys_info.rotthrust);
  672.  
  673.                         PHYSFSX_writeFixAng(f, obj.mtype.phys_info.turnroll);
  674.                         PHYSFS_writeSLE16(f, obj.mtype.phys_info.flags);
  675.  
  676.                         break;
  677.  
  678.                 case MT_SPINNING:
  679.  
  680.                         PHYSFSX_writeVector(f, obj.mtype.spin_rate);
  681.                         break;
  682.  
  683.                 case MT_NONE:
  684.                         break;
  685.  
  686.                 default:
  687.                         Int3();
  688.         }
  689.  
  690.         switch (obj.control_type) {
  691.  
  692.                 case CT_AI: {
  693.                         PHYSFSX_writeU8(f, static_cast<uint8_t>(obj.ctype.ai_info.behavior));
  694.  
  695.                         range_for (auto &i, obj.ctype.ai_info.flags)
  696.                                 PHYSFSX_writeU8(f, i);
  697.  
  698.                         PHYSFS_writeSLE16(f, obj.ctype.ai_info.hide_segment);
  699.                         PHYSFS_writeSLE16(f, obj.ctype.ai_info.hide_index);
  700.                         PHYSFS_writeSLE16(f, obj.ctype.ai_info.path_length);
  701.                         PHYSFS_writeSLE16(f, obj.ctype.ai_info.cur_path_index);
  702.  
  703. #if defined(DXX_BUILD_DESCENT_I)
  704.                         PHYSFS_writeSLE16(f, segment_none);
  705.                         PHYSFS_writeSLE16(f, segment_none);
  706. #elif defined(DXX_BUILD_DESCENT_II)
  707.                         if (version <= 25)
  708.                         {
  709.                                 PHYSFS_writeSLE16(f, -1);       //obj.ctype.ai_info.follow_path_start_seg
  710.                                 PHYSFS_writeSLE16(f, -1);       //obj.ctype.ai_info.follow_path_end_seg
  711.                         }
  712. #endif
  713.  
  714.                         break;
  715.                 }
  716.  
  717.                 case CT_EXPLOSION:
  718.  
  719.                         PHYSFSX_writeFix(f, obj.ctype.expl_info.spawn_time);
  720.                         PHYSFSX_writeFix(f, obj.ctype.expl_info.delete_time);
  721.                         PHYSFS_writeSLE16(f, obj.ctype.expl_info.delete_objnum);
  722.  
  723.                         break;
  724.  
  725.                 case CT_WEAPON:
  726.  
  727.                         //do I really need to write these objects?
  728.  
  729.                         PHYSFS_writeSLE16(f, obj.ctype.laser_info.parent_type);
  730.                         PHYSFS_writeSLE16(f, obj.ctype.laser_info.parent_num);
  731.                         PHYSFS_writeSLE32(f, obj.ctype.laser_info.parent_signature.get());
  732.  
  733.                         break;
  734.  
  735.                 case CT_LIGHT:
  736.  
  737.                         PHYSFSX_writeFix(f, obj.ctype.light_info.intensity);
  738.                         break;
  739.  
  740.                 case CT_POWERUP:
  741.  
  742. #if defined(DXX_BUILD_DESCENT_I)
  743.                         PHYSFS_writeSLE32(f, obj.ctype.powerup_info.count);
  744. #elif defined(DXX_BUILD_DESCENT_II)
  745.                         if (version >= 25)
  746.                                 PHYSFS_writeSLE32(f, obj.ctype.powerup_info.count);
  747. #endif
  748.                         break;
  749.  
  750.                 case CT_NONE:
  751.                 case CT_FLYING:
  752.                 case CT_DEBRIS:
  753.                         break;
  754.  
  755.                 case CT_SLEW:           //the player is generally saved as slew
  756.                         break;
  757.  
  758.                 case CT_CNTRLCEN:
  759.                         break;                  //control center object.
  760.  
  761.                 case CT_MORPH:
  762.                 case CT_REPAIRCEN:
  763.                 case CT_FLYTHROUGH:
  764.                 default:
  765.                         Int3();
  766.        
  767.         }
  768.  
  769.         switch (obj.render_type) {
  770.  
  771.                 case RT_NONE:
  772.                         break;
  773.  
  774.                 case RT_MORPH:
  775.                 case RT_POLYOBJ: {
  776.                         PHYSFS_writeSLE32(f, obj.rtype.pobj_info.model_num);
  777.  
  778.                         range_for (auto &i, obj.rtype.pobj_info.anim_angles)
  779.                                 PHYSFSX_writeAngleVec(f, i);
  780.  
  781.                         PHYSFS_writeSLE32(f, obj.rtype.pobj_info.subobj_flags);
  782.  
  783.                         PHYSFS_writeSLE32(f, obj.rtype.pobj_info.tmap_override);
  784.  
  785.                         break;
  786.                 }
  787.  
  788.                 case RT_WEAPON_VCLIP:
  789.                 case RT_HOSTAGE:
  790.                 case RT_POWERUP:
  791.                 case RT_FIREBALL:
  792.  
  793.                         PHYSFS_writeSLE32(f, obj.rtype.vclip_info.vclip_num);
  794.                         PHYSFSX_writeFix(f, obj.rtype.vclip_info.frametime);
  795.                         PHYSFSX_writeU8(f, obj.rtype.vclip_info.framenum);
  796.  
  797.                         break;
  798.  
  799.                 case RT_LASER:
  800.                         break;
  801.  
  802.                 default:
  803.                         Int3();
  804.  
  805.         }
  806.  
  807. }
  808. }
  809. #endif
  810.  
  811. // --------------------------------------------------------------------
  812. // Load game
  813. // Loads all the relevant data for a level.
  814. // If level != -1, it loads the filename with extension changed to .min
  815. // Otherwise it loads the appropriate level mine.
  816. // returns 0=everything ok, 1=old version, -1=error
  817. namespace dsx {
  818.  
  819. static void validate_segment_wall(const vcsegptridx_t seg, shared_side &side, const unsigned sidenum)
  820. {
  821.         auto &rwn0 = side.wall_num;
  822.         const auto wn0 = rwn0;
  823.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  824.         auto &vcwallptr = Walls.vcptr;
  825.         auto &w0 = *vcwallptr(wn0);
  826.         switch (w0.type)
  827.         {
  828.                 case WALL_DOOR:
  829.                         {
  830.                                 const auto connected_seg = seg->children[sidenum];
  831.                                 if (connected_seg == segment_none)
  832.                                 {
  833.                                         rwn0 = wall_none;
  834.                                         LevelError("segment %u side %u wall %u has no child segment; removing orphan wall.", seg.get_unchecked_index(), sidenum, wn0);
  835.                                         return;
  836.                                 }
  837.                                 const shared_segment &vcseg = *vcsegptr(connected_seg);
  838.                                 const unsigned connected_side = find_connect_side(seg, vcseg);
  839.                                 const auto wn1 = vcseg.sides[connected_side].wall_num;
  840.                                 if (wn1 == wall_none)
  841.                                 {
  842.                                         rwn0 = wall_none;
  843.                                         LevelError("segment %u side %u wall %u has child segment %u side %u, but no wall; removing orphan wall.", seg.get_unchecked_index(), sidenum, wn0, connected_seg, connected_side);
  844.                                         return;
  845.                                 }
  846.                         }
  847.                         break;
  848.                 default:
  849.                         break;
  850.         }
  851. }
  852.  
  853. static int load_game_data(
  854. #if defined(DXX_BUILD_DESCENT_II)
  855.         d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
  856. #endif
  857.         fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, PHYSFS_File *LoadFile)
  858. {
  859.         auto &Objects = LevelUniqueObjectState.Objects;
  860.         auto &vmobjptr = Objects.vmptr;
  861.         auto &WallAnims = GameSharedState.WallAnims;
  862.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  863.         const auto &vcsegptridx = vmsegptridx;
  864.         short game_top_fileinfo_version;
  865.         int object_offset;
  866.         unsigned gs_num_objects;
  867.         int trig_size;
  868.  
  869.         //===================== READ FILE INFO ========================
  870.  
  871.         // Check signature
  872.         if (PHYSFSX_readShort(LoadFile) != 0x6705)
  873.                 return -1;
  874.  
  875.         // Read and check version number
  876.         game_top_fileinfo_version = PHYSFSX_readShort(LoadFile);
  877.         if (game_top_fileinfo_version < GAME_COMPATIBLE_VERSION )
  878.                 return -1;
  879.  
  880.         // We skip some parts of the former game_top_fileinfo
  881.         PHYSFSX_fseek(LoadFile, 31, SEEK_CUR);
  882.  
  883.         object_offset = PHYSFSX_readInt(LoadFile);
  884.         gs_num_objects = PHYSFSX_readInt(LoadFile);
  885.         PHYSFSX_fseek(LoadFile, 8, SEEK_CUR);
  886.  
  887.         init_exploding_walls();
  888.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  889.         Walls.set_count(PHYSFSX_readInt(LoadFile));
  890.         PHYSFSX_fseek(LoadFile, 20, SEEK_CUR);
  891.  
  892.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  893.         Triggers.set_count(PHYSFSX_readInt(LoadFile));
  894.         PHYSFSX_fseek(LoadFile, 24, SEEK_CUR);
  895.  
  896.         trig_size = PHYSFSX_readInt(LoadFile);
  897.         Assert(trig_size == sizeof(ControlCenterTriggers));
  898.         (void)trig_size;
  899.         PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
  900.  
  901.         const unsigned Num_robot_centers = PHYSFSX_readInt(LoadFile);
  902.         LevelSharedRobotcenterState.Num_robot_centers = Num_robot_centers;
  903.         PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
  904.  
  905. #if defined(DXX_BUILD_DESCENT_I)
  906. #elif defined(DXX_BUILD_DESCENT_II)
  907.         unsigned num_delta_lights;
  908.         unsigned Num_static_lights;
  909.         if (game_top_fileinfo_version >= 29) {
  910.                 PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
  911.                 Num_static_lights = PHYSFSX_readInt(LoadFile);
  912.                 PHYSFSX_fseek(LoadFile, 8, SEEK_CUR);
  913.                 num_delta_lights = PHYSFSX_readInt(LoadFile);
  914.                 PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
  915.         } else {
  916.                 Num_static_lights = 0;
  917.                 num_delta_lights = 0;
  918.         }
  919. #endif
  920.  
  921.         if (game_top_fileinfo_version >= 31) //load mine filename
  922.                 // read newline-terminated string, not sure what version this changed.
  923.                 PHYSFSX_fgets(Current_level_name,LoadFile);
  924.         else if (game_top_fileinfo_version >= 14) { //load mine filename
  925.                 // read null-terminated string
  926.                 //must do read one char at a time, since no PHYSFSX_fgets()
  927.                 for (auto p = Current_level_name.next().begin(); (*p = PHYSFSX_fgetc(LoadFile));)
  928.                 {
  929.                         if (++p == Current_level_name.line().end())
  930.                         {
  931.                                 p[-1] = 0;
  932.                                 while (PHYSFSX_fgetc(LoadFile))
  933.                                         ;
  934.                                 break;
  935.                         }
  936.                 }
  937.         }
  938.         else
  939.                 Current_level_name.next()[0]=0;
  940.  
  941.         if (game_top_fileinfo_version >= 19) {  //load pof names
  942.                 const unsigned N_save_pof_names = PHYSFSX_readShort(LoadFile);
  943.                 if (N_save_pof_names < MAX_POLYGON_MODELS)
  944.                         PHYSFS_read(LoadFile,Save_pof_names,N_save_pof_names,FILENAME_LEN);
  945.                 else
  946.                         LevelError("Level contains bogus N_save_pof_names %#x; ignoring", N_save_pof_names);
  947.         }
  948.  
  949.         //===================== READ PLAYER INFO ==========================
  950.  
  951.  
  952.         //===================== READ OBJECT INFO ==========================
  953.  
  954.         Gamesave_num_org_robots = 0;
  955.         Gamesave_num_players = 0;
  956.  
  957.         if (object_offset > -1) {
  958.                 if (PHYSFSX_fseek( LoadFile, object_offset, SEEK_SET ))
  959.                         Error( "Error seeking to object_offset in gamesave.c" );
  960.  
  961.                 range_for (auto &i, partial_range(Objects, gs_num_objects))
  962.                 {
  963.                         const auto &&o = vmobjptr(&i);
  964.                         read_object(o, LoadFile, game_top_fileinfo_version);
  965.                         verify_object(Vclip, o);
  966.                 }
  967.         }
  968.  
  969.         //===================== READ WALL INFO ============================
  970.  
  971.         auto &vmwallptr = Walls.vmptr;
  972.         range_for (const auto &&vw, vmwallptr)
  973.         {
  974.                 auto &nw = *vw;
  975.                 if (game_top_fileinfo_version >= 20)
  976.                         wall_read(LoadFile, nw); // v20 walls and up.
  977.                 else if (game_top_fileinfo_version >= 17) {
  978.                         v19_wall w;
  979.                         v19_wall_read(LoadFile, w);
  980.                         nw.segnum               = w.segnum;
  981.                         nw.sidenum      = w.sidenum;
  982.                         nw.linked_wall  = w.linked_wall;
  983.                         nw.type         = w.type;
  984.                         nw.flags                = w.flags & ~WALL_EXPLODING;
  985.                         nw.hps          = w.hps;
  986.                         nw.trigger      = w.trigger;
  987. #if defined(DXX_BUILD_DESCENT_I)
  988.                         nw.clip_num     = convert_wclip(w.clip_num);
  989. #elif defined(DXX_BUILD_DESCENT_II)
  990.                         nw.clip_num     = w.clip_num;
  991. #endif
  992.                         nw.keys         = w.keys;
  993.                         nw.state                = WALL_DOOR_CLOSED;
  994.                 } else {
  995.                         v16_wall w;
  996.                         v16_wall_read(LoadFile, w);
  997.                         nw.segnum = segment_none;
  998.                         nw.sidenum = nw.linked_wall = -1;
  999.                         nw.type         = w.type;
  1000.                         nw.flags                = w.flags & ~WALL_EXPLODING;
  1001.                         nw.hps          = w.hps;
  1002.                         nw.trigger      = w.trigger;
  1003. #if defined(DXX_BUILD_DESCENT_I)
  1004.                         nw.clip_num     = convert_wclip(w.clip_num);
  1005. #elif defined(DXX_BUILD_DESCENT_II)
  1006.                         nw.clip_num     = w.clip_num;
  1007. #endif
  1008.                         nw.keys         = w.keys;
  1009.                 }
  1010.         }
  1011.  
  1012.         //==================== READ TRIGGER INFO ==========================
  1013.  
  1014.         auto &vmtrgptr = Triggers.vmptr;
  1015.         range_for (const auto vt, vmtrgptr)
  1016.         {
  1017.                 auto &i = *vt;
  1018. #if defined(DXX_BUILD_DESCENT_I)
  1019.                 if (game_top_fileinfo_version <= 25)
  1020.                         v25_trigger_read(LoadFile, &i);
  1021.                 else {
  1022.                         v26_trigger_read(LoadFile, i);
  1023.                 }
  1024. #elif defined(DXX_BUILD_DESCENT_II)
  1025.                 if (game_top_fileinfo_version < 31)
  1026.                 {
  1027.                         if (game_top_fileinfo_version < 30) {
  1028.                                 v29_trigger_read_as_v31(LoadFile, i);
  1029.                         }
  1030.                         else
  1031.                                 v30_trigger_read_as_v31(LoadFile, i);
  1032.                 }
  1033.                 else
  1034.                         trigger_read(&i, LoadFile);
  1035. #endif
  1036.         }
  1037.  
  1038.         //================ READ CONTROL CENTER TRIGGER INFO ===============
  1039.  
  1040.         control_center_triggers_read(&ControlCenterTriggers, LoadFile);
  1041.  
  1042.         //================ READ MATERIALOGRIFIZATIONATORS INFO ===============
  1043.  
  1044.         range_for (auto &&e, enumerate(partial_range(RobotCenters, Num_robot_centers)))
  1045.         {
  1046.                 const uint_fast32_t i = e.idx;
  1047.                 auto &r = e.value;
  1048. #if defined(DXX_BUILD_DESCENT_I)
  1049.                 matcen_info_read(LoadFile, r, game_top_fileinfo_version);
  1050. #elif defined(DXX_BUILD_DESCENT_II)
  1051.                 if (game_top_fileinfo_version < 27) {
  1052.                         d1_matcen_info_read(LoadFile, r);
  1053.                 }
  1054.                 else
  1055.                         matcen_info_read(LoadFile, r);
  1056. #endif
  1057.                         //      Set links in RobotCenters to Station array
  1058.                 range_for (const shared_segment &seg, partial_const_range(Segments, Highest_segment_index + 1))
  1059.                         if (seg.special == SEGMENT_IS_ROBOTMAKER)
  1060.                                 if (seg.matcen_num == i)
  1061.                                         r.fuelcen_num = seg.station_idx;
  1062.         }
  1063.  
  1064. #if defined(DXX_BUILD_DESCENT_II)
  1065.         //================ READ DL_INDICES INFO ===============
  1066.  
  1067.         {
  1068.         auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices;
  1069.         Dl_indices.set_count(Num_static_lights);
  1070.         if (game_top_fileinfo_version < 29)
  1071.         {
  1072.                 if (Num_static_lights)
  1073.                         throw std::logic_error("Static lights in old file");
  1074.         }
  1075.         else
  1076.         {
  1077.                 const auto &&lr = partial_range(Dl_indices, Num_static_lights);
  1078.                 range_for (auto &i, lr)
  1079.                         dl_index_read(&i, LoadFile);
  1080.                 std::sort(lr.begin(), lr.end());
  1081.         }
  1082.         }
  1083.  
  1084.         //      Indicate that no light has been subtracted from any vertices.
  1085.         clear_light_subtracted();
  1086.  
  1087.         //================ READ DELTA LIGHT INFO ===============
  1088.  
  1089.                 if (game_top_fileinfo_version < 29) {
  1090.                         ;
  1091.                 } else
  1092.         {
  1093.                 auto &Delta_lights = LevelSharedDestructibleLightState.Delta_lights;
  1094.                 range_for (auto &i, partial_range(Delta_lights, num_delta_lights))
  1095.                         delta_light_read(&i, LoadFile);
  1096.         }
  1097. #endif
  1098.  
  1099.         //========================= UPDATE VARIABLES ======================
  1100.  
  1101.         reset_objects(LevelUniqueObjectState, gs_num_objects);
  1102.  
  1103.         range_for (auto &i, Objects)
  1104.         {
  1105.                 if (i.type != OBJ_NONE) {
  1106.                         auto objsegnum = i.segnum;
  1107.                         if (objsegnum > Highest_segment_index)          //bogus object
  1108.                         {
  1109.                                 Warning("Object %p is in non-existent segment %i, highest=%i", &i, objsegnum, Highest_segment_index);
  1110.                                 i.type = OBJ_NONE;
  1111.                         }
  1112.                         else {
  1113.                                 obj_link_unchecked(Objects.vmptr, vmobjptridx(&i), vmsegptridx(objsegnum));
  1114.                         }
  1115.                 }
  1116.         }
  1117.  
  1118.         clear_transient_objects(1);             //1 means clear proximity bombs
  1119.  
  1120.         // Make sure non-transparent doors are set correctly.
  1121.         range_for (auto &&i, vmsegptridx)
  1122.                 for (auto &&[sside, uside, side_idx] : zip(i->shared_segment::sides, i->unique_segment::sides, xrange(MAX_SIDES_PER_SEGMENT)))
  1123.                 {
  1124.                         if (sside.wall_num == wall_none)
  1125.                                 continue;
  1126.                         auto &w = *vmwallptr(sside.wall_num);
  1127.                         if (w.clip_num != -1)
  1128.                         {
  1129.                                 auto &wa = WallAnims[w.clip_num];
  1130.                                 if (wa.flags & WCF_TMAP1)
  1131.                                 {
  1132.                                         uside.tmap_num = wa.frames[0];
  1133.                                         uside.tmap_num2 = 0;
  1134.                                 }
  1135.                         }
  1136.                         validate_segment_wall(i, sside, side_idx);
  1137.                 }
  1138.  
  1139.         auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
  1140.         ActiveDoors.set_count(0);
  1141.  
  1142.         //go through all walls, killing references to invalid triggers
  1143.         range_for (const auto &&p, vmwallptr)
  1144.         {
  1145.                 auto &w = *p;
  1146.                 if (w.trigger >= Triggers.get_count()) {
  1147.                         w.trigger = trigger_none;       //kill trigger
  1148.                 }
  1149.         }
  1150.  
  1151. #if DXX_USE_EDITOR
  1152.         //go through all triggers, killing unused ones
  1153.         {
  1154.                 const auto &&wr = make_range(vmwallptr);
  1155.         for (uint_fast32_t i = 0;i < Triggers.get_count();) {
  1156.                 auto a = [i](const wall &w) { return w.trigger == i; };
  1157.                 //      Find which wall this trigger is connected to.
  1158.                 auto w = std::find_if(wr.begin(), wr.end(), a);
  1159.                 if (w == wr.end())
  1160.                 {
  1161.                         remove_trigger_num(i);
  1162.                 }
  1163.                 else
  1164.                         i++;
  1165.         }
  1166.         }
  1167. #endif
  1168.  
  1169.         //      MK, 10/17/95: Make walls point back at the triggers that control them.
  1170.         //      Go through all triggers, stuffing controlling_trigger field in Walls.
  1171.         {
  1172. #if defined(DXX_BUILD_DESCENT_II)
  1173.                 range_for (const auto &&w, vmwallptr)
  1174.                         w->controlling_trigger = -1;
  1175. #endif
  1176.  
  1177.                 auto &vctrgptridx = Triggers.vcptridx;
  1178.                 range_for (const auto &&t, vctrgptridx)
  1179.                 {
  1180.                         auto &tr = *t;
  1181.                         for (unsigned l = 0; l < tr.num_links; ++l)
  1182.                         {
  1183.                                 //check to see that if a trigger requires a wall that it has one,
  1184.                                 //and if it requires a matcen that it has one
  1185.                                 const auto seg_num = tr.seg[l];
  1186.                                 if (trigger_is_matcen(tr))
  1187.                                 {
  1188.                                         if (Segments[seg_num].special != SEGMENT_IS_ROBOTMAKER)
  1189.                                                 con_printf(CON_URGENT, "matcen %u triggers non-matcen segment %hu", t.get_unchecked_index(), seg_num);
  1190.                                 }
  1191. #if defined(DXX_BUILD_DESCENT_II)
  1192.                                 else if (tr.type != trigger_action::light_off && tr.type != trigger_action::light_on)
  1193.                                 {       //light triggers don't require walls
  1194.                                         const auto side_num = tr.side[l];
  1195.                                         auto wall_num = vmsegptr(seg_num)->shared_segment::sides[side_num].wall_num;
  1196.                                         if (const auto &&uwall = vmwallptr.check_untrusted(wall_num))
  1197.                                                 (*uwall)->controlling_trigger = t;
  1198.                                         else
  1199.                                         {
  1200.                                                 LevelError("trigger %u link %u type %u references segment %hu, side %u which is an invalid wall; ignoring.", static_cast<trgnum_t>(t), l, static_cast<unsigned>(tr.type), seg_num, side_num);
  1201.                                         }
  1202.                                 }
  1203. #endif
  1204.                         }
  1205.                 }
  1206.         }
  1207.  
  1208.         //fix old wall structs
  1209.         if (game_top_fileinfo_version < 17) {
  1210.                 range_for (const auto &&segp, vcsegptridx)
  1211.                 {
  1212.                         range_for (const int sidenum, xrange(6u))
  1213.                         {
  1214.                                 const auto wallnum = segp->shared_segment::sides[sidenum].wall_num;
  1215.                                 if (wallnum != wall_none)
  1216.                                 {
  1217.                                         auto &w = *vmwallptr(wallnum);
  1218.                                         w.segnum = segp;
  1219.                                         w.sidenum = sidenum;
  1220.                                 }
  1221.                         }
  1222.                 }
  1223.         }
  1224.         fix_object_segs();
  1225.  
  1226.         if (game_top_fileinfo_version < GAME_VERSION
  1227. #if defined(DXX_BUILD_DESCENT_II)
  1228.             && !(game_top_fileinfo_version == 25 && GAME_VERSION == 26)
  1229. #endif
  1230.                 )
  1231.                 return 1;               //means old version
  1232.         else
  1233.                 return 0;
  1234. }
  1235. }
  1236.  
  1237. // ----------------------------------------------------------------------------
  1238.  
  1239. #if defined(DXX_BUILD_DESCENT_I)
  1240. #define LEVEL_FILE_VERSION              1
  1241. #elif defined(DXX_BUILD_DESCENT_II)
  1242. #define LEVEL_FILE_VERSION      8
  1243. #endif
  1244. //1 -> 2  add palette name
  1245. //2 -> 3  add control center explosion time
  1246. //3 -> 4  add reactor strength
  1247. //4 -> 5  killed hostage text stuff
  1248. //5 -> 6  added Secret_return_segment and Secret_return_orient
  1249. //6 -> 7  added flickering lights
  1250. //7 -> 8  made version 8 to be not compatible with D2 1.0 & 1.1
  1251.  
  1252. #ifndef RELEASE
  1253. const char *Level_being_loaded=NULL;
  1254. #endif
  1255.  
  1256. #if defined(DXX_BUILD_DESCENT_II)
  1257. int no_old_level_file_error=0;
  1258. #endif
  1259.  
  1260. //loads a level (.LVL) file from disk
  1261. //returns 0 if success, else error code
  1262. namespace dsx {
  1263. int load_level(
  1264. #if defined(DXX_BUILD_DESCENT_II)
  1265.         d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
  1266. #endif
  1267.         const char * filename_passed)
  1268. {
  1269.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1270.         auto &Objects = LevelUniqueObjectState.Objects;
  1271.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1272.         auto &vmobjptridx = Objects.vmptridx;
  1273. #if DXX_USE_EDITOR
  1274.         int use_compiled_level=1;
  1275. #endif
  1276.         char filename[PATH_MAX];
  1277.         int sig, minedata_offset, gamedata_offset;
  1278.         int mine_err, game_err;
  1279.  
  1280.         #ifndef RELEASE
  1281.         Level_being_loaded = filename_passed;
  1282.         #endif
  1283.  
  1284.         strcpy(filename,filename_passed);
  1285.  
  1286. #if DXX_USE_EDITOR
  1287.         //if we have the editor, try the LVL first, no matter what was passed.
  1288.         //if we don't have an LVL, try what was passed or RL2  
  1289.         //if we don't have the editor, we just use what was passed
  1290.  
  1291.         change_filename_extension(filename,filename_passed,".lvl");
  1292.         use_compiled_level = 0;
  1293.  
  1294.         if (!PHYSFSX_exists(filename,1))
  1295.         {
  1296.                 const char *p = strrchr(filename_passed, '.');
  1297.  
  1298.                 if (d_stricmp(p, ".lvl"))
  1299.                         strcpy(filename, filename_passed);      // set to what was passed
  1300.                 else
  1301.                         change_filename_extension(filename, filename, "." DXX_LEVEL_FILE_EXTENSION);
  1302.                 use_compiled_level = 1;
  1303.         }              
  1304. #endif
  1305.  
  1306.         auto LoadFile = PHYSFSX_openReadBuffered(filename);
  1307.         if (!LoadFile)
  1308.         {
  1309.                 snprintf(filename, sizeof(filename), "%.*s%s", DXX_ptrdiff_cast_int(std::distance(Current_mission->path.cbegin(), Current_mission->filename)), Current_mission->path.c_str(), filename_passed);
  1310.                 LoadFile = PHYSFSX_openReadBuffered(filename);
  1311.         }
  1312.  
  1313.         if (!LoadFile)  {
  1314. #if DXX_USE_EDITOR
  1315.                         return 1;
  1316.                 #else
  1317.                         Error("Can't open file <%s>\n",filename);
  1318.                 #endif
  1319.         }
  1320.  
  1321.         sig                      = PHYSFSX_readInt(LoadFile);
  1322.         Gamesave_current_version = PHYSFSX_readInt(LoadFile);
  1323.         minedata_offset          = PHYSFSX_readInt(LoadFile);
  1324.         gamedata_offset          = PHYSFSX_readInt(LoadFile);
  1325.  
  1326.         Assert(sig == MAKE_SIG('P','L','V','L'));
  1327.         (void)sig;
  1328.  
  1329.         if (Gamesave_current_version < 5)
  1330.                 PHYSFSX_readInt(LoadFile);       //was hostagetext_offset
  1331.         init_exploding_walls();
  1332. #if defined(DXX_BUILD_DESCENT_II)
  1333.         if (Gamesave_current_version >= 8) {    //read dummy data
  1334.                 PHYSFSX_readInt(LoadFile);
  1335.                 PHYSFSX_readShort(LoadFile);
  1336.                 PHYSFSX_readByte(LoadFile);
  1337.         }
  1338.  
  1339.         if (Gamesave_current_version > 1)
  1340.                 PHYSFSX_fgets(Current_level_palette,LoadFile);
  1341.         if (Gamesave_current_version <= 1 || Current_level_palette[0]==0) // descent 1 level
  1342.                 strcpy(Current_level_palette.next().data(), DEFAULT_LEVEL_PALETTE);
  1343.  
  1344.         if (Gamesave_current_version >= 3)
  1345.                 LevelSharedControlCenterState.Base_control_center_explosion_time = PHYSFSX_readInt(LoadFile);
  1346.         else
  1347.                 LevelSharedControlCenterState.Base_control_center_explosion_time = DEFAULT_CONTROL_CENTER_EXPLOSION_TIME;
  1348.  
  1349.         if (Gamesave_current_version >= 4)
  1350.                 LevelSharedControlCenterState.Reactor_strength = PHYSFSX_readInt(LoadFile);
  1351.         else
  1352.                 LevelSharedControlCenterState.Reactor_strength = -1;  //use old defaults
  1353.  
  1354.         if (Gamesave_current_version >= 7) {
  1355.                 Flickering_light_state.Num_flickering_lights = PHYSFSX_readInt(LoadFile);
  1356.                 range_for (auto &i, partial_range(Flickering_light_state.Flickering_lights, Flickering_light_state.Num_flickering_lights))
  1357.                         flickering_light_read(i, LoadFile);
  1358.         }
  1359.         else
  1360.                 Flickering_light_state.Num_flickering_lights = 0;
  1361.  
  1362.         {
  1363.                 auto &Secret_return_orient = LevelSharedSegmentState.Secret_return_orient;
  1364.         if (Gamesave_current_version < 6) {
  1365.                 LevelSharedSegmentState.Secret_return_segment = segment_first;
  1366.                 Secret_return_orient.rvec.x = F1_0;
  1367.                 Secret_return_orient.rvec.y = 0;
  1368.                 Secret_return_orient.rvec.z = 0;
  1369.                 Secret_return_orient.fvec.x = 0;
  1370.                 Secret_return_orient.fvec.y = F1_0;
  1371.                 Secret_return_orient.fvec.z = 0;
  1372.                 Secret_return_orient.uvec.x = 0;
  1373.                 Secret_return_orient.uvec.y = 0;
  1374.                 Secret_return_orient.uvec.z = F1_0;
  1375.         } else {
  1376.                 LevelSharedSegmentState.Secret_return_segment = PHYSFSX_readInt(LoadFile);
  1377.                 Secret_return_orient.rvec.x = PHYSFSX_readInt(LoadFile);
  1378.                 Secret_return_orient.rvec.y = PHYSFSX_readInt(LoadFile);
  1379.                 Secret_return_orient.rvec.z = PHYSFSX_readInt(LoadFile);
  1380.                 Secret_return_orient.fvec.x = PHYSFSX_readInt(LoadFile);
  1381.                 Secret_return_orient.fvec.y = PHYSFSX_readInt(LoadFile);
  1382.                 Secret_return_orient.fvec.z = PHYSFSX_readInt(LoadFile);
  1383.                 Secret_return_orient.uvec.x = PHYSFSX_readInt(LoadFile);
  1384.                 Secret_return_orient.uvec.y = PHYSFSX_readInt(LoadFile);
  1385.                 Secret_return_orient.uvec.z = PHYSFSX_readInt(LoadFile);
  1386.         }
  1387.         }
  1388. #endif
  1389.  
  1390.         PHYSFSX_fseek(LoadFile,minedata_offset,SEEK_SET);
  1391. #if DXX_USE_EDITOR
  1392.         if (!use_compiled_level) {
  1393.                 mine_err = load_mine_data(LoadFile);
  1394. #if 0 // get from d1src if needed
  1395.                 // Compress all uv coordinates in mine, improves texmap precision. --MK, 02/19/96
  1396.                 compress_uv_coordinates_all();
  1397. #endif
  1398.         } else
  1399.         #endif
  1400.                 //NOTE LINK TO ABOVE!!
  1401.                 mine_err = load_mine_data_compiled(LoadFile, filename);
  1402.  
  1403.         /* !!!HACK!!!
  1404.          * Descent 1 - Level 19: OBERON MINE has some ugly overlapping rooms (segment 484).
  1405.          * HACK to make this issue less visible by moving one vertex a little.
  1406.          */
  1407.         auto &vmvertptr = Vertices.vmptr;
  1408.         if (Current_mission && !d_stricmp("Descent: First Strike",Current_mission_longname) && !d_stricmp("level19.rdl",filename) && PHYSFS_fileLength(LoadFile) == 136706)
  1409.                 vmvertptr(1905u)->z = -385 * F1_0;
  1410. #if defined(DXX_BUILD_DESCENT_II)
  1411.         /* !!!HACK!!!
  1412.          * Descent 2 - Level 12: MAGNACORE STATION has a segment (104) with illegal dimensions.
  1413.          * HACK to fix this by moving the Vertex and fixing the associated Normals.
  1414.          * NOTE: This only fixes the normals of segment 104, not the other ones connected to this Vertex but this is unsignificant.
  1415.          */
  1416.         if (Current_mission && !d_stricmp("Descent 2: Counterstrike!",Current_mission_longname) && !d_stricmp("d2levc-4.rl2",filename))
  1417.         {
  1418.                 shared_segment &s104 = *vmsegptr(vmsegidx_t(104));
  1419.                 auto &s104v0 = *vmvertptr(s104.verts[0]);
  1420.                 auto &s104s1 = s104.sides[1];
  1421.                 auto &s104s1n0 = s104s1.normals[0];
  1422.                 auto &s104s1n1 = s104s1.normals[1];
  1423.                 auto &s104s2 = s104.sides[2];
  1424.                 auto &s104s2n0 = s104s2.normals[0];
  1425.                 auto &s104s2n1 = s104s2.normals[1];
  1426.                 if (
  1427.                         (s104v0.x == -53990800 && s104v0.y == -59927741 && s104v0.z == 23034584) &&
  1428.                         (s104s1n0.x == 56775 && s104s1n0.y == -27796 && s104s1n0.z == -17288 && s104s1n1.x == 50157 && s104s1n1.y == -34561 && s104s1n1.z == -24180) &&
  1429.                         (s104s2n0.x == 60867 && s104s2n0.y == -19485 && s104s2n0.z == -14507 && s104s2n1.x == 55485 && s104s2n1.y == -29668 && s104s2n1.z == -18332)
  1430.                 )
  1431.                 {
  1432.                         s104v0.x = -53859726;
  1433.                         s104v0.y = -59927743;
  1434.                         s104v0.z = 23034586;
  1435.                         s104s1n0.x = 56123;
  1436.                         s104s1n0.y = -27725;
  1437.                         s104s1n0.z = -19401;
  1438.                         s104s1n1.x = 49910;
  1439.                         s104s1n1.y = -33946;
  1440.                         s104s1n1.z = -25525;
  1441.                         s104s2n0.x = 60903;
  1442.                         s104s2n0.y = -18371;
  1443.                         s104s2n0.z = -15753;
  1444.                         s104s2n1.x = 57004;
  1445.                         s104s2n1.y = -26385;
  1446.                         s104s2n1.z = -18688;
  1447.                         // I feel so dirty now ...
  1448.                 }
  1449.         }
  1450. #endif
  1451.  
  1452.         if (mine_err == -1) {   //error!!
  1453.                 return 2;
  1454.         }
  1455.  
  1456.         PHYSFSX_fseek(LoadFile,gamedata_offset,SEEK_SET);
  1457.         game_err = load_game_data(
  1458. #if defined(DXX_BUILD_DESCENT_II)
  1459.                 LevelSharedDestructibleLightState,
  1460. #endif
  1461.                 vmobjptridx, vmsegptridx, LoadFile);
  1462.  
  1463.         if (game_err == -1) {   //error!!
  1464.                 return 3;
  1465.         }
  1466.  
  1467.         //======================== CLOSE FILE =============================
  1468.         LoadFile.reset();
  1469. #if defined(DXX_BUILD_DESCENT_II)
  1470.         set_ambient_sound_flags();
  1471. #endif
  1472.  
  1473. #if DXX_USE_EDITOR
  1474. #if defined(DXX_BUILD_DESCENT_I)
  1475.         //If an old version, ask the use if he wants to save as new version
  1476.         if (((LEVEL_FILE_VERSION>1) && Gamesave_current_version < LEVEL_FILE_VERSION) || mine_err==1 || game_err==1) {
  1477.                 gr_palette_load(gr_palette);
  1478.                 if (nm_messagebox( NULL, 2, "Don't Save", "Save", "You just loaded a old version level.  Would\n"
  1479.                                                 "you like to save it as a current version level?")==1)
  1480.                         save_level(filename);
  1481.         }
  1482. #elif defined(DXX_BUILD_DESCENT_II)
  1483.         //If a Descent 1 level and the Descent 1 pig isn't present, pretend it's a Descent 2 level.
  1484.         if (EditorWindow && (Gamesave_current_version <= 3) && !d1_pig_present)
  1485.         {
  1486.                 if (!no_old_level_file_error)
  1487.                         Warning("A Descent 1 level was loaded,\n"
  1488.                                         "and there is no Descent 1 texture\n"
  1489.                                         "set available. Saving it will\n"
  1490.                                         "convert it to a Descent 2 level.");
  1491.  
  1492.                 Gamesave_current_version = LEVEL_FILE_VERSION;
  1493.         }
  1494. #endif
  1495.         #endif
  1496.  
  1497. #if DXX_USE_EDITOR
  1498.         if (EditorWindow)
  1499.                 editor_status_fmt("Loaded NEW mine %s, \"%s\"", filename, static_cast<const char *>(Current_level_name));
  1500.         #endif
  1501.  
  1502. #ifdef NDEBUG
  1503.         if (!PLAYING_BUILTIN_MISSION)
  1504. #endif
  1505.         if (check_segment_connections())
  1506.         {
  1507. #ifndef NDEBUG
  1508.                 nm_messagebox( "ERROR", 1, "Ok",
  1509.                                 "Connectivity errors detected in\n"
  1510.                                 "mine.  See monochrome screen for\n"
  1511.                                 "details, and contact Matt or Mike." );
  1512.         #endif
  1513.         }
  1514.  
  1515.  
  1516. #if defined(DXX_BUILD_DESCENT_II)
  1517.         compute_slide_segs();
  1518. #endif
  1519.         return 0;
  1520. }
  1521. }
  1522.  
  1523. #if DXX_USE_EDITOR
  1524. int get_level_name()
  1525. {
  1526.         std::array<newmenu_item, 2> m{{
  1527.                 nm_item_text("Please enter a name for this mine:"),
  1528.                 nm_item_input(Current_level_name.next()),
  1529.         }};
  1530.         return newmenu_do( NULL, "Enter mine name", m, unused_newmenu_subfunction, unused_newmenu_userdata ) >= 0;
  1531. }
  1532. #endif
  1533.  
  1534.  
  1535. #if DXX_USE_EDITOR
  1536.  
  1537. // --------------------------------------------------------------------------------------
  1538. //      Create a new mine, set global variables.
  1539. namespace dsx {
  1540. int create_new_mine(void)
  1541. {
  1542.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1543.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1544.         vms_matrix      m1 = IDENTITY_MATRIX;
  1545.        
  1546.         // initialize_mine_arrays();
  1547.        
  1548.         //      gamestate_not_restored = 1;
  1549.        
  1550.         // Clear refueling center code
  1551.         fuelcen_reset();
  1552.         init_all_vertices();
  1553.        
  1554.         Current_level_num = 1;          // make level 1 (for now)
  1555.         Current_level_name.next()[0] = 0;
  1556. #if defined(DXX_BUILD_DESCENT_I)
  1557.         Gamesave_current_version = LEVEL_FILE_VERSION;
  1558. #elif defined(DXX_BUILD_DESCENT_II)
  1559.         Gamesave_current_version = GAME_VERSION;
  1560.        
  1561.         strcpy(Current_level_palette.next().data(), DEFAULT_LEVEL_PALETTE);
  1562. #endif
  1563.        
  1564.         Cur_object_index = -1;
  1565.         reset_objects(LevelUniqueObjectState, 1);               //just one object, the player
  1566.        
  1567.         num_groups = 0;
  1568.         current_group = -1;
  1569.        
  1570.        
  1571.         LevelSharedVertexState.Num_vertices = 0;                // Number of vertices in global array.
  1572.         Vertices.set_count(1);
  1573.         LevelSharedSegmentState.Num_segments = 0;               // Number of segments in global array, will get increased in med_create_segment
  1574.         Segments.set_count(1);
  1575.         Cursegp = imsegptridx(segment_first);   // Say current segment is the only segment.
  1576.         Curside = WBACK;                // The active side is the back side
  1577.         Markedsegp = segment_none;              // Say there is no marked segment.
  1578.         Markedside = WBACK;     //      Shouldn't matter since Markedsegp == 0, but just in case...
  1579.         for (int s=0;s<MAX_GROUPS+1;s++) {
  1580.                 GroupList[s].clear();
  1581.                 Groupsegp[s] = NULL;
  1582.                 Groupside[s] = 0;
  1583.         }
  1584.        
  1585.         LevelSharedRobotcenterState.Num_robot_centers = 0;
  1586.         auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
  1587.         ActiveDoors.set_count(0);
  1588.         wall_init();
  1589.         trigger_init();
  1590.        
  1591.         // Create New_segment, which is the segment we will be adding at each instance.
  1592.         med_create_new_segment({DEFAULT_X_SIZE, DEFAULT_Y_SIZE, DEFAULT_Z_SIZE});               // New_segment = Segments[0];
  1593.         //      med_create_segment(Segments,0,0,0,DEFAULT_X_SIZE,DEFAULT_Y_SIZE,DEFAULT_Z_SIZE,vm_mat_make(&m1,F1_0,0,0,0,F1_0,0,0,0,F1_0));
  1594.         med_create_segment(vmsegptridx(segment_first), 0, 0, 0, DEFAULT_X_SIZE, DEFAULT_Y_SIZE, DEFAULT_Z_SIZE, m1);
  1595.        
  1596.         Found_segs.clear();
  1597.         Selected_segs.clear();
  1598.         Warning_segs.clear();
  1599.        
  1600.         //--repair-- create_local_segment_data();
  1601.        
  1602.         ControlCenterTriggers.num_links = 0;
  1603.        
  1604.         create_new_mission();
  1605.        
  1606.     //editor_status("New mine created.");
  1607.         return  0;                      // say no error
  1608. }
  1609. }
  1610.  
  1611. int     Errors_in_mine;
  1612.  
  1613. namespace dsx {
  1614. // -----------------------------------------------------------------------------
  1615. #if defined(DXX_BUILD_DESCENT_II)
  1616. static unsigned compute_num_delta_light_records(fvcdlindexptr &vcdlindexptr)
  1617. {
  1618.         unsigned total = 0;
  1619.         range_for (const auto &&i, vcdlindexptr)
  1620.                 total += i->count;
  1621.         return total;
  1622.  
  1623. }
  1624. #endif
  1625.  
  1626. // -----------------------------------------------------------------------------
  1627. // Save game
  1628. static int save_game_data(
  1629. #if defined(DXX_BUILD_DESCENT_II)
  1630.         const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
  1631. #endif
  1632.         PHYSFS_File *SaveFile)
  1633. {
  1634.         auto &Objects = LevelUniqueObjectState.Objects;
  1635.         auto &vcobjptr = Objects.vcptr;
  1636.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  1637. #if defined(DXX_BUILD_DESCENT_I)
  1638.         short game_top_fileinfo_version = Gamesave_current_version >= 5 ? 31 : GAME_VERSION;
  1639. #elif defined(DXX_BUILD_DESCENT_II)
  1640.         short game_top_fileinfo_version = Gamesave_current_version >= 5 ? 31 : 25;
  1641.         int     dl_indices_offset=0, delta_light_offset=0;
  1642. #endif
  1643.         int  player_offset=0, object_offset=0, walls_offset=0, doors_offset=0, triggers_offset=0, control_offset=0, matcen_offset=0; //, links_offset;
  1644.         int offset_offset=0, end_offset=0;
  1645.         //===================== SAVE FILE INFO ========================
  1646.  
  1647.         PHYSFS_writeSLE16(SaveFile, 0x6705);    // signature
  1648.         PHYSFS_writeSLE16(SaveFile, game_top_fileinfo_version);
  1649.         PHYSFS_writeSLE32(SaveFile, 0);
  1650.         PHYSFS_write(SaveFile, Current_level_name.line(), 15, 1);
  1651.         PHYSFS_writeSLE32(SaveFile, Current_level_num);
  1652.         offset_offset = PHYSFS_tell(SaveFile);  // write the offsets later
  1653.         PHYSFS_writeSLE32(SaveFile, -1);
  1654.         PHYSFS_writeSLE32(SaveFile, 0);
  1655.  
  1656. #define WRITE_HEADER_ENTRY(t, n) do { PHYSFS_writeSLE32(SaveFile, -1); PHYSFS_writeSLE32(SaveFile, n); PHYSFS_writeSLE32(SaveFile, sizeof(t)); } while(0)
  1657.  
  1658.         WRITE_HEADER_ENTRY(object, Highest_object_index + 1);
  1659.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1660.         WRITE_HEADER_ENTRY(wall, Walls.get_count());
  1661.         auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
  1662.         WRITE_HEADER_ENTRY(active_door, ActiveDoors.get_count());
  1663.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  1664.         WRITE_HEADER_ENTRY(trigger, Triggers.get_count());
  1665.         WRITE_HEADER_ENTRY(0, 0);               // links (removed by Parallax)
  1666.         WRITE_HEADER_ENTRY(control_center_triggers, 1);
  1667.         const auto Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers;
  1668.         WRITE_HEADER_ENTRY(matcen_info, Num_robot_centers);
  1669.  
  1670. #if defined(DXX_BUILD_DESCENT_II)
  1671.         unsigned num_delta_lights = 0;
  1672.         if (game_top_fileinfo_version >= 29)
  1673.         {
  1674.                 auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices;
  1675.                 const unsigned Num_static_lights = Dl_indices.get_count();
  1676.                 WRITE_HEADER_ENTRY(dl_index, Num_static_lights);
  1677.                 WRITE_HEADER_ENTRY(delta_light, num_delta_lights = compute_num_delta_light_records(Dl_indices.vcptr));
  1678.         }
  1679.  
  1680.         // Write the mine name
  1681.         if (game_top_fileinfo_version >= 31)
  1682. #endif
  1683.                 PHYSFSX_printf(SaveFile, "%s\n", static_cast<const char *>(Current_level_name));
  1684. #if defined(DXX_BUILD_DESCENT_II)
  1685.         else if (game_top_fileinfo_version >= 14)
  1686.                 PHYSFSX_writeString(SaveFile, Current_level_name);
  1687.  
  1688.         if (game_top_fileinfo_version >= 19)
  1689. #endif
  1690.         {
  1691.                 PHYSFS_writeSLE16(SaveFile, N_polygon_models);
  1692.                 range_for (auto &i, partial_const_range(Pof_names, N_polygon_models))
  1693.                         PHYSFS_write(SaveFile, &i, sizeof(i), 1);
  1694.         }
  1695.  
  1696.         //==================== SAVE PLAYER INFO ===========================
  1697.  
  1698.         player_offset = PHYSFS_tell(SaveFile);
  1699.  
  1700.         //==================== SAVE OBJECT INFO ===========================
  1701.  
  1702.         object_offset = PHYSFS_tell(SaveFile);
  1703.         range_for (const auto &&objp, vcobjptr)
  1704.         {
  1705.                 write_object(objp, game_top_fileinfo_version, SaveFile);
  1706.         }
  1707.  
  1708.         //==================== SAVE WALL INFO =============================
  1709.  
  1710.         walls_offset = PHYSFS_tell(SaveFile);
  1711.         auto &vcwallptr = Walls.vcptr;
  1712.         range_for (const auto &&w, vcwallptr)
  1713.                 wall_write(SaveFile, *w, game_top_fileinfo_version);
  1714.  
  1715.         //==================== SAVE TRIGGER INFO =============================
  1716.  
  1717.         triggers_offset = PHYSFS_tell(SaveFile);
  1718.         auto &vctrgptr = Triggers.vcptr;
  1719.         range_for (const auto vt, vctrgptr)
  1720.         {
  1721.                 auto &t = *vt;
  1722.                 if (game_top_fileinfo_version <= 29)
  1723.                         v29_trigger_write(SaveFile, t);
  1724.                 else if (game_top_fileinfo_version <= 30)
  1725.                         v30_trigger_write(SaveFile, t);
  1726.                 else if (game_top_fileinfo_version >= 31)
  1727.                         v31_trigger_write(SaveFile, t);
  1728.         }
  1729.  
  1730.         //================ SAVE CONTROL CENTER TRIGGER INFO ===============
  1731.  
  1732.         control_offset = PHYSFS_tell(SaveFile);
  1733.         control_center_triggers_write(&ControlCenterTriggers, SaveFile);
  1734.  
  1735.  
  1736.         //================ SAVE MATERIALIZATION CENTER TRIGGER INFO ===============
  1737.  
  1738.         matcen_offset = PHYSFS_tell(SaveFile);
  1739.         range_for (auto &r, partial_const_range(RobotCenters, Num_robot_centers))
  1740.                 matcen_info_write(SaveFile, r, game_top_fileinfo_version);
  1741.  
  1742.         //================ SAVE DELTA LIGHT INFO ===============
  1743. #if defined(DXX_BUILD_DESCENT_II)
  1744.         if (game_top_fileinfo_version >= 29)
  1745.         {
  1746.                 dl_indices_offset = PHYSFS_tell(SaveFile);
  1747.                 auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices;
  1748.                 range_for (const auto &&i, Dl_indices.vcptr)
  1749.                         dl_index_write(i, SaveFile);
  1750.  
  1751.                 delta_light_offset = PHYSFS_tell(SaveFile);
  1752.                 auto &Delta_lights = LevelSharedDestructibleLightState.Delta_lights;
  1753.                 range_for (auto &i, partial_const_range(Delta_lights, num_delta_lights))
  1754.                         delta_light_write(&i, SaveFile);
  1755.         }
  1756. #endif
  1757.  
  1758.         //============= SAVE OFFSETS ===============
  1759.  
  1760.         end_offset = PHYSFS_tell(SaveFile);
  1761.  
  1762.         // Update the offset fields
  1763.  
  1764. #define WRITE_OFFSET(o, n) do { PHYSFS_seek(SaveFile, offset_offset); PHYSFS_writeSLE32(SaveFile, o ## _offset); offset_offset += sizeof(int)*n; } while (0)
  1765.  
  1766.         WRITE_OFFSET(player, 2);
  1767.         WRITE_OFFSET(object, 3);
  1768.         WRITE_OFFSET(walls, 3);
  1769.         WRITE_OFFSET(doors, 3);
  1770.         WRITE_OFFSET(triggers, 6);
  1771.         WRITE_OFFSET(control, 3);
  1772.         WRITE_OFFSET(matcen, 3);
  1773. #if defined(DXX_BUILD_DESCENT_II)
  1774.         if (game_top_fileinfo_version >= 29)
  1775.         {
  1776.                 WRITE_OFFSET(dl_indices, 3);
  1777.                 WRITE_OFFSET(delta_light, 0);
  1778.         }
  1779. #endif
  1780.  
  1781.         // Go back to end of data
  1782.         PHYSFS_seek(SaveFile, end_offset);
  1783.  
  1784.         return 0;
  1785. }
  1786.  
  1787. // -----------------------------------------------------------------------------
  1788. // Save game
  1789. static int save_level_sub(
  1790. #if defined(DXX_BUILD_DESCENT_II)
  1791.         const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
  1792. #endif
  1793.         fvmobjptridx &vmobjptridx, const char *const filename)
  1794. {
  1795.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1796.         auto &Objects = LevelUniqueObjectState.Objects;
  1797.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1798.         auto &vmobjptr = Objects.vmptr;
  1799.         char temp_filename[PATH_MAX];
  1800.         int minedata_offset=0,gamedata_offset=0;
  1801.  
  1802. //      if ( !compiled_version )
  1803.         {
  1804.                 write_game_text_file(filename);
  1805.  
  1806.                 if (Errors_in_mine) {
  1807.                         if (is_real_level(filename)) {
  1808.                                 gr_palette_load(gr_palette);
  1809.          
  1810.                                 if (nm_messagebox( NULL, 2, "Cancel Save", "Save", "Warning: %i errors in this mine!\n", Errors_in_mine )!=1)   {
  1811.                                         return 1;
  1812.                                 }
  1813.                         }
  1814.                 }
  1815. //              change_filename_extension(temp_filename,filename,".LVL");
  1816.         }
  1817. //      else
  1818.         {
  1819. #if defined(DXX_BUILD_DESCENT_II)
  1820.                 if (Gamesave_current_version > 3)
  1821.                         change_filename_extension(temp_filename, filename, "." D2X_LEVEL_FILE_EXTENSION);
  1822.                 else
  1823. #endif
  1824.                         change_filename_extension(temp_filename, filename, "." D1X_LEVEL_FILE_EXTENSION);
  1825.         }
  1826.  
  1827.         auto SaveFile = PHYSFSX_openWriteBuffered(temp_filename);
  1828.         if (!SaveFile)
  1829.         {
  1830.                 gr_palette_load(gr_palette);
  1831.                 nm_messagebox( NULL, 1, "Ok", "ERROR: Cannot write to '%s'.", temp_filename);
  1832.                 return 1;
  1833.         }
  1834.  
  1835.         if (Current_level_name[0] == 0)
  1836.                 strcpy(Current_level_name.next().data(),"Untitled");
  1837.  
  1838.         clear_transient_objects(1);             //1 means clear proximity bombs
  1839.  
  1840.         compress_objects();             //after this, Highest_object_index == num objects
  1841.  
  1842.         //make sure player is in a segment
  1843.         {
  1844.                 const auto &&plr = vmobjptridx(vcplayerptr(0u)->objnum);
  1845.                 if (update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, plr) == 0)
  1846.                 {
  1847.                         if (plr->segnum > Highest_segment_index)
  1848.                                 plr->segnum = segment_first;
  1849.                         auto &vcvertptr = Vertices.vcptr;
  1850.                         compute_segment_center(vcvertptr, plr->pos, vcsegptr(plr->segnum));
  1851.                 }
  1852.         }
  1853.  
  1854.         fix_object_segs();
  1855.  
  1856.         //Write the header
  1857.  
  1858.         PHYSFS_writeSLE32(SaveFile, MAKE_SIG('P','L','V','L'));
  1859.         PHYSFS_writeSLE32(SaveFile, Gamesave_current_version);
  1860.  
  1861.         //save placeholders
  1862.         PHYSFS_writeSLE32(SaveFile, minedata_offset);
  1863.         PHYSFS_writeSLE32(SaveFile, gamedata_offset);
  1864. #if defined(DXX_BUILD_DESCENT_I)
  1865.         int hostagetext_offset = 0;
  1866.         PHYSFS_writeSLE32(SaveFile, hostagetext_offset);
  1867. #endif
  1868.  
  1869.         //Now write the damn data
  1870.  
  1871. #if defined(DXX_BUILD_DESCENT_II)
  1872.         if (Gamesave_current_version >= 8)
  1873.         {
  1874.                 //write the version 8 data (to make file unreadable by 1.0 & 1.1)
  1875.                 PHYSFS_writeSLE32(SaveFile, GameTime64);
  1876.                 PHYSFS_writeSLE16(SaveFile, d_tick_count);
  1877.                 PHYSFSX_writeU8(SaveFile, FrameTime);
  1878.         }
  1879.  
  1880.         if (Gamesave_current_version < 5)
  1881.                 PHYSFS_writeSLE32(SaveFile, -1);       //was hostagetext_offset
  1882.  
  1883.         // Write the palette file name
  1884.         if (Gamesave_current_version > 1)
  1885.                 PHYSFSX_printf(SaveFile, "%s\n", static_cast<const char *>(Current_level_palette));
  1886.  
  1887.         if (Gamesave_current_version >= 3)
  1888.                 PHYSFS_writeSLE32(SaveFile, LevelSharedControlCenterState.Base_control_center_explosion_time);
  1889.         if (Gamesave_current_version >= 4)
  1890.                 PHYSFS_writeSLE32(SaveFile, LevelSharedControlCenterState.Reactor_strength);
  1891.  
  1892.         if (Gamesave_current_version >= 7)
  1893.         {
  1894.                 const auto Num_flickering_lights = Flickering_light_state.Num_flickering_lights;
  1895.                 PHYSFS_writeSLE32(SaveFile, Num_flickering_lights);
  1896.                 range_for (auto &i, partial_const_range(Flickering_light_state.Flickering_lights, Num_flickering_lights))
  1897.                         flickering_light_write(i, SaveFile);
  1898.         }
  1899.  
  1900.         if (Gamesave_current_version >= 6)
  1901.         {
  1902.                 PHYSFS_writeSLE32(SaveFile, LevelSharedSegmentState.Secret_return_segment);
  1903.                 auto &Secret_return_orient = LevelSharedSegmentState.Secret_return_orient;
  1904.                 PHYSFSX_writeVector(SaveFile, Secret_return_orient.rvec);
  1905.                 PHYSFSX_writeVector(SaveFile, Secret_return_orient.fvec);
  1906.                 PHYSFSX_writeVector(SaveFile, Secret_return_orient.uvec);
  1907.         }
  1908. #endif
  1909.  
  1910.         minedata_offset = PHYSFS_tell(SaveFile);
  1911. #if 0   // only save compiled mine data
  1912.         if ( !compiled_version )       
  1913.                 save_mine_data(SaveFile);
  1914.         else
  1915. #endif
  1916.                 save_mine_data_compiled(SaveFile);
  1917.         gamedata_offset = PHYSFS_tell(SaveFile);
  1918.         save_game_data(
  1919. #if defined(DXX_BUILD_DESCENT_II)
  1920.                 LevelSharedDestructibleLightState,
  1921. #endif
  1922.                 SaveFile);
  1923. #if defined(DXX_BUILD_DESCENT_I)
  1924.         hostagetext_offset = PHYSFS_tell(SaveFile);
  1925. #endif
  1926.  
  1927.         PHYSFS_seek(SaveFile, sizeof(int) + sizeof(Gamesave_current_version));
  1928.         PHYSFS_writeSLE32(SaveFile, minedata_offset);
  1929.         PHYSFS_writeSLE32(SaveFile, gamedata_offset);
  1930. #if defined(DXX_BUILD_DESCENT_I)
  1931.         PHYSFS_writeSLE32(SaveFile, hostagetext_offset);
  1932. #elif defined(DXX_BUILD_DESCENT_II)
  1933.         if (Gamesave_current_version < 5)
  1934.                 PHYSFS_writeSLE32(SaveFile, PHYSFS_fileLength(SaveFile));
  1935. #endif
  1936.  
  1937.         //==================== CLOSE THE FILE =============================
  1938.  
  1939. //      if ( !compiled_version )
  1940.         {
  1941.                 if (EditorWindow)
  1942.                         editor_status_fmt("Saved mine %s, \"%s\"", filename, static_cast<const char *>(Current_level_name));
  1943.         }
  1944.  
  1945.         return 0;
  1946.  
  1947. }
  1948.  
  1949. int save_level(
  1950. #if defined(DXX_BUILD_DESCENT_II)
  1951.         const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
  1952. #endif
  1953.         const char * filename)
  1954. {
  1955.         auto &Objects = LevelUniqueObjectState.Objects;
  1956.         auto &vmobjptridx = Objects.vmptridx;
  1957.         int r1;
  1958.  
  1959.         // Save compiled version...
  1960.         r1 = save_level_sub(
  1961. #if defined(DXX_BUILD_DESCENT_II)
  1962.                 LevelSharedDestructibleLightState,
  1963. #endif
  1964.                 vmobjptridx, filename);
  1965.  
  1966.         return r1;
  1967. }
  1968. }
  1969.  
  1970. #endif  //EDITOR
  1971.