dxx-rebirth/similar/main/gamesave.cpp
Kp 3d246427e1 Poison objects on level load
Objects are overwritten without using obj_allocate, so the normal poison
path is not triggered.  Poison objects explicitly to trap uninitialized
data from a previous level.

This also fixes an unreported issue where player shields always reset to
100 on entering a new level.  The game is supposed to raise shields to
100 if the player was lower on exiting the level.  However, it is not
supposed to reduce shields to 100 if the player had a higher value on
exiting the level.

This also fixes a demo consistency issue reported by zicodxx <https://github.com/dxx-rebirth/dxx-rebirth/issues/168>.

Fixes: 2e28a491f2 ("Move player shields to struct object")
2015-12-10 03:25:37 +00:00

1810 lines
50 KiB
C++

/*
* 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 <stdexcept>
#include <stdio.h>
#include <string.h>
#include "pstypes.h"
#include "strutil.h"
#include "console.h"
#include "key.h"
#include "gr.h"
#include "palette.h"
#include "newmenu.h"
#include "inferno.h"
#ifdef 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 "screens.h"
#include "wall.h"
#include "gamemine.h"
#include "robot.h"
#include "bm.h"
#include "menu.h"
#include "fireball.h"
#include "switch.h"
#include "fuelcen.h"
#include "cntrlcen.h"
#include "powerup.h"
#include "hostage.h"
#include "weapon.h"
#include "player.h"
#include "newdemo.h"
#include "gameseq.h"
#include "automap.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 "dxxsconf.h"
#include "compiler-range_for.h"
#include "highest_valid.h"
#include "partial_range.h"
#if defined(DXX_BUILD_DESCENT_I)
#ifdef 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;
#ifdef 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;
#if defined(DXX_BUILD_DESCENT_I)
int N_save_pof_names=25;
#define MAX_POLYGON_MODELS_NEW 167
static array<char[FILENAME_LEN], MAX_POLYGON_MODELS_NEW> Save_pof_names;
static int convert_vclip(int vc) {
if (vc < 0)
return vc;
if ((vc < VCLIP_MAXNUM) && (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;
}
static int convert_polymod(int polymod) {
return (polymod >= N_polygon_models) ? polymod % N_polygon_models : polymod;
}
#elif defined(DXX_BUILD_DESCENT_II)
int N_save_pof_names;
static array<char[FILENAME_LEN], MAX_POLYGON_MODELS> Save_pof_names;
#endif
static void verify_object(const vobjptr_t obj)
{
obj->lifeleft = IMMORTAL_TIME; //all loaded object are immortal, for now
if ( obj->type == OBJ_ROBOT ) {
Gamesave_num_org_robots++;
// Make sure valid id...
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 ) {
#if defined(DXX_BUILD_DESCENT_II)
Assert(Robot_info[get_robot_id(obj)].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(Robot_info[get_robot_id(obj)].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 = Robot_info[get_robot_id(obj)].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_type==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_type = MT_NONE;
#endif
if (obj->movement_type == MT_PHYSICS) {
obj->mtype.phys_info.mass = Robot_info[get_robot_id(obj)].mass;
obj->mtype.phys_info.drag = Robot_info[get_robot_id(obj)].drag;
}
}
else { //Robots taken care of above
if ( obj->render_type == RT_POLYOBJ ) {
char *name = Save_pof_names[obj->rtype.pobj_info.model_num];
for (uint_fast32_t i = 0;i < N_polygon_models;i++)
if (!d_stricmp(Pof_names[i],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(obj, POW_SHIELD_BOOST);
Assert( obj->render_type != RT_POLYOBJ );
}
obj->control_type = CT_POWERUP;
obj->size = Powerup_info[get_powerup_id(obj)].size;
obj->ctype.powerup_info.creation_time = 0;
if (Game_mode & GM_NETWORK)
{
PowerupCaps.inc_powerup_both(get_powerup_id(obj));
}
}
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_type = CT_CNTRLCEN;
#if defined(DXX_BUILD_DESCENT_I)
// Make model number is correct...
for (int i=0; i<Num_total_object_types; i++ )
if ( ObjType[i] == OL_CONTROL_CENTER ) {
obj->rtype.pobj_info.model_num = ObjId[i];
obj->shields = ObjStrength[i];
break;
}
#elif defined(DXX_BUILD_DESCENT_II)
if (Gamesave_current_version <= 1) { // descent 1 reactor
set_reactor_id(obj, 0); // used to be only one kind of reactor
obj->rtype.pobj_info.model_num = Reactors[0].model_num;// descent 1 reactor
}
// Make sure model number is correct...
//obj->rtype.pobj_info.model_num = Reactors[obj->id].model_num;
#endif
}
if ( obj->type == OBJ_PLAYER ) {
//int i;
//Assert(obj == Player);
if ( obj == ConsoleObject )
init_player_object();
else
if (obj->render_type == RT_POLYOBJ) //recover from Matt's pof file matchup bug
obj->rtype.pobj_info.model_num = Player_ship->model_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) {
#if defined(DXX_BUILD_DESCENT_I)
if (get_hostage_id(obj) > N_hostage_types)
set_hostage_id(obj, 0);
#endif
obj->render_type = RT_HOSTAGE;
obj->control_type = CT_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 vobjptr_t obj,PHYSFS_file *f,int version)
{
const auto poison_obj = reinterpret_cast<uint8_t *>(&*obj);
DXX_POISON_MEMORY(poison_obj, sizeof(*obj), 0xfd);
obj->type = PHYSFSX_readByte(f);
obj->id = PHYSFSX_readByte(f);
#if defined(DXX_BUILD_DESCENT_I)
if (obj->type == OBJ_ROBOT && get_robot_id(obj) > 23) {
set_robot_id(obj, get_robot_id(obj) % 24);
}
#endif
obj->control_type = PHYSFSX_readByte(f);
obj->movement_type = PHYSFSX_readByte(f);
obj->render_type = PHYSFSX_readByte(f);
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);
PHYSFSX_readVector(f, obj->last_pos);
obj->contains_type = PHYSFSX_readByte(f);
obj->contains_id = PHYSFSX_readByte(f);
obj->contains_count = PHYSFSX_readByte(f);
switch (obj->movement_type) {
case MT_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 MT_SPINNING:
PHYSFSX_readVector(f, obj->mtype.spin_rate);
break;
case MT_NONE:
break;
default:
Int3();
}
switch (obj->control_type) {
case CT_AI: {
obj->ctype.ai_info.behavior = static_cast<ai_behavior>(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 CT_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 CT_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<uint16_t>(PHYSFSX_readInt(f))};
#if defined(DXX_BUILD_DESCENT_II)
obj->ctype.laser_info.last_afterburner_time = 0;
#endif
break;
case CT_LIGHT:
obj->ctype.light_info.intensity = PHYSFSX_readFix(f);
break;
case CT_POWERUP:
if (version >= 25)
obj->ctype.powerup_info.count = PHYSFSX_readInt(f);
else
obj->ctype.powerup_info.count = 1;
if (obj->type == OBJ_POWERUP)
{
/* Hostages have control type CT_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 CT_NONE:
case CT_FLYING:
case CT_DEBRIS:
break;
case CT_SLEW: //the player is generally saved as slew
break;
case CT_CNTRLCEN:
break;
case CT_MORPH:
case CT_FLYTHROUGH:
case CT_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(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);
#ifndef 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(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();
}
}
#ifdef EDITOR
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
static void write_object(const vcobjptr_t 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, obj->control_type);
PHYSFSX_writeU8(f, obj->movement_type);
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->last_pos);
PHYSFSX_writeU8(f, obj->contains_type);
PHYSFSX_writeU8(f, obj->contains_id);
PHYSFSX_writeU8(f, obj->contains_count);
switch (obj->movement_type) {
case MT_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 MT_SPINNING:
PHYSFSX_writeVector(f, obj->mtype.spin_rate);
break;
case MT_NONE:
break;
default:
Int3();
}
switch (obj->control_type) {
case CT_AI: {
PHYSFSX_writeU8(f, static_cast<uint8_t>(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 CT_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 CT_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, obj->ctype.laser_info.parent_signature.get());
break;
case CT_LIGHT:
PHYSFSX_writeFix(f, obj->ctype.light_info.intensity);
break;
case CT_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 CT_NONE:
case CT_FLYING:
case CT_DEBRIS:
break;
case CT_SLEW: //the player is generally saved as slew
break;
case CT_CNTRLCEN:
break; //control center object.
case CT_MORPH:
case CT_REPAIRCEN:
case CT_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
static int load_game_data(PHYSFS_file *LoadFile)
{
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);
Num_walls = PHYSFSX_readInt(LoadFile);
PHYSFSX_fseek(LoadFile, 20, SEEK_CUR);
Num_triggers = 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);
Num_robot_centers = PHYSFSX_readInt(LoadFile);
PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
#if defined(DXX_BUILD_DESCENT_I)
#elif defined(DXX_BUILD_DESCENT_II)
unsigned num_delta_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;
if (game_top_fileinfo_version >= 19) { //load pof names
N_save_pof_names = PHYSFSX_readShort(LoadFile);
if (N_save_pof_names != 0x614d && N_save_pof_names != 0x5547) { // "Ma"de w/DMB beta/"GU"ILE
Assert(N_save_pof_names < MAX_POLYGON_MODELS);
PHYSFS_read(LoadFile,Save_pof_names,N_save_pof_names,FILENAME_LEN);
}
}
//===================== 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" );
const auto &&plr = vobjptr(Players[0].objnum);
const auto plr_shields = plr->shields;
const auto player_info = plr->ctype.player_info;
range_for (auto &i, partial_range(Objects, gs_num_objects))
{
const auto &&o = vobjptr(&i);
read_object(o, LoadFile, game_top_fileinfo_version);
i.signature = obj_get_signature();
verify_object(o);
}
plr->shields = plr_shields;
plr->ctype.player_info = player_info;
}
//===================== READ WALL INFO ============================
range_for (auto &nw, partial_range(Walls, Num_walls))
{
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;
nw.hps = w.hps;
nw.trigger = 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 = w.keys;
nw.state = WALL_DOOR_CLOSED;
} else {
v16_wall w;
v16_wall_read(LoadFile, w);
nw.segnum = segment_none;
nw.sidenum = nw.linked_wall = -1;
nw.type = w.type;
nw.flags = w.flags;
nw.hps = w.hps;
nw.trigger = 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 = w.keys;
}
}
//==================== READ TRIGGER INFO ==========================
range_for (auto &i, partial_range(Triggers, Num_triggers))
{
#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 (uint_fast32_t i = 0; i < Num_robot_centers; i++) {
#if defined(DXX_BUILD_DESCENT_I)
matcen_info_read(LoadFile, RobotCenters[i], game_top_fileinfo_version);
#elif defined(DXX_BUILD_DESCENT_II)
if (game_top_fileinfo_version < 27) {
d1_matcen_info_read(LoadFile, RobotCenters[i]);
}
else
matcen_info_read(LoadFile, RobotCenters[i]);
#endif
// Set links in RobotCenters to Station array
range_for (segment &seg, partial_range(Segments, Highest_segment_index + 1))
if (seg.special == SEGMENT_IS_ROBOTMAKER)
if (seg.matcen_num == i)
RobotCenters[i].fuelcen_num = seg.value;
}
#if defined(DXX_BUILD_DESCENT_II)
//================ READ DL_INDICES INFO ===============
if (game_top_fileinfo_version < 29)
{
if (Num_static_lights)
throw std::logic_error("Static lights in old file");
}
else
{
range_for (auto &i, partial_range(Dl_indices, Num_static_lights))
dl_index_read(&i, LoadFile);
}
// Indicate that no light has been subtracted from any vertices.
clear_light_subtracted();
//================ READ DELTA LIGHT INFO ===============
if (game_top_fileinfo_version < 29) {
;
} else
{
range_for (auto &i, partial_range(Delta_lights, num_delta_lights))
delta_light_read(&i, LoadFile);
}
#endif
//========================= UPDATE VARIABLES ======================
reset_objects(gs_num_objects);
range_for (auto &i, Objects)
{
i.next = i.prev = object_none;
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 {
i.segnum = segment_none; //avoid Assert()
obj_link(vobjptridx(&i),objsegnum);
}
}
}
clear_transient_objects(1); //1 means clear proximity bombs
// Make sure non-transparent doors are set correctly.
range_for (auto &i, partial_range(Segments, Num_segments))
range_for (auto &side, i.sides)
{
const auto sidep = &side;
if ((sidep->wall_num != wall_none) && (Walls[sidep->wall_num].clip_num != -1)) {
if (WallAnims[Walls[sidep->wall_num].clip_num].flags & WCF_TMAP1) {
sidep->tmap_num = WallAnims[Walls[sidep->wall_num].clip_num].frames[0];
sidep->tmap_num2 = 0;
}
}
}
reset_walls();
Num_open_doors = 0;
//go through all walls, killing references to invalid triggers
range_for (auto &w, partial_range(Walls, Num_walls))
if (w.trigger >= Num_triggers) {
w.trigger = -1; //kill trigger
}
#ifdef EDITOR
//go through all triggers, killing unused ones
for (uint_fast32_t i = 0;i < Num_triggers;) {
auto a = [i](const wall &w) { return w.trigger == i; };
// Find which wall this trigger is connected to.
auto w = std::find_if(Walls.begin(), Walls.end(), a);
if (w == Walls.end())
{
remove_trigger_num(i);
}
else
i++;
}
#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 (auto &w, partial_range(Walls, Num_walls))
w.controlling_trigger = -1;
#endif
for (uint_fast32_t t = 0; t < Num_triggers; t++) {
int l;
for (l=0; l<Triggers[t].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 = Triggers[t].seg[l];
if (trigger_is_matcen(Triggers[t]))
{
if (Segments[seg_num].special != SEGMENT_IS_ROBOTMAKER)
throw std::runtime_error("matcen triggers non-matcen segment");
}
#if defined(DXX_BUILD_DESCENT_II)
else if (Triggers[t].type != TT_LIGHT_OFF && Triggers[t].type != TT_LIGHT_ON) { //light triggers don't require walls
int side_num = Triggers[t].side[l];
auto wall_num = Segments[seg_num].sides[side_num].wall_num;
if (wall_num == wall_none)
Int3(); // This is illegal. This trigger requires a wall
else
Walls[wall_num].controlling_trigger = t;
}
#endif
}
}
}
//fix old wall structs
if (game_top_fileinfo_version < 17) {
int wallnum;
range_for (const auto segnum, highest_valid(Segments))
{
const auto &&segp = vcsegptr(static_cast<segnum_t>(segnum));
for (int sidenum=0;sidenum<6;sidenum++)
if ((wallnum = segp->sides[sidenum].wall_num) != -1)
{
Walls[wallnum].segnum = segnum;
Walls[wallnum].sidenum = sidenum;
}
}
}
#ifndef NDEBUG
{
for (int sidenum=0; sidenum<6; sidenum++) {
int wallnum = Segments[Highest_segment_index].sides[sidenum].wall_num;
if (wallnum != -1)
if ((Walls[wallnum].segnum != Highest_segment_index) || (Walls[wallnum].sidenum != sidenum))
Int3(); // Error. Bogus walls in this segment.
// Consult Yuan or Mike.
}
}
#endif
//create_local_segment_data();
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
int load_level(const char * filename_passed)
{
#ifdef EDITOR
int use_compiled_level=1;
#endif
char filename[PATH_MAX];
int sig, minedata_offset, gamedata_offset;
int mine_err, game_err;
if (Game_mode & GM_NETWORK)
{
PowerupCaps.clear();
}
#ifndef RELEASE
Level_being_loaded = filename_passed;
#endif
strcpy(filename,filename_passed);
#ifdef EDITOR
//if we have the editor, try the LVL first, no matter what was passed.
//if we don't have an LVL, try what was passed or RL2
//if we don't have the editor, we just use what was passed
change_filename_extension(filename,filename_passed,".lvl");
use_compiled_level = 0;
if (!PHYSFSX_exists(filename,1))
{
const char *p = strrchr(filename_passed, '.');
if (d_stricmp(p, ".lvl"))
strcpy(filename, filename_passed); // set to what was passed
else
change_filename_extension(filename, filename, "." DXX_LEVEL_FILE_EXTENSION);
use_compiled_level = 1;
}
#endif
auto LoadFile = PHYSFSX_openReadBuffered(filename);
if (!LoadFile)
{
snprintf(filename, sizeof(filename), "%.*s%s", static_cast<int>(std::distance(Current_mission->path.cbegin(), Current_mission->filename)), Current_mission->path.c_str(), filename_passed);
LoadFile = PHYSFSX_openReadBuffered(filename);
}
if (!LoadFile) {
#ifdef EDITOR
return 1;
#else
Error("Can't open file <%s>\n",filename);
#endif
}
strcpy( Gamesave_current_filename, filename );
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)
Base_control_center_explosion_time = PHYSFSX_readInt(LoadFile);
else
Base_control_center_explosion_time = DEFAULT_CONTROL_CENTER_EXPLOSION_TIME;
if (Gamesave_current_version >= 4)
Reactor_strength = PHYSFSX_readInt(LoadFile);
else
Reactor_strength = -1; //use old defaults
if (Gamesave_current_version >= 7) {
Num_flickering_lights = PHYSFSX_readInt(LoadFile);
range_for (auto &i, partial_range(Flickering_lights, Num_flickering_lights))
flickering_light_read(&i, LoadFile);
}
else
Num_flickering_lights = 0;
if (Gamesave_current_version < 6) {
Secret_return_segment = 0;
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 {
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);
#ifdef EDITOR
if (!use_compiled_level) {
mine_err = load_mine_data(LoadFile);
#if 0 // get from d1src if needed
// Compress all uv coordinates in mine, improves texmap precision. --MK, 02/19/96
compress_uv_coordinates_all();
#endif
} else
#endif
//NOTE LINK TO ABOVE!!
mine_err = load_mine_data_compiled(LoadFile);
/* !!!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.
*/
if (Current_mission && !d_stricmp("Descent: First Strike",Current_mission_longname) && !d_stricmp("level19.rdl",filename) && PHYSFS_fileLength(LoadFile) == 136706)
Vertices[1905].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)
&& ( Vertices[Segments[104].verts[0]].x == -53990800 && Vertices[Segments[104].verts[0]].y == -59927741 && Vertices[Segments[104].verts[0]].z == 23034584 )
&& ( Segments[104].sides[1].normals[0].x == 56775 && Segments[104].sides[1].normals[0].y == -27796 && Segments[104].sides[1].normals[0].z == -17288 && Segments[104].sides[1].normals[1].x == 50157 && Segments[104].sides[1].normals[1].y == -34561 && Segments[104].sides[1].normals[1].z == -24180 )
&& ( Segments[104].sides[2].normals[0].x == 60867 && Segments[104].sides[2].normals[0].y == -19485 && Segments[104].sides[2].normals[0].z == -14507 && Segments[104].sides[2].normals[1].x == 55485 && Segments[104].sides[2].normals[1].y == -29668 && Segments[104].sides[2].normals[1].z == -18332 )
)
{
Vertices[Segments[104].verts[0]].x = -53859726;
Vertices[Segments[104].verts[0]].y = -59927743;
Vertices[Segments[104].verts[0]].z = 23034586;
Segments[104].sides[1].normals[0].x = 56123;
Segments[104].sides[1].normals[0].y = -27725;
Segments[104].sides[1].normals[0].z = -19401;
Segments[104].sides[1].normals[1].x = 49910;
Segments[104].sides[1].normals[1].y = -33946;
Segments[104].sides[1].normals[1].z = -25525;
Segments[104].sides[2].normals[0].x = 60903;
Segments[104].sides[2].normals[0].y = -18371;
Segments[104].sides[2].normals[0].z = -15753;
Segments[104].sides[2].normals[1].x = 57004;
Segments[104].sides[2].normals[1].y = -26385;
Segments[104].sides[2].normals[1].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(LoadFile);
if (game_err == -1) { //error!!
return 3;
}
//======================== CLOSE FILE =============================
LoadFile.reset();
#if defined(DXX_BUILD_DESCENT_II)
set_ambient_sound_flags();
#endif
#ifdef 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( NULL, 2, "Don't Save", "Save", "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
#ifdef EDITOR
if (EditorWindow)
editor_status_fmt("Loaded NEW mine %s, \"%s\"", filename, static_cast<const char *>(Current_level_name));
#endif
#if !defined(NDEBUG) && !defined(COMPACT_SEGS)
if (check_segment_connections())
nm_messagebox( "ERROR", 1, "Ok",
"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;
}
#ifdef EDITOR
int get_level_name()
{
array<newmenu_item, 2> m{{
nm_item_text("Please enter a name for this mine:"),
nm_item_input(Current_level_name.next()),
}};
return newmenu_do( NULL, "Enter mine name", m, unused_newmenu_subfunction, unused_newmenu_userdata ) >= 0;
}
#endif
#ifdef EDITOR
// --------------------------------------------------------------------------------------
// Create a new mine, set global variables.
int create_new_mine(void)
{
vms_matrix m1 = IDENTITY_MATRIX;
// initialize_mine_arrays();
// gamestate_not_restored = 1;
// Clear refueling center code
fuelcen_reset();
hostage_init_all();
init_all_vertices();
Current_level_num = 0; //0 means not a real level
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(1); //just one object, the player
num_groups = 0;
current_group = -1;
Num_vertices = 0; // Number of vertices in global array.
Highest_vertex_index = 0;
Num_segments = 0; // Number of segments in global array, will get increased in med_create_segment
Highest_segment_index = 0;
Cursegp = segptridx(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;s<MAX_GROUPS+1;s++) {
GroupList[s].clear();
Groupsegp[s] = NULL;
Groupside[s] = 0;
}
Num_robot_centers = 0;
Num_open_doors = 0;
wall_init();
trigger_init();
// Create New_segment, which is the segment we will be adding at each instance.
med_create_new_segment({DEFAULT_X_SIZE, DEFAULT_Y_SIZE, DEFAULT_Z_SIZE}); // New_segment = Segments[0];
// 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));
med_create_segment(vsegptridx(static_cast<segnum_t>(0)),0,0,0,DEFAULT_X_SIZE,DEFAULT_Y_SIZE,DEFAULT_Z_SIZE,m1);
Found_segs.clear();
Selected_segs.clear();
Warning_segs.clear();
//--repair-- create_local_segment_data();
ControlCenterTriggers.num_links = 0;
create_new_mission();
//editor_status("New mine created.");
return 0; // say no error
}
int Errors_in_mine;
// -----------------------------------------------------------------------------
#if defined(DXX_BUILD_DESCENT_II)
static int compute_num_delta_light_records(void)
{
int total = 0;
range_for (auto &i, partial_range(Dl_indices, Num_static_lights))
total += i.count;
return total;
}
#endif
// -----------------------------------------------------------------------------
// Save game
static int save_game_data(PHYSFS_file *SaveFile)
{
#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);
WRITE_HEADER_ENTRY(wall, Num_walls);
WRITE_HEADER_ENTRY(active_door, Num_open_doors);
WRITE_HEADER_ENTRY(trigger, Num_triggers);
WRITE_HEADER_ENTRY(0, 0); // links (removed by Parallax)
WRITE_HEADER_ENTRY(control_center_triggers, 1);
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)
{
WRITE_HEADER_ENTRY(dl_index, Num_static_lights);
WRITE_HEADER_ENTRY(delta_light, num_delta_lights = compute_num_delta_light_records());
}
// Write the mine name
if (game_top_fileinfo_version >= 31)
#endif
PHYSFSX_printf(SaveFile, "%s\n", static_cast<const char *>(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
{
PHYSFS_writeSLE16(SaveFile, N_polygon_models);
range_for (const auto &i, partial_range(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 i, highest_valid(Objects))
{
const auto &&objp = vcobjptr(static_cast<objnum_t>(i));
write_object(objp, game_top_fileinfo_version, SaveFile);
}
//==================== SAVE WALL INFO =============================
walls_offset = PHYSFS_tell(SaveFile);
range_for (auto &w, partial_range(Walls, Num_walls))
wall_write(SaveFile, w, game_top_fileinfo_version);
//==================== SAVE TRIGGER INFO =============================
triggers_offset = PHYSFS_tell(SaveFile);
range_for (auto &t, partial_range(Triggers, Num_triggers))
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_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);
range_for (auto &i, partial_range(Dl_indices, Num_static_lights))
dl_index_write(&i, SaveFile);
delta_light_offset = PHYSFS_tell(SaveFile);
range_for (auto &i, partial_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(const char * filename)
{
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( NULL, 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 = PHYSFSX_openWriteBuffered(temp_filename);
if (!SaveFile)
{
gr_palette_load(gr_palette);
nm_messagebox( NULL, 1, "Ok", "ERROR: Cannot write to '%s'.", temp_filename);
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
{
auto plr = vobjptridx(Players[0].objnum);
if (update_object_seg(plr) == 0) {
if (plr->segnum > Highest_segment_index)
plr->segnum = segment_first;
compute_segment_center(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<const char *>(Current_level_palette));
if (Gamesave_current_version >= 3)
PHYSFS_writeSLE32(SaveFile, Base_control_center_explosion_time);
if (Gamesave_current_version >= 4)
PHYSFS_writeSLE32(SaveFile, Reactor_strength);
if (Gamesave_current_version >= 7)
{
PHYSFS_writeSLE32(SaveFile, Num_flickering_lights);
range_for (auto &i, partial_range(Flickering_lights, Num_flickering_lights))
flickering_light_write(&i, SaveFile);
}
if (Gamesave_current_version >= 6)
{
PHYSFS_writeSLE32(SaveFile, Secret_return_segment);
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);
#if 0 // only save compiled mine data
if ( !compiled_version )
save_mine_data(SaveFile);
else
#endif
save_mine_data_compiled(SaveFile);
gamedata_offset = PHYSFS_tell(SaveFile);
save_game_data(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<const char *>(Current_level_name));
}
return 0;
}
int save_level(const char * filename)
{
int r1;
// Save normal version...
//save_level_sub(filename, 0); // just save compiled one
// Save compiled version...
r1 = save_level_sub(filename);
return r1;
}
#endif //EDITOR