/* * Portions of this file are copyright Rebirth contributors and licensed as * described in COPYING.txt. * Portions of this file are copyright Parallax Software and licensed * according to the Parallax license below. * See COPYING.txt for license details. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * * Save game information * */ #include #include #include #include "pstypes.h" #include "strutil.h" #include "console.h" #include "gr.h" #include "palette.h" #include "newmenu.h" #include "inferno.h" #if DXX_USE_EDITOR #include "editor/editor.h" #include "editor/esegment.h" #include "editor/eswitch.h" #endif #include "dxxerror.h" #include "object.h" #include "game.h" #include "gameseg.h" #include "wall.h" #include "gamemine.h" #include "robot.h" #include "bm.h" #include "fireball.h" #include "switch.h" #include "fuelcen.h" #include "cntrlcen.h" #include "powerup.h" #include "weapon.h" #include "player.h" #include "newdemo.h" #include "gameseq.h" #include "polyobj.h" #include "text.h" #include "gamefont.h" #include "gamesave.h" #include "gamepal.h" #include "physics.h" #include "laser.h" #include "multi.h" #include "makesig.h" #include "textures.h" #include "d_enumerate.h" #include "d_range.h" #include "vclip.h" #include "compiler-range_for.h" #include "d_levelstate.h" #include "d_underlying_value.h" #include "d_zip.h" #include "partial_range.h" #if defined(DXX_BUILD_DESCENT_I) #if DXX_USE_EDITOR const char Shareware_level_names[NUM_SHAREWARE_LEVELS][12] = { "level01.rdl", "level02.rdl", "level03.rdl", "level04.rdl", "level05.rdl", "level06.rdl", "level07.rdl" }; const char Registered_level_names[NUM_REGISTERED_LEVELS][12] = { "level08.rdl", "level09.rdl", "level10.rdl", "level11.rdl", "level12.rdl", "level13.rdl", "level14.rdl", "level15.rdl", "level16.rdl", "level17.rdl", "level18.rdl", "level19.rdl", "level20.rdl", "level21.rdl", "level22.rdl", "level23.rdl", "level24.rdl", "level25.rdl", "level26.rdl", "level27.rdl", "levels1.rdl", "levels2.rdl", "levels3.rdl" }; #endif #endif char Gamesave_current_filename[PATH_MAX]; int Gamesave_current_version; #if defined(DXX_BUILD_DESCENT_I) #define GAME_VERSION 25 #elif defined(DXX_BUILD_DESCENT_II) #define GAME_VERSION 32 #endif #define GAME_COMPATIBLE_VERSION 22 //version 28->29 add delta light support //version 27->28 controlcen id now is reactor number, not model number //version 28->29 ?? //version 29->30 changed trigger structure //version 30->31 changed trigger structure some more //version 31->32 change segment structure, make it 512 bytes w/o editor, add Segment2s array. #define MENU_CURSOR_X_MIN MENU_X #define MENU_CURSOR_X_MAX MENU_X+6 int Gamesave_num_org_robots = 0; //--unused-- grs_bitmap * Gamesave_saved_bitmap = NULL; #if DXX_USE_EDITOR // Return true if this level has a name of the form "level??" // Note that a pathspec can appear at the beginning of the filename. static int is_real_level(const char *filename) { int len = strlen(filename); if (len < 6) return 0; return !d_strnicmp(&filename[len-11], "level"); } #endif //--unused-- vms_angvec zero_angles={0,0,0}; int Gamesave_num_players=0; namespace dsx { #if defined(DXX_BUILD_DESCENT_I) namespace { using savegame_pof_names_type = std::array; static int convert_vclip(const d_vclip_array &Vclip, int vc) { if (vc < 0) return vc; if (vc < Vclip.size() && (Vclip[vc].num_frames != ~0u)) return vc; return 0; } static int convert_wclip(int wc) { return (wc < Num_wall_anims) ? wc : wc % Num_wall_anims; } } int convert_tmap(int tmap) { if (tmap == -1) return tmap; return (tmap >= NumTextures) ? tmap % NumTextures : tmap; } namespace { static unsigned convert_polymod(const unsigned N_polygon_models, const unsigned polymod) { return (polymod >= N_polygon_models) ? polymod % N_polygon_models : polymod; } } #elif defined(DXX_BUILD_DESCENT_II) namespace { using savegame_pof_names_type = std::array; } #endif namespace { static void verify_object(const d_vclip_array &Vclip, object &obj, const savegame_pof_names_type &Save_pof_names) { auto &Robot_info = LevelSharedRobotInfoState.Robot_info; obj.lifeleft = IMMORTAL_TIME; //all loaded object are immortal, for now auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; if (obj.type == OBJ_ROBOT) { Gamesave_num_org_robots++; // Make sure valid id... const auto N_robot_types = LevelSharedRobotInfoState.N_robot_types; if (get_robot_id(obj) >= N_robot_types ) set_robot_id(obj, get_robot_id(obj) % N_robot_types); // Make sure model number & size are correct... if (obj.render_type == RT_POLYOBJ) { auto &ri = Robot_info[get_robot_id(obj)]; #if defined(DXX_BUILD_DESCENT_II) assert(ri.model_num != -1); //if you fail this assert, it means that a robot in this level //hasn't been loaded, possibly because he's marked as //non-shareware. To see what robot number, print obj.id. assert(ri.always_0xabcd == 0xabcd); //if you fail this assert, it means that the robot_ai for //a robot in this level hasn't been loaded, possibly because //it's marked as non-shareware. To see what robot number, //print obj.id. #endif obj.rtype.pobj_info.model_num = ri.model_num; obj.size = Polygon_models[obj.rtype.pobj_info.model_num].rad; //@@Took out this ugly hack 1/12/96, because Mike has added code //@@that should fix it in a better way. //@@//this is a super-ugly hack. Since the baby stripe robots have //@@//their firing point on their bounding sphere, the firing points //@@//can poke through a wall if the robots are very close to it. So //@@//we make their radii bigger so the guns can't get too close to //@@//the walls //@@if (Robot_info[obj.id].flags & RIF_BIG_RADIUS) //@@ obj.size = (obj.size*3)/2; //@@if (obj.control_source==CT_AI && Robot_info[obj.id].attack_type) //@@ obj.size = obj.size*3/4; } #if defined(DXX_BUILD_DESCENT_II) if (get_robot_id(obj) == 65) //special "reactor" robots obj.movement_source = object::movement_type::None; #endif if (obj.movement_source == object::movement_type::physics) { auto &ri = Robot_info[get_robot_id(obj)]; obj.mtype.phys_info.mass = ri.mass; obj.mtype.phys_info.drag = ri.drag; } } else { //Robots taken care of above if (obj.render_type == RT_POLYOBJ) { const auto name = Save_pof_names[obj.rtype.pobj_info.model_num]; for (auto &&[i, candidate_name] : enumerate(partial_range(LevelSharedPolygonModelState.Pof_names, LevelSharedPolygonModelState.N_polygon_models))) if (!d_stricmp(candidate_name, name)) { //found it! obj.rtype.pobj_info.model_num = i; break; } } } if (obj.type == OBJ_POWERUP) { if ( get_powerup_id(obj) >= N_powerup_types ) { set_powerup_id(Powerup_info, Vclip, obj, POW_SHIELD_BOOST); Assert( obj.render_type != RT_POLYOBJ ); } obj.control_source = object::control_type::powerup; obj.size = Powerup_info[get_powerup_id(obj)].size; obj.ctype.powerup_info.creation_time = 0; } if (obj.type == OBJ_WEAPON) { if ( get_weapon_id(obj) >= N_weapon_types ) { set_weapon_id(obj, weapon_id_type::LASER_ID_L1); Assert( obj.render_type != RT_POLYOBJ ); } #if defined(DXX_BUILD_DESCENT_II) const auto weapon_id = get_weapon_id(obj); if (weapon_id == weapon_id_type::PMINE_ID) { //make sure pmines have correct values obj.mtype.phys_info.mass = Weapon_info[weapon_id].mass; obj.mtype.phys_info.drag = Weapon_info[weapon_id].drag; obj.mtype.phys_info.flags |= PF_FREE_SPINNING; // Make sure model number & size are correct... Assert( obj.render_type == RT_POLYOBJ ); obj.rtype.pobj_info.model_num = Weapon_info[weapon_id].model_num; obj.size = Polygon_models[obj.rtype.pobj_info.model_num].rad; } #endif } if (obj.type == OBJ_CNTRLCEN) { obj.render_type = RT_POLYOBJ; obj.control_source = object::control_type::cntrlcen; #if defined(DXX_BUILD_DESCENT_I) // Make model number is correct... for (int i=0; imodel_num; //Make sure orient matrix is orthogonal check_and_fix_matrix(obj.orient); set_player_id(obj, Gamesave_num_players++); } if (obj.type == OBJ_HOSTAGE) { obj.render_type = RT_HOSTAGE; obj.control_source = object::control_type::powerup; } } //static gs_skip(int len,PHYSFS_File *file) //{ // // PHYSFSX_fseek(file,len,SEEK_CUR); //} //reads one object of the given version from the given file static void read_object(const vmobjptr_t obj,PHYSFS_File *f,int version) { const auto poison_obj = reinterpret_cast(&*obj); DXX_POISON_MEMORY(poison_obj, sizeof(*obj), 0xfd); obj->signature = object_signature_t{0}; set_object_type(*obj, PHYSFSX_readByte(f)); obj->id = PHYSFSX_readByte(f); if (obj->type == OBJ_ROBOT) { #if defined(DXX_BUILD_DESCENT_I) const auto id = get_robot_id(obj); if (id > 23) set_robot_id(obj, id % 24); #endif obj->matcen_creator = 0; } { uint8_t ctype = PHYSFSX_readByte(f); switch (typename object::control_type{ctype}) { case object::control_type::None: case object::control_type::ai: case object::control_type::explosion: case object::control_type::flying: case object::control_type::slew: case object::control_type::flythrough: case object::control_type::weapon: case object::control_type::repaircen: case object::control_type::morph: case object::control_type::debris: case object::control_type::powerup: case object::control_type::light: case object::control_type::remote: case object::control_type::cntrlcen: break; default: ctype = static_cast(object::control_type::None); break; } obj->control_source = typename object::control_type{ctype}; } { uint8_t mtype = PHYSFSX_readByte(f); switch (typename object::movement_type{mtype}) { case object::movement_type::None: case object::movement_type::physics: case object::movement_type::spinning: break; default: mtype = static_cast(object::movement_type::None); break; } obj->movement_source = typename object::movement_type{mtype}; } const uint8_t render_type = PHYSFSX_readByte(f); if (valid_render_type(render_type)) obj->render_type = render_type_t{render_type}; else { LevelError("Level contains bogus render type %#x for object %p; using none instead", render_type, &*obj); obj->render_type = RT_NONE; } obj->flags = PHYSFSX_readByte(f); obj->segnum = PHYSFSX_readShort(f); obj->attached_obj = object_none; PHYSFSX_readVector(f, obj->pos); PHYSFSX_readMatrix(&obj->orient,f); obj->size = PHYSFSX_readFix(f); obj->shields = PHYSFSX_readFix(f); { vms_vector last_pos; PHYSFSX_readVector(f, last_pos); } obj->contains_type = PHYSFSX_readByte(f); obj->contains_id = PHYSFSX_readByte(f); obj->contains_count = PHYSFSX_readByte(f); switch (obj->movement_source) { case object::movement_type::physics: PHYSFSX_readVector(f, obj->mtype.phys_info.velocity); PHYSFSX_readVector(f, obj->mtype.phys_info.thrust); obj->mtype.phys_info.mass = PHYSFSX_readFix(f); obj->mtype.phys_info.drag = PHYSFSX_readFix(f); PHYSFSX_readFix(f); /* brakes */ PHYSFSX_readVector(f, obj->mtype.phys_info.rotvel); PHYSFSX_readVector(f, obj->mtype.phys_info.rotthrust); obj->mtype.phys_info.turnroll = PHYSFSX_readFixAng(f); obj->mtype.phys_info.flags = PHYSFSX_readShort(f); break; case object::movement_type::spinning: PHYSFSX_readVector(f, obj->mtype.spin_rate); break; case object::movement_type::None: break; default: Int3(); } switch (obj->control_source) { case object::control_type::ai: { obj->ctype.ai_info.behavior = static_cast(PHYSFSX_readByte(f)); range_for (auto &i, obj->ctype.ai_info.flags) i = PHYSFSX_readByte(f); obj->ctype.ai_info.hide_segment = PHYSFSX_readShort(f); obj->ctype.ai_info.hide_index = PHYSFSX_readShort(f); obj->ctype.ai_info.path_length = PHYSFSX_readShort(f); obj->ctype.ai_info.cur_path_index = PHYSFSX_readShort(f); if (version <= 25) { PHYSFSX_readShort(f); // obj->ctype.ai_info.follow_path_start_seg = PHYSFSX_readShort(f); // obj->ctype.ai_info.follow_path_end_seg = } break; } case object::control_type::explosion: obj->ctype.expl_info.spawn_time = PHYSFSX_readFix(f); obj->ctype.expl_info.delete_time = PHYSFSX_readFix(f); obj->ctype.expl_info.delete_objnum = PHYSFSX_readShort(f); obj->ctype.expl_info.next_attach = obj->ctype.expl_info.prev_attach = obj->ctype.expl_info.attach_parent = object_none; break; case object::control_type::weapon: //do I really need to read these? Are they even saved to disk? obj->ctype.laser_info.parent_type = PHYSFSX_readShort(f); obj->ctype.laser_info.parent_num = PHYSFSX_readShort(f); obj->ctype.laser_info.parent_signature = object_signature_t{static_cast(PHYSFSX_readInt(f))}; #if defined(DXX_BUILD_DESCENT_II) obj->ctype.laser_info.last_afterburner_time = 0; #endif obj->ctype.laser_info.clear_hitobj(); break; case object::control_type::light: obj->ctype.light_info.intensity = PHYSFSX_readFix(f); break; case object::control_type::powerup: if (version >= 25) obj->ctype.powerup_info.count = PHYSFSX_readInt(f); else obj->ctype.powerup_info.count = 1; if (obj->type == OBJ_POWERUP) { /* Objects loaded from a level file were not ejected by * the player. */ obj->ctype.powerup_info.flags = 0; /* Hostages have control type object::control_type::powerup, but object * type OBJ_HOSTAGE. Hostages are never weapons, so * prevent checking their IDs. */ if (get_powerup_id(obj) == POW_VULCAN_WEAPON) obj->ctype.powerup_info.count = VULCAN_WEAPON_AMMO_AMOUNT; #if defined(DXX_BUILD_DESCENT_II) else if (get_powerup_id(obj) == POW_GAUSS_WEAPON) obj->ctype.powerup_info.count = VULCAN_WEAPON_AMMO_AMOUNT; else if (get_powerup_id(obj) == POW_OMEGA_WEAPON) obj->ctype.powerup_info.count = MAX_OMEGA_CHARGE; #endif } break; case object::control_type::None: case object::control_type::flying: case object::control_type::debris: break; case object::control_type::slew: //the player is generally saved as slew break; case object::control_type::cntrlcen: break; case object::control_type::morph: case object::control_type::flythrough: case object::control_type::repaircen: default: Int3(); } switch (obj->render_type) { case RT_NONE: break; case RT_MORPH: case RT_POLYOBJ: { int tmo; #if defined(DXX_BUILD_DESCENT_I) obj->rtype.pobj_info.model_num = convert_polymod(LevelSharedPolygonModelState.N_polygon_models, PHYSFSX_readInt(f)); #elif defined(DXX_BUILD_DESCENT_II) obj->rtype.pobj_info.model_num = PHYSFSX_readInt(f); #endif range_for (auto &i, obj->rtype.pobj_info.anim_angles) PHYSFSX_readAngleVec(&i, f); obj->rtype.pobj_info.subobj_flags = PHYSFSX_readInt(f); tmo = PHYSFSX_readInt(f); #if !DXX_USE_EDITOR #if defined(DXX_BUILD_DESCENT_I) obj->rtype.pobj_info.tmap_override = convert_tmap(tmo); #elif defined(DXX_BUILD_DESCENT_II) obj->rtype.pobj_info.tmap_override = tmo; #endif #else if (tmo==-1) obj->rtype.pobj_info.tmap_override = -1; else { int xlated_tmo = tmap_xlate_table[tmo]; if (xlated_tmo < 0) { Int3(); xlated_tmo = 0; } obj->rtype.pobj_info.tmap_override = xlated_tmo; } #endif obj->rtype.pobj_info.alt_textures = 0; break; } case RT_WEAPON_VCLIP: case RT_HOSTAGE: case RT_POWERUP: case RT_FIREBALL: #if defined(DXX_BUILD_DESCENT_I) obj->rtype.vclip_info.vclip_num = convert_vclip(Vclip, PHYSFSX_readInt(f)); #elif defined(DXX_BUILD_DESCENT_II) obj->rtype.vclip_info.vclip_num = PHYSFSX_readInt(f); #endif obj->rtype.vclip_info.frametime = PHYSFSX_readFix(f); obj->rtype.vclip_info.framenum = PHYSFSX_readByte(f); break; case RT_LASER: break; default: Int3(); } } } } #if DXX_USE_EDITOR namespace { static int PHYSFSX_writeMatrix(PHYSFS_File *file, const vms_matrix &m) { if (PHYSFSX_writeVector(file, m.rvec) < 1 || PHYSFSX_writeVector(file, m.uvec) < 1 || PHYSFSX_writeVector(file, m.fvec) < 1) return 0; return 1; } static int PHYSFSX_writeAngleVec(PHYSFS_File *file, const vms_angvec &v) { if (PHYSFSX_writeFixAng(file, v.p) < 1 || PHYSFSX_writeFixAng(file, v.b) < 1 || PHYSFSX_writeFixAng(file, v.h) < 1) return 0; return 1; } } //writes one object to the given file namespace dsx { namespace { static void write_object(const object &obj, short version, PHYSFS_File *f) { #if defined(DXX_BUILD_DESCENT_I) (void)version; #endif PHYSFSX_writeU8(f, obj.type); PHYSFSX_writeU8(f, obj.id); PHYSFSX_writeU8(f, static_cast(obj.control_source)); PHYSFSX_writeU8(f, static_cast(obj.movement_source)); PHYSFSX_writeU8(f, obj.render_type); PHYSFSX_writeU8(f, obj.flags); PHYSFS_writeSLE16(f, obj.segnum); PHYSFSX_writeVector(f, obj.pos); PHYSFSX_writeMatrix(f, obj.orient); PHYSFSX_writeFix(f, obj.size); PHYSFSX_writeFix(f, obj.shields); PHYSFSX_writeVector(f, obj.pos); PHYSFSX_writeU8(f, obj.contains_type); PHYSFSX_writeU8(f, obj.contains_id); PHYSFSX_writeU8(f, obj.contains_count); switch (obj.movement_source) { case object::movement_type::physics: PHYSFSX_writeVector(f, obj.mtype.phys_info.velocity); PHYSFSX_writeVector(f, obj.mtype.phys_info.thrust); PHYSFSX_writeFix(f, obj.mtype.phys_info.mass); PHYSFSX_writeFix(f, obj.mtype.phys_info.drag); PHYSFSX_writeFix(f, 0); /* brakes */ PHYSFSX_writeVector(f, obj.mtype.phys_info.rotvel); PHYSFSX_writeVector(f, obj.mtype.phys_info.rotthrust); PHYSFSX_writeFixAng(f, obj.mtype.phys_info.turnroll); PHYSFS_writeSLE16(f, obj.mtype.phys_info.flags); break; case object::movement_type::spinning: PHYSFSX_writeVector(f, obj.mtype.spin_rate); break; case object::movement_type::None: break; default: Int3(); } switch (obj.control_source) { case object::control_type::ai: { PHYSFSX_writeU8(f, static_cast(obj.ctype.ai_info.behavior)); range_for (auto &i, obj.ctype.ai_info.flags) PHYSFSX_writeU8(f, i); PHYSFS_writeSLE16(f, obj.ctype.ai_info.hide_segment); PHYSFS_writeSLE16(f, obj.ctype.ai_info.hide_index); PHYSFS_writeSLE16(f, obj.ctype.ai_info.path_length); PHYSFS_writeSLE16(f, obj.ctype.ai_info.cur_path_index); #if defined(DXX_BUILD_DESCENT_I) PHYSFS_writeSLE16(f, segment_none); PHYSFS_writeSLE16(f, segment_none); #elif defined(DXX_BUILD_DESCENT_II) if (version <= 25) { PHYSFS_writeSLE16(f, -1); //obj.ctype.ai_info.follow_path_start_seg PHYSFS_writeSLE16(f, -1); //obj.ctype.ai_info.follow_path_end_seg } #endif break; } case object::control_type::explosion: PHYSFSX_writeFix(f, obj.ctype.expl_info.spawn_time); PHYSFSX_writeFix(f, obj.ctype.expl_info.delete_time); PHYSFS_writeSLE16(f, obj.ctype.expl_info.delete_objnum); break; case object::control_type::weapon: //do I really need to write these objects? PHYSFS_writeSLE16(f, obj.ctype.laser_info.parent_type); PHYSFS_writeSLE16(f, obj.ctype.laser_info.parent_num); PHYSFS_writeSLE32(f, static_cast(obj.ctype.laser_info.parent_signature)); break; case object::control_type::light: PHYSFSX_writeFix(f, obj.ctype.light_info.intensity); break; case object::control_type::powerup: #if defined(DXX_BUILD_DESCENT_I) PHYSFS_writeSLE32(f, obj.ctype.powerup_info.count); #elif defined(DXX_BUILD_DESCENT_II) if (version >= 25) PHYSFS_writeSLE32(f, obj.ctype.powerup_info.count); #endif break; case object::control_type::None: case object::control_type::flying: case object::control_type::debris: break; case object::control_type::slew: //the player is generally saved as slew break; case object::control_type::cntrlcen: break; //control center object. case object::control_type::morph: case object::control_type::repaircen: case object::control_type::flythrough: default: Int3(); } switch (obj.render_type) { case RT_NONE: break; case RT_MORPH: case RT_POLYOBJ: { PHYSFS_writeSLE32(f, obj.rtype.pobj_info.model_num); range_for (auto &i, obj.rtype.pobj_info.anim_angles) PHYSFSX_writeAngleVec(f, i); PHYSFS_writeSLE32(f, obj.rtype.pobj_info.subobj_flags); PHYSFS_writeSLE32(f, obj.rtype.pobj_info.tmap_override); break; } case RT_WEAPON_VCLIP: case RT_HOSTAGE: case RT_POWERUP: case RT_FIREBALL: PHYSFS_writeSLE32(f, obj.rtype.vclip_info.vclip_num); PHYSFSX_writeFix(f, obj.rtype.vclip_info.frametime); PHYSFSX_writeU8(f, obj.rtype.vclip_info.framenum); break; case RT_LASER: break; default: Int3(); } } } } #endif // -------------------------------------------------------------------- // Load game // Loads all the relevant data for a level. // If level != -1, it loads the filename with extension changed to .min // Otherwise it loads the appropriate level mine. // returns 0=everything ok, 1=old version, -1=error namespace dsx { namespace { static void validate_segment_wall(const vcsegptridx_t seg, shared_side &side, const unsigned sidenum) { auto &rwn0 = side.wall_num; const auto wn0 = rwn0; auto &Walls = LevelUniqueWallSubsystemState.Walls; auto &vcwallptr = Walls.vcptr; auto &w0 = *vcwallptr(wn0); switch (w0.type) { case WALL_DOOR: { const auto connected_seg = seg->shared_segment::children[sidenum]; if (connected_seg == segment_none) { rwn0 = wall_none; LevelError("segment %u side %u wall %u has no child segment; removing orphan wall.", seg.get_unchecked_index(), sidenum, static_cast(wn0)); return; } const shared_segment &vcseg = *vcsegptr(connected_seg); const unsigned connected_side = find_connect_side(seg, vcseg); const auto wn1 = vcseg.sides[connected_side].wall_num; if (wn1 == wall_none) { rwn0 = wall_none; LevelError("segment %u side %u wall %u has child segment %u side %u, but no wall; removing orphan wall.", seg.get_unchecked_index(), sidenum, static_cast(wn0), connected_seg, connected_side); return; } } break; default: break; } } static int load_game_data( #if defined(DXX_BUILD_DESCENT_II) d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, #endif fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, PHYSFS_File *LoadFile) { auto &Objects = LevelUniqueObjectState.Objects; auto &vmobjptr = Objects.vmptr; auto &WallAnims = GameSharedState.WallAnims; auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; const auto &vcsegptridx = vmsegptridx; short game_top_fileinfo_version; int object_offset; unsigned gs_num_objects; int trig_size; //===================== READ FILE INFO ======================== // Check signature if (PHYSFSX_readShort(LoadFile) != 0x6705) return -1; // Read and check version number game_top_fileinfo_version = PHYSFSX_readShort(LoadFile); if (game_top_fileinfo_version < GAME_COMPATIBLE_VERSION ) return -1; // We skip some parts of the former game_top_fileinfo PHYSFSX_fseek(LoadFile, 31, SEEK_CUR); object_offset = PHYSFSX_readInt(LoadFile); gs_num_objects = PHYSFSX_readInt(LoadFile); PHYSFSX_fseek(LoadFile, 8, SEEK_CUR); init_exploding_walls(); auto &Walls = LevelUniqueWallSubsystemState.Walls; Walls.set_count(PHYSFSX_readInt(LoadFile)); PHYSFSX_fseek(LoadFile, 20, SEEK_CUR); auto &Triggers = LevelUniqueWallSubsystemState.Triggers; Triggers.set_count(PHYSFSX_readInt(LoadFile)); PHYSFSX_fseek(LoadFile, 24, SEEK_CUR); trig_size = PHYSFSX_readInt(LoadFile); Assert(trig_size == sizeof(ControlCenterTriggers)); (void)trig_size; PHYSFSX_fseek(LoadFile, 4, SEEK_CUR); const unsigned Num_robot_centers = PHYSFSX_readInt(LoadFile); LevelSharedRobotcenterState.Num_robot_centers = Num_robot_centers; PHYSFSX_fseek(LoadFile, 4, SEEK_CUR); #if defined(DXX_BUILD_DESCENT_I) #elif defined(DXX_BUILD_DESCENT_II) unsigned num_delta_lights; unsigned Num_static_lights; if (game_top_fileinfo_version >= 29) { PHYSFSX_fseek(LoadFile, 4, SEEK_CUR); Num_static_lights = PHYSFSX_readInt(LoadFile); PHYSFSX_fseek(LoadFile, 8, SEEK_CUR); num_delta_lights = PHYSFSX_readInt(LoadFile); PHYSFSX_fseek(LoadFile, 4, SEEK_CUR); } else { Num_static_lights = 0; num_delta_lights = 0; } #endif if (game_top_fileinfo_version >= 31) //load mine filename // read newline-terminated string, not sure what version this changed. PHYSFSX_fgets(Current_level_name,LoadFile); else if (game_top_fileinfo_version >= 14) { //load mine filename // read null-terminated string //must do read one char at a time, since no PHYSFSX_fgets() for (auto p = Current_level_name.next().begin(); (*p = PHYSFSX_fgetc(LoadFile));) { if (++p == Current_level_name.line().end()) { p[-1] = 0; while (PHYSFSX_fgetc(LoadFile)) ; break; } } } else Current_level_name.next()[0]=0; savegame_pof_names_type Save_pof_names; if (game_top_fileinfo_version >= 19) { //load pof names const unsigned N_save_pof_names = PHYSFSX_readShort(LoadFile); if (N_save_pof_names < MAX_POLYGON_MODELS) PHYSFS_read(LoadFile,Save_pof_names,N_save_pof_names,FILENAME_LEN); else LevelError("Level contains bogus N_save_pof_names %#x; ignoring", N_save_pof_names); } //===================== READ PLAYER INFO ========================== //===================== READ OBJECT INFO ========================== Gamesave_num_org_robots = 0; Gamesave_num_players = 0; if (object_offset > -1) { if (PHYSFSX_fseek( LoadFile, object_offset, SEEK_SET )) Error( "Error seeking to object_offset in gamesave.c" ); range_for (auto &i, partial_range(Objects, gs_num_objects)) { const auto &&o = vmobjptr(&i); read_object(o, LoadFile, game_top_fileinfo_version); verify_object(Vclip, o, Save_pof_names); } } //===================== READ WALL INFO ============================ auto &vmwallptr = Walls.vmptr; range_for (const auto &&vw, vmwallptr) { auto &nw = *vw; if (game_top_fileinfo_version >= 20) wall_read(LoadFile, nw); // v20 walls and up. else if (game_top_fileinfo_version >= 17) { v19_wall w; v19_wall_read(LoadFile, w); nw.segnum = w.segnum; nw.sidenum = w.sidenum; nw.linked_wall = w.linked_wall; nw.type = w.type; nw.flags = w.flags & ~WALL_EXPLODING; nw.hps = w.hps; nw.trigger = static_cast(w.trigger); #if defined(DXX_BUILD_DESCENT_I) nw.clip_num = convert_wclip(w.clip_num); #elif defined(DXX_BUILD_DESCENT_II) nw.clip_num = w.clip_num; #endif nw.keys = static_cast(w.keys); nw.state = WALL_DOOR_CLOSED; } else { v16_wall w; v16_wall_read(LoadFile, w); nw.segnum = segment_none; nw.sidenum = -1; nw.linked_wall = wall_none; nw.type = w.type; nw.flags = w.flags & ~WALL_EXPLODING; nw.hps = w.hps; nw.trigger = static_cast(w.trigger); #if defined(DXX_BUILD_DESCENT_I) nw.clip_num = convert_wclip(w.clip_num); #elif defined(DXX_BUILD_DESCENT_II) nw.clip_num = w.clip_num; #endif nw.keys = static_cast(w.keys); } } //==================== READ TRIGGER INFO ========================== auto &vmtrgptr = Triggers.vmptr; range_for (const auto vt, vmtrgptr) { auto &i = *vt; #if defined(DXX_BUILD_DESCENT_I) if (game_top_fileinfo_version <= 25) v25_trigger_read(LoadFile, &i); else { v26_trigger_read(LoadFile, i); } #elif defined(DXX_BUILD_DESCENT_II) if (game_top_fileinfo_version < 31) { if (game_top_fileinfo_version < 30) { v29_trigger_read_as_v31(LoadFile, i); } else v30_trigger_read_as_v31(LoadFile, i); } else trigger_read(&i, LoadFile); #endif } //================ READ CONTROL CENTER TRIGGER INFO =============== control_center_triggers_read(&ControlCenterTriggers, LoadFile); //================ READ MATERIALOGRIFIZATIONATORS INFO =============== for (auto &&[i, r] : enumerate(partial_range(RobotCenters, Num_robot_centers))) { #if defined(DXX_BUILD_DESCENT_I) matcen_info_read(LoadFile, r, game_top_fileinfo_version); #elif defined(DXX_BUILD_DESCENT_II) if (game_top_fileinfo_version < 27) { d1_matcen_info_read(LoadFile, r); } else matcen_info_read(LoadFile, r); #endif // Set links in RobotCenters to Station array range_for (const shared_segment &seg, partial_const_range(Segments, Highest_segment_index + 1)) if (seg.special == SEGMENT_IS_ROBOTMAKER) if (seg.matcen_num == i) r.fuelcen_num = seg.station_idx; } #if defined(DXX_BUILD_DESCENT_II) //================ READ DL_INDICES INFO =============== { auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices; Dl_indices.set_count(Num_static_lights); if (game_top_fileinfo_version < 29) { if (Num_static_lights) throw std::logic_error("Static lights in old file"); } else { const auto &&lr = partial_range(Dl_indices, Num_static_lights); range_for (auto &i, lr) dl_index_read(&i, LoadFile); std::sort(lr.begin(), lr.end()); } } // Indicate that no light has been subtracted from any vertices. clear_light_subtracted(); //================ READ DELTA LIGHT INFO =============== if (game_top_fileinfo_version < 29) { ; } else { auto &Delta_lights = LevelSharedDestructibleLightState.Delta_lights; range_for (auto &i, partial_range(Delta_lights, num_delta_lights)) delta_light_read(&i, LoadFile); } #endif //========================= UPDATE VARIABLES ====================== reset_objects(LevelUniqueObjectState, gs_num_objects); range_for (auto &i, Objects) { if (i.type != OBJ_NONE) { auto objsegnum = i.segnum; if (objsegnum > Highest_segment_index) //bogus object { Warning("Object %p is in non-existent segment %i, highest=%i", &i, objsegnum, Highest_segment_index); i.type = OBJ_NONE; } else { obj_link_unchecked(Objects.vmptr, vmobjptridx(&i), vmsegptridx(objsegnum)); } } } clear_transient_objects(1); //1 means clear proximity bombs // Make sure non-transparent doors are set correctly. range_for (auto &&i, vmsegptridx) for (auto &&[sside, uside, side_idx] : zip(i->shared_segment::sides, i->unique_segment::sides, xrange(MAX_SIDES_PER_SEGMENT))) { if (sside.wall_num == wall_none) continue; auto &w = *vmwallptr(sside.wall_num); if (w.clip_num != -1) { auto &wa = WallAnims[w.clip_num]; if (wa.flags & WCF_TMAP1) { uside.tmap_num = build_texture1_value(wa.frames[0]); uside.tmap_num2 = texture2_value(); } } validate_segment_wall(i, sside, side_idx); } auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors; ActiveDoors.set_count(0); //go through all walls, killing references to invalid triggers range_for (const auto &&p, vmwallptr) { auto &w = *p; if (underlying_value(w.trigger) >= Triggers.get_count()) { w.trigger = trigger_none; //kill trigger } } #if DXX_USE_EDITOR //go through all triggers, killing unused ones { const auto &&wr = make_range(vmwallptr); for (auto iter = Triggers.vmptridx.begin(); iter != Triggers.vmptridx.end();) { const auto i = (*iter).get_unchecked_index(); auto a = [i](const wall &w) { return w.trigger == i; }; // Find which wall this trigger is connected to. auto w = std::find_if(wr.begin(), wr.end(), a); if (w == wr.end()) { remove_trigger_num(Triggers, Walls.vmptr, i); } else ++iter; } } #endif // MK, 10/17/95: Make walls point back at the triggers that control them. // Go through all triggers, stuffing controlling_trigger field in Walls. { #if defined(DXX_BUILD_DESCENT_II) range_for (const auto &&w, vmwallptr) w->controlling_trigger = trigger_none; #endif auto &vctrgptridx = Triggers.vcptridx; range_for (const auto &&t, vctrgptridx) { auto &tr = *t; for (unsigned l = 0; l < tr.num_links; ++l) { //check to see that if a trigger requires a wall that it has one, //and if it requires a matcen that it has one const auto seg_num = tr.seg[l]; if (trigger_is_matcen(tr)) { if (Segments[seg_num].special != SEGMENT_IS_ROBOTMAKER) con_printf(CON_URGENT, "matcen %u triggers non-matcen segment %hu", underlying_value(t.get_unchecked_index()), seg_num); } #if defined(DXX_BUILD_DESCENT_II) else if (tr.type != trigger_action::light_off && tr.type != trigger_action::light_on) { //light triggers don't require walls const auto side_num = tr.side[l]; auto wall_num = vmsegptr(seg_num)->shared_segment::sides[side_num].wall_num; if (const auto &&uwall = vmwallptr.check_untrusted(wall_num)) (*uwall)->controlling_trigger = t; else { LevelError("trigger %u link %u type %u references segment %hu, side %u which is an invalid wall; ignoring.", underlying_value(t.get_unchecked_index()), l, static_cast(tr.type), seg_num, side_num); } } #endif } } } //fix old wall structs if (game_top_fileinfo_version < 17) { range_for (const auto &&segp, vcsegptridx) { range_for (const int sidenum, xrange(6u)) { const auto wallnum = segp->shared_segment::sides[sidenum].wall_num; if (wallnum != wall_none) { auto &w = *vmwallptr(wallnum); w.segnum = segp; w.sidenum = sidenum; } } } } fix_object_segs(); if (game_top_fileinfo_version < GAME_VERSION #if defined(DXX_BUILD_DESCENT_II) && !(game_top_fileinfo_version == 25 && GAME_VERSION == 26) #endif ) return 1; //means old version else return 0; } } } // ---------------------------------------------------------------------------- #if defined(DXX_BUILD_DESCENT_I) #define LEVEL_FILE_VERSION 1 #elif defined(DXX_BUILD_DESCENT_II) #define LEVEL_FILE_VERSION 8 #endif //1 -> 2 add palette name //2 -> 3 add control center explosion time //3 -> 4 add reactor strength //4 -> 5 killed hostage text stuff //5 -> 6 added Secret_return_segment and Secret_return_orient //6 -> 7 added flickering lights //7 -> 8 made version 8 to be not compatible with D2 1.0 & 1.1 #ifndef RELEASE const char *Level_being_loaded=NULL; #endif #if defined(DXX_BUILD_DESCENT_II) int no_old_level_file_error=0; #endif //loads a level (.LVL) file from disk //returns 0 if success, else error code namespace dsx { int load_level( #if defined(DXX_BUILD_DESCENT_II) d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, #endif const char * filename_passed) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Objects = LevelUniqueObjectState.Objects; auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vmobjptridx = Objects.vmptridx; char filename[PATH_MAX]; int sig, minedata_offset, gamedata_offset; int mine_err, game_err; #ifndef RELEASE Level_being_loaded = filename_passed; #endif strcpy(filename,filename_passed); auto LoadFile = PHYSFSX_openReadBuffered(filename).first; if (!LoadFile) { 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); auto &&[fp, physfserr] = PHYSFSX_openReadBuffered(filename); if (!fp) { #if DXX_USE_EDITOR con_printf(CON_VERBOSE, "Failed to open file <%s>: %s", filename, PHYSFS_getErrorByCode(physfserr)); return 1; #else Error("Failed to open file <%s>: %s", filename, PHYSFS_getErrorByCode(physfserr)); #endif } LoadFile = std::move(fp); } sig = PHYSFSX_readInt(LoadFile); Gamesave_current_version = PHYSFSX_readInt(LoadFile); minedata_offset = PHYSFSX_readInt(LoadFile); gamedata_offset = PHYSFSX_readInt(LoadFile); Assert(sig == MAKE_SIG('P','L','V','L')); (void)sig; if (Gamesave_current_version < 5) PHYSFSX_readInt(LoadFile); //was hostagetext_offset init_exploding_walls(); #if defined(DXX_BUILD_DESCENT_II) if (Gamesave_current_version >= 8) { //read dummy data PHYSFSX_readInt(LoadFile); PHYSFSX_readShort(LoadFile); PHYSFSX_readByte(LoadFile); } if (Gamesave_current_version > 1) PHYSFSX_fgets(Current_level_palette,LoadFile); if (Gamesave_current_version <= 1 || Current_level_palette[0]==0) // descent 1 level strcpy(Current_level_palette.next().data(), DEFAULT_LEVEL_PALETTE); if (Gamesave_current_version >= 3) LevelSharedControlCenterState.Base_control_center_explosion_time = PHYSFSX_readInt(LoadFile); else LevelSharedControlCenterState.Base_control_center_explosion_time = DEFAULT_CONTROL_CENTER_EXPLOSION_TIME; if (Gamesave_current_version >= 4) LevelSharedControlCenterState.Reactor_strength = PHYSFSX_readInt(LoadFile); else LevelSharedControlCenterState.Reactor_strength = -1; //use old defaults if (Gamesave_current_version >= 7) { Flickering_light_state.Num_flickering_lights = PHYSFSX_readInt(LoadFile); range_for (auto &i, partial_range(Flickering_light_state.Flickering_lights, Flickering_light_state.Num_flickering_lights)) flickering_light_read(i, LoadFile); } else Flickering_light_state.Num_flickering_lights = 0; { auto &Secret_return_orient = LevelSharedSegmentState.Secret_return_orient; if (Gamesave_current_version < 6) { LevelSharedSegmentState.Secret_return_segment = segment_first; Secret_return_orient.rvec.x = F1_0; Secret_return_orient.rvec.y = 0; Secret_return_orient.rvec.z = 0; Secret_return_orient.fvec.x = 0; Secret_return_orient.fvec.y = F1_0; Secret_return_orient.fvec.z = 0; Secret_return_orient.uvec.x = 0; Secret_return_orient.uvec.y = 0; Secret_return_orient.uvec.z = F1_0; } else { LevelSharedSegmentState.Secret_return_segment = PHYSFSX_readInt(LoadFile); Secret_return_orient.rvec.x = PHYSFSX_readInt(LoadFile); Secret_return_orient.rvec.y = PHYSFSX_readInt(LoadFile); Secret_return_orient.rvec.z = PHYSFSX_readInt(LoadFile); Secret_return_orient.fvec.x = PHYSFSX_readInt(LoadFile); Secret_return_orient.fvec.y = PHYSFSX_readInt(LoadFile); Secret_return_orient.fvec.z = PHYSFSX_readInt(LoadFile); Secret_return_orient.uvec.x = PHYSFSX_readInt(LoadFile); Secret_return_orient.uvec.y = PHYSFSX_readInt(LoadFile); Secret_return_orient.uvec.z = PHYSFSX_readInt(LoadFile); } } #endif PHYSFSX_fseek(LoadFile,minedata_offset,SEEK_SET); //NOTE LINK TO ABOVE!! mine_err = load_mine_data_compiled(LoadFile, filename); /* !!!HACK!!! * Descent 1 - Level 19: OBERON MINE has some ugly overlapping rooms (segment 484). * HACK to make this issue less visible by moving one vertex a little. */ auto &vmvertptr = Vertices.vmptr; if (Current_mission && !d_stricmp("Descent: First Strike",Current_mission_longname) && !d_stricmp("level19.rdl",filename) && PHYSFS_fileLength(LoadFile) == 136706) vmvertptr(vertnum_t{1905u})->z = -385 * F1_0; #if defined(DXX_BUILD_DESCENT_II) /* !!!HACK!!! * Descent 2 - Level 12: MAGNACORE STATION has a segment (104) with illegal dimensions. * HACK to fix this by moving the Vertex and fixing the associated Normals. * NOTE: This only fixes the normals of segment 104, not the other ones connected to this Vertex but this is unsignificant. */ if (Current_mission && !d_stricmp("Descent 2: Counterstrike!",Current_mission_longname) && !d_stricmp("d2levc-4.rl2",filename)) { shared_segment &s104 = *vmsegptr(vmsegidx_t(104)); auto &s104v0 = *vmvertptr(s104.verts[0]); auto &s104s1 = s104.sides[1]; auto &s104s1n0 = s104s1.normals[0]; auto &s104s1n1 = s104s1.normals[1]; auto &s104s2 = s104.sides[2]; auto &s104s2n0 = s104s2.normals[0]; auto &s104s2n1 = s104s2.normals[1]; if ( (s104v0.x == -53990800 && s104v0.y == -59927741 && s104v0.z == 23034584) && (s104s1n0.x == 56775 && s104s1n0.y == -27796 && s104s1n0.z == -17288 && s104s1n1.x == 50157 && s104s1n1.y == -34561 && s104s1n1.z == -24180) && (s104s2n0.x == 60867 && s104s2n0.y == -19485 && s104s2n0.z == -14507 && s104s2n1.x == 55485 && s104s2n1.y == -29668 && s104s2n1.z == -18332) ) { s104v0.x = -53859726; s104v0.y = -59927743; s104v0.z = 23034586; s104s1n0.x = 56123; s104s1n0.y = -27725; s104s1n0.z = -19401; s104s1n1.x = 49910; s104s1n1.y = -33946; s104s1n1.z = -25525; s104s2n0.x = 60903; s104s2n0.y = -18371; s104s2n0.z = -15753; s104s2n1.x = 57004; s104s2n1.y = -26385; s104s2n1.z = -18688; // I feel so dirty now ... } } #endif if (mine_err == -1) { //error!! return 2; } PHYSFSX_fseek(LoadFile,gamedata_offset,SEEK_SET); game_err = load_game_data( #if defined(DXX_BUILD_DESCENT_II) LevelSharedDestructibleLightState, #endif vmobjptridx, vmsegptridx, LoadFile); if (game_err == -1) { //error!! return 3; } //======================== CLOSE FILE ============================= LoadFile.reset(); #if defined(DXX_BUILD_DESCENT_II) set_ambient_sound_flags(); #endif #if DXX_USE_EDITOR #if defined(DXX_BUILD_DESCENT_I) //If an old version, ask the use if he wants to save as new version if (((LEVEL_FILE_VERSION>1) && Gamesave_current_version < LEVEL_FILE_VERSION) || mine_err==1 || game_err==1) { gr_palette_load(gr_palette); if (nm_messagebox_str(menu_title{nullptr}, nm_messagebox_tie("Don't Save", "Save"), menu_subtitle{"You just loaded a old version level. Would\n" "you like to save it as a current version level?"}) == 1) save_level(filename); } #elif defined(DXX_BUILD_DESCENT_II) //If a Descent 1 level and the Descent 1 pig isn't present, pretend it's a Descent 2 level. if (EditorWindow && (Gamesave_current_version <= 3) && !d1_pig_present) { if (!no_old_level_file_error) Warning("A Descent 1 level was loaded,\n" "and there is no Descent 1 texture\n" "set available. Saving it will\n" "convert it to a Descent 2 level."); Gamesave_current_version = LEVEL_FILE_VERSION; } #endif #endif #if DXX_USE_EDITOR if (EditorWindow) editor_status_fmt("Loaded NEW mine %s, \"%s\"", filename, static_cast(Current_level_name)); #endif #ifdef NDEBUG if (!PLAYING_BUILTIN_MISSION) #endif if (check_segment_connections()) { #ifndef NDEBUG nm_messagebox_str(menu_title{TXT_ERROR}, nm_messagebox_tie(TXT_OK), menu_subtitle{"Connectivity errors detected in\n" "mine. See monochrome screen for\n" "details, and contact Matt or Mike."}); #endif } #if defined(DXX_BUILD_DESCENT_II) compute_slide_segs(); #endif return 0; } } #if DXX_USE_EDITOR int get_level_name() { using items_type = std::array; struct request_menu : items_type, passive_newmenu { request_menu(grs_canvas &canvas) : items_type{{ newmenu_item::nm_item_text{"Please enter a name for this mine:"}, newmenu_item::nm_item_input(Current_level_name.next()), }}, passive_newmenu(menu_title{nullptr}, menu_subtitle{"Enter mine name"}, menu_filename{nullptr}, tiny_mode_flag::normal, tab_processing_flag::ignore, adjusted_citem::create(*static_cast(this), 1), canvas, draw_box_flag::none) { } }; return run_blocking_newmenu(*grd_curcanv); } #endif #if DXX_USE_EDITOR // -------------------------------------------------------------------------------------- // Create a new mine, set global variables. namespace dsx { int create_new_mine(void) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Vertices = LevelSharedVertexState.get_vertices(); vms_matrix m1 = IDENTITY_MATRIX; // initialize_mine_arrays(); // gamestate_not_restored = 1; // Clear refueling center code fuelcen_reset(); init_all_vertices(); Current_level_num = 1; // make level 1 (for now) Current_level_name.next()[0] = 0; #if defined(DXX_BUILD_DESCENT_I) Gamesave_current_version = LEVEL_FILE_VERSION; #elif defined(DXX_BUILD_DESCENT_II) Gamesave_current_version = GAME_VERSION; strcpy(Current_level_palette.next().data(), DEFAULT_LEVEL_PALETTE); #endif Cur_object_index = -1; reset_objects(LevelUniqueObjectState, 1); //just one object, the player num_groups = 0; current_group = -1; LevelSharedVertexState.Num_vertices = 0; // Number of vertices in global array. Vertices.set_count(1); LevelSharedSegmentState.Num_segments = 0; // Number of segments in global array, will get increased in med_create_segment Segments.set_count(1); Cursegp = imsegptridx(segment_first); // Say current segment is the only segment. Curside = WBACK; // The active side is the back side Markedsegp = segment_none; // Say there is no marked segment. Markedside = WBACK; // Shouldn't matter since Markedsegp == 0, but just in case... for (int s=0;scount; return total; } #endif // ----------------------------------------------------------------------------- // Save game static int save_game_data( #if defined(DXX_BUILD_DESCENT_II) const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, #endif PHYSFS_File *SaveFile) { auto &Objects = LevelUniqueObjectState.Objects; auto &vcobjptr = Objects.vcptr; auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; #if defined(DXX_BUILD_DESCENT_I) short game_top_fileinfo_version = Gamesave_current_version >= 5 ? 31 : GAME_VERSION; #elif defined(DXX_BUILD_DESCENT_II) short game_top_fileinfo_version = Gamesave_current_version >= 5 ? 31 : 25; int dl_indices_offset=0, delta_light_offset=0; #endif int player_offset=0, object_offset=0, walls_offset=0, doors_offset=0, triggers_offset=0, control_offset=0, matcen_offset=0; //, links_offset; int offset_offset=0, end_offset=0; //===================== SAVE FILE INFO ======================== PHYSFS_writeSLE16(SaveFile, 0x6705); // signature PHYSFS_writeSLE16(SaveFile, game_top_fileinfo_version); PHYSFS_writeSLE32(SaveFile, 0); PHYSFS_write(SaveFile, Current_level_name.line(), 15, 1); PHYSFS_writeSLE32(SaveFile, Current_level_num); offset_offset = PHYSFS_tell(SaveFile); // write the offsets later PHYSFS_writeSLE32(SaveFile, -1); PHYSFS_writeSLE32(SaveFile, 0); #define WRITE_HEADER_ENTRY(t, n) do { PHYSFS_writeSLE32(SaveFile, -1); PHYSFS_writeSLE32(SaveFile, n); PHYSFS_writeSLE32(SaveFile, sizeof(t)); } while(0) WRITE_HEADER_ENTRY(object, Highest_object_index + 1); auto &Walls = LevelUniqueWallSubsystemState.Walls; WRITE_HEADER_ENTRY(wall, Walls.get_count()); auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors; WRITE_HEADER_ENTRY(active_door, ActiveDoors.get_count()); auto &Triggers = LevelUniqueWallSubsystemState.Triggers; WRITE_HEADER_ENTRY(trigger, Triggers.get_count()); WRITE_HEADER_ENTRY(0, 0); // links (removed by Parallax) WRITE_HEADER_ENTRY(control_center_triggers, 1); const auto Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers; WRITE_HEADER_ENTRY(matcen_info, Num_robot_centers); #if defined(DXX_BUILD_DESCENT_II) unsigned num_delta_lights = 0; if (game_top_fileinfo_version >= 29) { auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices; const unsigned Num_static_lights = Dl_indices.get_count(); WRITE_HEADER_ENTRY(dl_index, Num_static_lights); WRITE_HEADER_ENTRY(delta_light, num_delta_lights = compute_num_delta_light_records(Dl_indices.vcptr)); } // Write the mine name if (game_top_fileinfo_version >= 31) #endif PHYSFSX_printf(SaveFile, "%s\n", static_cast(Current_level_name)); #if defined(DXX_BUILD_DESCENT_II) else if (game_top_fileinfo_version >= 14) PHYSFSX_writeString(SaveFile, Current_level_name); if (game_top_fileinfo_version >= 19) #endif { const auto N_polygon_models = LevelSharedPolygonModelState.N_polygon_models; PHYSFS_writeSLE16(SaveFile, N_polygon_models); range_for (auto &i, partial_const_range(LevelSharedPolygonModelState.Pof_names, N_polygon_models)) PHYSFS_write(SaveFile, &i, sizeof(i), 1); } //==================== SAVE PLAYER INFO =========================== player_offset = PHYSFS_tell(SaveFile); //==================== SAVE OBJECT INFO =========================== object_offset = PHYSFS_tell(SaveFile); range_for (const auto &&objp, vcobjptr) { write_object(objp, game_top_fileinfo_version, SaveFile); } //==================== SAVE WALL INFO ============================= walls_offset = PHYSFS_tell(SaveFile); auto &vcwallptr = Walls.vcptr; range_for (const auto &&w, vcwallptr) wall_write(SaveFile, *w, game_top_fileinfo_version); //==================== SAVE TRIGGER INFO ============================= triggers_offset = PHYSFS_tell(SaveFile); auto &vctrgptr = Triggers.vcptr; range_for (const auto vt, vctrgptr) { auto &t = *vt; if (game_top_fileinfo_version <= 29) v29_trigger_write(SaveFile, t); else if (game_top_fileinfo_version <= 30) v30_trigger_write(SaveFile, t); else if (game_top_fileinfo_version >= 31) v31_trigger_write(SaveFile, t); } //================ SAVE CONTROL CENTER TRIGGER INFO =============== control_offset = PHYSFS_tell(SaveFile); control_center_triggers_write(&ControlCenterTriggers, SaveFile); //================ SAVE MATERIALIZATION CENTER TRIGGER INFO =============== matcen_offset = PHYSFS_tell(SaveFile); range_for (auto &r, partial_const_range(RobotCenters, Num_robot_centers)) matcen_info_write(SaveFile, r, game_top_fileinfo_version); //================ SAVE DELTA LIGHT INFO =============== #if defined(DXX_BUILD_DESCENT_II) if (game_top_fileinfo_version >= 29) { dl_indices_offset = PHYSFS_tell(SaveFile); auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices; range_for (const auto &&i, Dl_indices.vcptr) dl_index_write(i, SaveFile); delta_light_offset = PHYSFS_tell(SaveFile); auto &Delta_lights = LevelSharedDestructibleLightState.Delta_lights; range_for (auto &i, partial_const_range(Delta_lights, num_delta_lights)) delta_light_write(&i, SaveFile); } #endif //============= SAVE OFFSETS =============== end_offset = PHYSFS_tell(SaveFile); // Update the offset fields #define WRITE_OFFSET(o, n) do { PHYSFS_seek(SaveFile, offset_offset); PHYSFS_writeSLE32(SaveFile, o ## _offset); offset_offset += sizeof(int)*n; } while (0) WRITE_OFFSET(player, 2); WRITE_OFFSET(object, 3); WRITE_OFFSET(walls, 3); WRITE_OFFSET(doors, 3); WRITE_OFFSET(triggers, 6); WRITE_OFFSET(control, 3); WRITE_OFFSET(matcen, 3); #if defined(DXX_BUILD_DESCENT_II) if (game_top_fileinfo_version >= 29) { WRITE_OFFSET(dl_indices, 3); WRITE_OFFSET(delta_light, 0); } #endif // Go back to end of data PHYSFS_seek(SaveFile, end_offset); return 0; } // ----------------------------------------------------------------------------- // Save game static int save_level_sub( #if defined(DXX_BUILD_DESCENT_II) const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, #endif fvmobjptridx &vmobjptridx, const char *const filename) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Objects = LevelUniqueObjectState.Objects; auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vmobjptr = Objects.vmptr; char temp_filename[PATH_MAX]; int minedata_offset=0,gamedata_offset=0; // if ( !compiled_version ) { write_game_text_file(filename); if (Errors_in_mine) { if (is_real_level(filename)) { gr_palette_load(gr_palette); if (nm_messagebox(menu_title{nullptr}, 2, "Cancel Save", "Save", "Warning: %i errors in this mine!\n", Errors_in_mine )!=1) { return 1; } } } // change_filename_extension(temp_filename,filename,".LVL"); } // else { #if defined(DXX_BUILD_DESCENT_II) if (Gamesave_current_version > 3) change_filename_extension(temp_filename, filename, "." D2X_LEVEL_FILE_EXTENSION); else #endif change_filename_extension(temp_filename, filename, "." D1X_LEVEL_FILE_EXTENSION); } auto &&[SaveFile, physfserr] = PHYSFSX_openWriteBuffered(temp_filename); if (!SaveFile) { gr_palette_load(gr_palette); nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "ERROR: Cannot write to '%s'.\n%s", temp_filename, PHYSFS_getErrorByCode(physfserr)); return 1; } if (Current_level_name[0] == 0) strcpy(Current_level_name.next().data(),"Untitled"); clear_transient_objects(1); //1 means clear proximity bombs compress_objects(); //after this, Highest_object_index == num objects //make sure player is in a segment { const auto &&plr = vmobjptridx(vcplayerptr(0u)->objnum); if (update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, plr) == 0) { if (plr->segnum > Highest_segment_index) plr->segnum = segment_first; auto &vcvertptr = Vertices.vcptr; compute_segment_center(vcvertptr, plr->pos, vcsegptr(plr->segnum)); } } fix_object_segs(); //Write the header PHYSFS_writeSLE32(SaveFile, MAKE_SIG('P','L','V','L')); PHYSFS_writeSLE32(SaveFile, Gamesave_current_version); //save placeholders PHYSFS_writeSLE32(SaveFile, minedata_offset); PHYSFS_writeSLE32(SaveFile, gamedata_offset); #if defined(DXX_BUILD_DESCENT_I) int hostagetext_offset = 0; PHYSFS_writeSLE32(SaveFile, hostagetext_offset); #endif //Now write the damn data #if defined(DXX_BUILD_DESCENT_II) if (Gamesave_current_version >= 8) { //write the version 8 data (to make file unreadable by 1.0 & 1.1) PHYSFS_writeSLE32(SaveFile, GameTime64); PHYSFS_writeSLE16(SaveFile, d_tick_count); PHYSFSX_writeU8(SaveFile, FrameTime); } if (Gamesave_current_version < 5) PHYSFS_writeSLE32(SaveFile, -1); //was hostagetext_offset // Write the palette file name if (Gamesave_current_version > 1) PHYSFSX_printf(SaveFile, "%s\n", static_cast(Current_level_palette)); if (Gamesave_current_version >= 3) PHYSFS_writeSLE32(SaveFile, LevelSharedControlCenterState.Base_control_center_explosion_time); if (Gamesave_current_version >= 4) PHYSFS_writeSLE32(SaveFile, LevelSharedControlCenterState.Reactor_strength); if (Gamesave_current_version >= 7) { const auto Num_flickering_lights = Flickering_light_state.Num_flickering_lights; PHYSFS_writeSLE32(SaveFile, Num_flickering_lights); range_for (auto &i, partial_const_range(Flickering_light_state.Flickering_lights, Num_flickering_lights)) flickering_light_write(i, SaveFile); } if (Gamesave_current_version >= 6) { PHYSFS_writeSLE32(SaveFile, LevelSharedSegmentState.Secret_return_segment); auto &Secret_return_orient = LevelSharedSegmentState.Secret_return_orient; PHYSFSX_writeVector(SaveFile, Secret_return_orient.rvec); PHYSFSX_writeVector(SaveFile, Secret_return_orient.fvec); PHYSFSX_writeVector(SaveFile, Secret_return_orient.uvec); } #endif minedata_offset = PHYSFS_tell(SaveFile); save_mine_data_compiled(SaveFile); gamedata_offset = PHYSFS_tell(SaveFile); save_game_data( #if defined(DXX_BUILD_DESCENT_II) LevelSharedDestructibleLightState, #endif SaveFile); #if defined(DXX_BUILD_DESCENT_I) hostagetext_offset = PHYSFS_tell(SaveFile); #endif PHYSFS_seek(SaveFile, sizeof(int) + sizeof(Gamesave_current_version)); PHYSFS_writeSLE32(SaveFile, minedata_offset); PHYSFS_writeSLE32(SaveFile, gamedata_offset); #if defined(DXX_BUILD_DESCENT_I) PHYSFS_writeSLE32(SaveFile, hostagetext_offset); #elif defined(DXX_BUILD_DESCENT_II) if (Gamesave_current_version < 5) PHYSFS_writeSLE32(SaveFile, PHYSFS_fileLength(SaveFile)); #endif //==================== CLOSE THE FILE ============================= // if ( !compiled_version ) { if (EditorWindow) editor_status_fmt("Saved mine %s, \"%s\"", filename, static_cast(Current_level_name)); } return 0; } } int save_level( #if defined(DXX_BUILD_DESCENT_II) const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, #endif const char * filename) { auto &Objects = LevelUniqueObjectState.Objects; auto &vmobjptridx = Objects.vmptridx; int r1; // Save compiled version... r1 = save_level_sub( #if defined(DXX_BUILD_DESCENT_II) LevelSharedDestructibleLightState, #endif vmobjptridx, filename); return r1; } } #endif //EDITOR