summaryrefslogtreecommitdiff
path: root/src/hexen/sv_save.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hexen/sv_save.c')
-rw-r--r--src/hexen/sv_save.c3371
1 files changed, 3371 insertions, 0 deletions
diff --git a/src/hexen/sv_save.c b/src/hexen/sv_save.c
new file mode 100644
index 00000000..f81df46a
--- /dev/null
+++ b/src/hexen/sv_save.c
@@ -0,0 +1,3371 @@
+// Emacs style mode select -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2008 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+
+// HEADER FILES ------------------------------------------------------------
+
+#include "h2def.h"
+#include "i_system.h"
+#include "m_misc.h"
+#include "i_swap.h"
+#include "p_local.h"
+
+// MACROS ------------------------------------------------------------------
+
+#define MAX_TARGET_PLAYERS 512
+#define MOBJ_NULL -1
+#define MOBJ_XX_PLAYER -2
+#define GET_BYTE (*SavePtr.b++)
+#define GET_WORD SHORT(*SavePtr.w++)
+#define GET_LONG LONG(*SavePtr.l++)
+#define MAX_MAPS 99
+#define BASE_SLOT 6
+#define REBORN_SLOT 7
+#define REBORN_DESCRIPTION "TEMP GAME"
+#define MAX_THINKER_SIZE 256
+
+// TYPES -------------------------------------------------------------------
+
+typedef enum
+{
+ ASEG_GAME_HEADER = 101,
+ ASEG_MAP_HEADER,
+ ASEG_WORLD,
+ ASEG_POLYOBJS,
+ ASEG_MOBJS,
+ ASEG_THINKERS,
+ ASEG_SCRIPTS,
+ ASEG_PLAYERS,
+ ASEG_SOUNDS,
+ ASEG_MISC,
+ ASEG_END
+} gameArchiveSegment_t;
+
+typedef enum
+{
+ TC_NULL,
+ TC_MOVE_CEILING,
+ TC_VERTICAL_DOOR,
+ TC_MOVE_FLOOR,
+ TC_PLAT_RAISE,
+ TC_INTERPRET_ACS,
+ TC_FLOOR_WAGGLE,
+ TC_LIGHT,
+ TC_PHASE,
+ TC_BUILD_PILLAR,
+ TC_ROTATE_POLY,
+ TC_MOVE_POLY,
+ TC_POLY_DOOR
+} thinkClass_t;
+
+typedef struct
+{
+ thinkClass_t tClass;
+ think_t thinkerFunc;
+ void (*writeFunc)();
+ void (*readFunc)();
+ void (*restoreFunc) ();
+ size_t size;
+} thinkInfo_t;
+
+typedef struct
+{
+ thinker_t thinker;
+ sector_t *sector;
+} ssthinker_t;
+
+// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
+
+void P_SpawnPlayer(mapthing_t * mthing);
+
+// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
+
+// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
+
+static void ArchiveWorld(void);
+static void UnarchiveWorld(void);
+static void ArchivePolyobjs(void);
+static void UnarchivePolyobjs(void);
+static void ArchiveMobjs(void);
+static void UnarchiveMobjs(void);
+static void ArchiveThinkers(void);
+static void UnarchiveThinkers(void);
+static void ArchiveScripts(void);
+static void UnarchiveScripts(void);
+static void ArchivePlayers(void);
+static void UnarchivePlayers(void);
+static void ArchiveSounds(void);
+static void UnarchiveSounds(void);
+static void ArchiveMisc(void);
+static void UnarchiveMisc(void);
+static void SetMobjArchiveNums(void);
+static void RemoveAllThinkers(void);
+static int GetMobjNum(mobj_t * mobj);
+static void SetMobjPtr(mobj_t **ptr, unsigned int archiveNum);
+static void RestoreSSThinker(ssthinker_t * sst);
+static void RestorePlatRaise(plat_t * plat);
+static void RestoreMoveCeiling(ceiling_t * ceiling);
+static void AssertSegment(gameArchiveSegment_t segType);
+static void ClearSaveSlot(int slot);
+static void CopySaveSlot(int sourceSlot, int destSlot);
+static void CopyFile(char *sourceName, char *destName);
+static boolean ExistingFile(char *name);
+static void OpenStreamOut(char *fileName);
+static void CloseStreamOut(void);
+static void StreamOutBuffer(void *buffer, int size);
+static void StreamOutByte(byte val);
+static void StreamOutWord(unsigned short val);
+static void StreamOutLong(unsigned int val);
+static void StreamOutPtr(void *ptr);
+
+// EXTERNAL DATA DECLARATIONS ----------------------------------------------
+
+// PUBLIC DATA DEFINITIONS -------------------------------------------------
+
+#define DEFAULT_SAVEPATH "hexndata/"
+
+char *SavePath = DEFAULT_SAVEPATH;
+
+// PRIVATE DATA DEFINITIONS ------------------------------------------------
+
+static int MobjCount;
+static mobj_t **MobjList;
+static mobj_t ***TargetPlayerAddrs;
+static int TargetPlayerCount;
+static byte *SaveBuffer;
+static boolean SavingPlayers;
+static union
+{
+ byte *b;
+ short *w;
+ int *l;
+} SavePtr;
+static FILE *SavingFP;
+
+// CODE --------------------------------------------------------------------
+
+// Autogenerated functions for reading/writing structs:
+
+//
+// acsstore_t
+//
+
+static void StreamIn_acsstore_t(acsstore_t *str)
+{
+ int i;
+
+ // int map;
+ str->map = GET_LONG;
+
+ // int script;
+ str->script = GET_LONG;
+
+ // byte args[4];
+ for (i=0; i<4; ++i)
+ {
+ str->args[i] = GET_BYTE;
+ }
+}
+
+static void StreamOut_acsstore_t(acsstore_t *str)
+{
+ int i;
+
+ // int map;
+ StreamOutLong(str->map);
+
+ // int script;
+ StreamOutLong(str->script);
+
+ // byte args[4];
+ for (i=0; i<4; ++i)
+ {
+ StreamOutByte(str->args[i]);
+ }
+}
+
+
+//
+// ticcmd_t
+// (this is based on the Vanilla definition of the struct)
+//
+
+static void StreamIn_ticcmd_t(ticcmd_t *str)
+{
+ // char forwardmove;
+ str->forwardmove = GET_BYTE;
+
+ // char sidemove;
+ str->sidemove = GET_BYTE;
+
+ // short angleturn;
+ str->angleturn = GET_WORD;
+
+ // short consistancy;
+ str->consistancy = GET_WORD;
+
+ // byte chatchar;
+ str->chatchar = GET_BYTE;
+
+ // byte buttons;
+ str->buttons = GET_BYTE;
+
+ // byte lookfly;
+ str->lookfly = GET_BYTE;
+
+ // byte arti;
+ str->arti = GET_BYTE;
+}
+
+static void StreamOut_ticcmd_t(ticcmd_t *str)
+{
+ // char forwardmove;
+ StreamOutByte(str->forwardmove);
+
+ // char sidemove;
+ StreamOutByte(str->sidemove);
+
+ // short angleturn;
+ StreamOutWord(str->angleturn);
+
+ // short consistancy;
+ StreamOutWord(str->consistancy);
+
+ // byte chatchar;
+ StreamOutByte(str->chatchar);
+
+ // byte buttons;
+ StreamOutByte(str->buttons);
+
+ // byte lookfly;
+ StreamOutByte(str->lookfly);
+
+ // byte arti;
+ StreamOutByte(str->arti);
+}
+
+
+
+//
+// inventory_t
+//
+
+static void StreamIn_inventory_t(inventory_t *str)
+{
+ // int type;
+ str->type = GET_LONG;
+
+ // int count;
+ str->count = GET_LONG;
+}
+
+static void StreamOut_inventory_t(inventory_t *str)
+{
+ // int type;
+ StreamOutLong(str->type);
+
+ // int count;
+ StreamOutLong(str->count);
+}
+
+
+//
+// pspdef_t
+//
+
+static void StreamIn_pspdef_t(pspdef_t *str)
+{
+ int state_num;
+
+ // state_t *state;
+
+ // This is a pointer; it is stored as an index into the states table.
+
+ state_num = GET_LONG;
+
+ if (state_num != 0)
+ {
+ str->state = states + state_num;
+ }
+ else
+ {
+ str->state = NULL;
+ }
+
+ // int tics;
+ str->tics = GET_LONG;
+
+ // fixed_t sx, sy;
+ str->sx = GET_LONG;
+ str->sy = GET_LONG;
+}
+
+static void StreamOut_pspdef_t(pspdef_t *str)
+{
+ // state_t *state;
+ // This is a pointer; store the index in the states table,
+ // rather than the pointer itself.
+ if (str->state != NULL)
+ {
+ StreamOutLong(str->state - states);
+ }
+ else
+ {
+ StreamOutLong(0);
+ }
+
+ // int tics;
+ StreamOutLong(str->tics);
+
+ // fixed_t sx, sy;
+ StreamOutLong(str->sx);
+ StreamOutLong(str->sy);
+}
+
+
+//
+// player_t
+//
+
+static void StreamIn_player_t(player_t *str)
+{
+ int i;
+
+ // mobj_t *mo;
+ // Pointer value is reset on load.
+ GET_LONG;
+ str->mo = NULL;
+
+ // playerstate_t playerstate;
+ str->playerstate = GET_LONG;
+
+ // ticcmd_t cmd;
+ StreamIn_ticcmd_t(&str->cmd);
+
+ // pclass_t class;
+ str->class = GET_LONG;
+
+ // fixed_t viewz;
+ str->viewz = GET_LONG;
+
+ // fixed_t viewheight;
+ str->viewheight = GET_LONG;
+
+ // fixed_t deltaviewheight;
+ str->deltaviewheight = GET_LONG;
+
+ // fixed_t bob;
+ str->bob = GET_LONG;
+
+ // int flyheight;
+ str->flyheight = GET_LONG;
+
+ // int lookdir;
+ str->lookdir = GET_LONG;
+
+ // boolean centering;
+ str->centering = GET_LONG;
+
+ // int health;
+ str->health = GET_LONG;
+
+ // int armorpoints[NUMARMOR];
+ for (i=0; i<NUMARMOR; ++i)
+ {
+ str->armorpoints[i] = GET_LONG;
+ }
+
+ // inventory_t inventory[NUMINVENTORYSLOTS];
+ for (i=0; i<NUMINVENTORYSLOTS; ++i)
+ {
+ StreamIn_inventory_t(&str->inventory[i]);
+ }
+
+ // artitype_t readyArtifact;
+ str->readyArtifact = GET_LONG;
+
+ // int artifactCount;
+ str->artifactCount = GET_LONG;
+
+ // int inventorySlotNum;
+ str->inventorySlotNum = GET_LONG;
+
+ // int powers[NUMPOWERS];
+ for (i=0; i<NUMPOWERS; ++i)
+ {
+ str->powers[i] = GET_LONG;
+ }
+
+ // int keys;
+ str->keys = GET_LONG;
+
+ // int pieces;
+ str->pieces = GET_LONG;
+
+ // signed int frags[MAXPLAYERS];
+ for (i=0; i<MAXPLAYERS; ++i)
+ {
+ str->frags[i] = GET_LONG;
+ }
+
+ // weapontype_t readyweapon;
+ str->readyweapon = GET_LONG;
+
+ // weapontype_t pendingweapon;
+ str->pendingweapon = GET_LONG;
+
+ // boolean weaponowned[NUMWEAPONS];
+ for (i=0; i<NUMWEAPONS; ++i)
+ {
+ str->weaponowned[i] = GET_LONG;
+ }
+
+ // int mana[NUMMANA];
+ for (i=0; i<NUMMANA; ++i)
+ {
+ str->mana[i] = GET_LONG;
+ }
+
+ // int attackdown, usedown;
+ str->attackdown = GET_LONG;
+ str->usedown = GET_LONG;
+
+ // int cheats;
+ str->cheats = GET_LONG;
+
+ // int refire;
+ str->refire = GET_LONG;
+
+ // int killcount, itemcount, secretcount;
+ str->killcount = GET_LONG;
+ str->itemcount = GET_LONG;
+ str->secretcount = GET_LONG;
+
+ // char message[80];
+ for (i=0; i<80; ++i)
+ {
+ str->message[i] = GET_BYTE;
+ }
+
+ // int messageTics;
+ str->messageTics = GET_LONG;
+
+ // short ultimateMessage;
+ str->ultimateMessage = GET_WORD;
+
+ // short yellowMessage;
+ str->yellowMessage = GET_WORD;
+
+ // int damagecount, bonuscount;
+ str->damagecount = GET_LONG;
+ str->bonuscount = GET_LONG;
+
+ // int poisoncount;
+ str->poisoncount = GET_LONG;
+
+ // mobj_t *poisoner;
+ // Pointer value is reset.
+ GET_LONG;
+ str->poisoner = NULL;
+
+ // mobj_t *attacker;
+ // Pointer value is reset.
+ GET_LONG;
+ str->attacker = NULL;
+
+ // int extralight;
+ str->extralight = GET_LONG;
+
+ // int fixedcolormap;
+ str->fixedcolormap = GET_LONG;
+
+ // int colormap;
+ str->colormap = GET_LONG;
+
+ // pspdef_t psprites[NUMPSPRITES];
+ for (i=0; i<NUMPSPRITES; ++i)
+ {
+ StreamIn_pspdef_t(&str->psprites[i]);
+ }
+
+ // int morphTics;
+ str->morphTics = GET_LONG;
+
+ // unsigned int jumpTics;
+ str->jumpTics = GET_LONG;
+
+ // unsigned int worldTimer;
+ str->worldTimer = GET_LONG;
+}
+
+static void StreamOut_player_t(player_t *str)
+{
+ int i;
+
+ // mobj_t *mo;
+ StreamOutPtr(str->mo);
+
+ // playerstate_t playerstate;
+ StreamOutLong(str->playerstate);
+
+ // ticcmd_t cmd;
+ StreamOut_ticcmd_t(&str->cmd);
+
+ // pclass_t class;
+ StreamOutLong(str->class);
+
+ // fixed_t viewz;
+ StreamOutLong(str->viewz);
+
+ // fixed_t viewheight;
+ StreamOutLong(str->viewheight);
+
+ // fixed_t deltaviewheight;
+ StreamOutLong(str->deltaviewheight);
+
+ // fixed_t bob;
+ StreamOutLong(str->bob);
+
+ // int flyheight;
+ StreamOutLong(str->flyheight);
+
+ // int lookdir;
+ StreamOutLong(str->lookdir);
+
+ // boolean centering;
+ StreamOutLong(str->centering);
+
+ // int health;
+ StreamOutLong(str->health);
+
+ // int armorpoints[NUMARMOR];
+ for (i=0; i<NUMARMOR; ++i)
+ {
+ StreamOutLong(str->armorpoints[i]);
+ }
+
+ // inventory_t inventory[NUMINVENTORYSLOTS];
+ for (i=0; i<NUMINVENTORYSLOTS; ++i)
+ {
+ StreamOut_inventory_t(&str->inventory[i]);
+ }
+
+ // artitype_t readyArtifact;
+ StreamOutLong(str->readyArtifact);
+
+ // int artifactCount;
+ StreamOutLong(str->artifactCount);
+
+ // int inventorySlotNum;
+ StreamOutLong(str->inventorySlotNum);
+
+ // int powers[NUMPOWERS];
+ for (i=0; i<NUMPOWERS; ++i)
+ {
+ StreamOutLong(str->powers[i]);
+ }
+
+ // int keys;
+ StreamOutLong(str->keys);
+
+ // int pieces;
+ StreamOutLong(str->pieces);
+
+ // signed int frags[MAXPLAYERS];
+ for (i=0; i<MAXPLAYERS; ++i)
+ {
+ StreamOutLong(str->frags[i]);
+ }
+
+ // weapontype_t readyweapon;
+ StreamOutLong(str->readyweapon);
+
+ // weapontype_t pendingweapon;
+ StreamOutLong(str->pendingweapon);
+
+ // boolean weaponowned[NUMWEAPONS];
+ for (i=0; i<NUMWEAPONS; ++i)
+ {
+ StreamOutLong(str->weaponowned[i]);
+ }
+
+ // int mana[NUMMANA];
+ for (i=0; i<NUMMANA; ++i)
+ {
+ StreamOutLong(str->mana[i]);
+ }
+
+ // int attackdown, usedown;
+ StreamOutLong(str->attackdown);
+ StreamOutLong(str->usedown);
+
+ // int cheats;
+ StreamOutLong(str->cheats);
+
+ // int refire;
+ StreamOutLong(str->refire);
+
+ // int killcount, itemcount, secretcount;
+ StreamOutLong(str->killcount);
+ StreamOutLong(str->itemcount);
+ StreamOutLong(str->secretcount);
+
+ // char message[80];
+ for (i=0; i<80; ++i)
+ {
+ StreamOutByte(str->message[i]);
+ }
+
+ // int messageTics;
+ StreamOutLong(str->messageTics);
+
+ // short ultimateMessage;
+ StreamOutWord(str->ultimateMessage);
+
+ // short yellowMessage;
+ StreamOutWord(str->yellowMessage);
+
+ // int damagecount, bonuscount;
+ StreamOutLong(str->damagecount);
+ StreamOutLong(str->bonuscount);
+
+ // int poisoncount;
+ StreamOutLong(str->poisoncount);
+
+ // mobj_t *poisoner;
+ StreamOutPtr(str->poisoner);
+
+ // mobj_t *attacker;
+ StreamOutPtr(str->attacker);
+
+ // int extralight;
+ StreamOutLong(str->extralight);
+
+ // int fixedcolormap;
+ StreamOutLong(str->fixedcolormap);
+
+ // int colormap;
+ StreamOutLong(str->colormap);
+
+ // pspdef_t psprites[NUMPSPRITES];
+ for (i=0; i<NUMPSPRITES; ++i)
+ {
+ StreamOut_pspdef_t(&str->psprites[i]);
+ }
+
+ // int morphTics;
+ StreamOutLong(str->morphTics);
+
+ // unsigned int jumpTics;
+ StreamOutLong(str->jumpTics);
+
+ // unsigned int worldTimer;
+ StreamOutLong(str->worldTimer);
+}
+
+
+//
+// thinker_t
+//
+
+static void StreamIn_thinker_t(thinker_t *str)
+{
+ // struct thinker_s *prev, *next;
+ // Pointers are discarded:
+ GET_LONG;
+ str->prev = NULL;
+ GET_LONG;
+ str->next = NULL;
+
+ // think_t function;
+ // Function pointer is discarded:
+ GET_LONG;
+ str->function = NULL;
+}
+
+static void StreamOut_thinker_t(thinker_t *str)
+{
+ // struct thinker_s *prev, *next;
+ StreamOutPtr(str->prev);
+ StreamOutPtr(str->next);
+
+ // think_t function;
+ StreamOutPtr(&str->function);
+}
+
+
+//
+// mobj_t
+//
+
+static void StreamInMobjSpecials(mobj_t *mobj)
+{
+ unsigned int special1, special2;
+
+ special1 = GET_LONG;
+ special2 = GET_LONG;
+
+ mobj->special1.i = special1;
+ mobj->special2.i = special2;
+
+ switch (mobj->type)
+ {
+ // Just special1
+ case MT_BISH_FX:
+ case MT_HOLY_FX:
+ case MT_DRAGON:
+ case MT_THRUSTFLOOR_UP:
+ case MT_THRUSTFLOOR_DOWN:
+ case MT_MINOTAUR:
+ case MT_SORCFX1:
+ SetMobjPtr(&mobj->special1.m, special1);
+ break;
+
+ // Just special2
+ case MT_LIGHTNING_FLOOR:
+ case MT_LIGHTNING_ZAP:
+ SetMobjPtr(&mobj->special2.m, special2);
+ break;
+
+ // Both special1 and special2
+ case MT_HOLY_TAIL:
+ case MT_LIGHTNING_CEILING:
+ SetMobjPtr(&mobj->special1.m, special1);
+ SetMobjPtr(&mobj->special2.m, special2);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void StreamIn_mobj_t(mobj_t *str)
+{
+ unsigned int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // fixed_t x, y, z;
+ str->x = GET_LONG;
+ str->y = GET_LONG;
+ str->z = GET_LONG;
+
+ // struct mobj_s *snext, *sprev;
+ // Pointer values are discarded:
+ GET_LONG;
+ str->snext = NULL;
+ GET_LONG;
+ str->sprev = NULL;
+
+ // angle_t angle;
+ str->angle = GET_LONG;
+
+ // spritenum_t sprite;
+ str->sprite = GET_LONG;
+
+ // int frame;
+ str->frame = GET_LONG;
+
+ // struct mobj_s *bnext, *bprev;
+ // Values are read but discarded; this will be restored when the thing's
+ // position is set.
+ GET_LONG;
+ str->bnext = NULL;
+ GET_LONG;
+ str->bprev = NULL;
+
+ // struct subsector_s *subsector;
+ // Read but discard: pointer will be restored when thing position is set.
+ GET_LONG;
+ str->subsector = NULL;
+
+ // fixed_t floorz, ceilingz;
+ str->floorz = GET_LONG;
+ str->ceilingz = GET_LONG;
+
+ // fixed_t floorpic;
+ str->floorpic = GET_LONG;
+
+ // fixed_t radius, height;
+ str->radius = GET_LONG;
+ str->height = GET_LONG;
+
+ // fixed_t momx, momy, momz;
+ str->momx = GET_LONG;
+ str->momy = GET_LONG;
+ str->momz = GET_LONG;
+
+ // int validcount;
+ str->validcount = GET_LONG;
+
+ // mobjtype_t type;
+ str->type = GET_LONG;
+
+ // mobjinfo_t *info;
+ // Pointer value is read but discarded.
+ GET_LONG;
+ str->info = NULL;
+
+ // int tics;
+ str->tics = GET_LONG;
+
+ // state_t *state;
+ // Restore as index into states table.
+ i = GET_LONG;
+ str->state = &states[i];
+
+ // int damage;
+ str->damage = GET_LONG;
+
+ // int flags;
+ str->flags = GET_LONG;
+
+ // int flags2;
+ str->flags2 = GET_LONG;
+
+ // specialval_t special1;
+ // specialval_t special2;
+ // Read in special values: there are special cases to deal with with
+ // mobj pointers.
+ StreamInMobjSpecials(str);
+
+ // int health;
+ str->health = GET_LONG;
+
+ // int movedir;
+ str->movedir = GET_LONG;
+
+ // int movecount;
+ str->movecount = GET_LONG;
+
+ // struct mobj_s *target;
+ i = GET_LONG;
+ SetMobjPtr(&str->target, i);
+
+ // int reactiontime;
+ str->reactiontime = GET_LONG;
+
+ // int threshold;
+ str->threshold = GET_LONG;
+
+ // struct player_s *player;
+ // Saved as player number.
+ i = GET_LONG;
+ if (i == 0)
+ {
+ str->player = NULL;
+ }
+ else
+ {
+ str->player = &players[i - 1];
+ str->player->mo = str;
+ }
+
+ // int lastlook;
+ str->lastlook = GET_LONG;
+
+ // fixed_t floorclip;
+ str->floorclip = GET_LONG;
+
+ // int archiveNum;
+ str->archiveNum = GET_LONG;
+
+ // short tid;
+ str->tid = GET_WORD;
+
+ // byte special;
+ str->special = GET_BYTE;
+
+ // byte args[5];
+ for (i=0; i<5; ++i)
+ {
+ str->args[i] = GET_BYTE;
+ }
+}
+
+static void StreamOutMobjSpecials(mobj_t *mobj)
+{
+ unsigned int special1, special2;
+ boolean corpse;
+
+ corpse = (mobj->flags & MF_CORPSE) != 0;
+ special1 = mobj->special1.i;
+ special2 = mobj->special2.i;
+
+ switch (mobj->type)
+ {
+ // Just special1
+ case MT_BISH_FX:
+ case MT_HOLY_FX:
+ case MT_DRAGON:
+ case MT_THRUSTFLOOR_UP:
+ case MT_THRUSTFLOOR_DOWN:
+ case MT_MINOTAUR:
+ case MT_SORCFX1:
+ case MT_MSTAFF_FX2:
+ if (corpse)
+ {
+ special1 = MOBJ_NULL;
+ }
+ else
+ {
+ special1 = GetMobjNum(mobj->special1.m);
+ }
+ break;
+
+ // Just special2
+ case MT_LIGHTNING_FLOOR:
+ case MT_LIGHTNING_ZAP:
+ if (corpse)
+ {
+ special2 = MOBJ_NULL;
+ }
+ else
+ {
+ special2 = GetMobjNum(mobj->special2.m);
+ }
+ break;
+
+ // Both special1 and special2
+ case MT_HOLY_TAIL:
+ case MT_LIGHTNING_CEILING:
+ if (corpse)
+ {
+ special1 = MOBJ_NULL;
+ special2 = MOBJ_NULL;
+ }
+ else
+ {
+ special1 = GetMobjNum(mobj->special1.m);
+ special2 = GetMobjNum(mobj->special2.m);
+ }
+ break;
+
+ // Miscellaneous
+ case MT_KORAX:
+ special1 = 0; // Searching index
+ break;
+
+ default:
+ break;
+ }
+
+ // Write special values to savegame file.
+
+ StreamOutLong(special1);
+ StreamOutLong(special2);
+}
+
+static void StreamOut_mobj_t(mobj_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // fixed_t x, y, z;
+ StreamOutLong(str->x);
+ StreamOutLong(str->y);
+ StreamOutLong(str->z);
+
+ // struct mobj_s *snext, *sprev;
+ StreamOutPtr(str->snext);
+ StreamOutPtr(str->sprev);
+
+ // angle_t angle;
+ StreamOutLong(str->angle);
+
+ // spritenum_t sprite;
+ StreamOutLong(str->sprite);
+
+ // int frame;
+ StreamOutLong(str->frame);
+
+ // struct mobj_s *bnext, *bprev;
+ StreamOutPtr(str->bnext);
+ StreamOutPtr(str->bprev);
+
+ // struct subsector_s *subsector;
+ StreamOutPtr(str->subsector);
+
+ // fixed_t floorz, ceilingz;
+ StreamOutLong(str->floorz);
+ StreamOutLong(str->ceilingz);
+
+ // fixed_t floorpic;
+ StreamOutLong(str->floorpic);
+
+ // fixed_t radius, height;
+ StreamOutLong(str->radius);
+ StreamOutLong(str->height);
+
+ // fixed_t momx, momy, momz;
+ StreamOutLong(str->momx);
+ StreamOutLong(str->momy);
+ StreamOutLong(str->momz);
+
+ // int validcount;
+ StreamOutLong(str->validcount);
+
+ // mobjtype_t type;
+ StreamOutLong(str->type);
+
+ // mobjinfo_t *info;
+ StreamOutPtr(str->info);
+
+ // int tics;
+ StreamOutLong(str->tics);
+
+ // state_t *state;
+ // Save as index into the states table.
+ StreamOutLong(str->state - states);
+
+ // int damage;
+ StreamOutLong(str->damage);
+
+ // int flags;
+ StreamOutLong(str->flags);
+
+ // int flags2;
+ StreamOutLong(str->flags2);
+
+ // specialval_t special1;
+ // specialval_t special2;
+ // There are lots of special cases for the special values:
+ StreamOutMobjSpecials(str);
+
+ // int health;
+ StreamOutLong(str->health);
+
+ // int movedir;
+ StreamOutLong(str->movedir);
+
+ // int movecount;
+ StreamOutLong(str->movecount);
+
+ // struct mobj_s *target;
+ if ((str->flags & MF_CORPSE) != 0)
+ {
+ StreamOutLong(MOBJ_NULL);
+ }
+ else
+ {
+ StreamOutLong(GetMobjNum(str->target));
+ }
+
+ // int reactiontime;
+ StreamOutLong(str->reactiontime);
+
+ // int threshold;
+ StreamOutLong(str->threshold);
+
+ // struct player_s *player;
+ // Stored as index into players[] array, if there is a player pointer.
+ if (str->player != NULL)
+ {
+ StreamOutLong(str->player - players + 1);
+ }
+ else
+ {
+ StreamOutLong(0);
+ }
+
+ // int lastlook;
+ StreamOutLong(str->lastlook);
+
+ // fixed_t floorclip;
+ StreamOutLong(str->floorclip);
+
+ // int archiveNum;
+ StreamOutLong(str->archiveNum);
+
+ // short tid;
+ StreamOutWord(str->tid);
+
+ // byte special;
+ StreamOutByte(str->special);
+
+ // byte args[5];
+ for (i=0; i<5; ++i)
+ {
+ StreamOutByte(str->args[i]);
+ }
+}
+
+
+//
+// floormove_t
+//
+
+static void StreamIn_floormove_t(floormove_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ i = GET_LONG;
+ str->sector = sectors + i;
+
+ // floor_e type;
+ str->type = GET_LONG;
+
+ // int crush;
+ str->crush = GET_LONG;
+
+ // int direction;
+ str->direction = GET_LONG;
+
+ // int newspecial;
+ str->newspecial = GET_LONG;
+
+ // short texture;
+ str->texture = GET_WORD;
+
+ // fixed_t floordestheight;
+ str->floordestheight = GET_LONG;
+
+ // fixed_t speed;
+ str->speed = GET_LONG;
+
+ // int delayCount;
+ str->delayCount = GET_LONG;
+
+ // int delayTotal;
+ str->delayTotal = GET_LONG;
+
+ // fixed_t stairsDelayHeight;
+ str->stairsDelayHeight = GET_LONG;
+
+ // fixed_t stairsDelayHeightDelta;
+ str->stairsDelayHeightDelta = GET_LONG;
+
+ // fixed_t resetHeight;
+ str->resetHeight = GET_LONG;
+
+ // short resetDelay;
+ str->resetDelay = GET_WORD;
+
+ // short resetDelayCount;
+ str->resetDelayCount = GET_WORD;
+
+ // byte textureChange;
+ str->textureChange = GET_BYTE;
+}
+
+static void StreamOut_floormove_t(floormove_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ StreamOutLong(str->sector - sectors);
+
+ // floor_e type;
+ StreamOutLong(str->type);
+
+ // int crush;
+ StreamOutLong(str->crush);
+
+ // int direction;
+ StreamOutLong(str->direction);
+
+ // int newspecial;
+ StreamOutLong(str->newspecial);
+
+ // short texture;
+ StreamOutWord(str->texture);
+
+ // fixed_t floordestheight;
+ StreamOutLong(str->floordestheight);
+
+ // fixed_t speed;
+ StreamOutLong(str->speed);
+
+ // int delayCount;
+ StreamOutLong(str->delayCount);
+
+ // int delayTotal;
+ StreamOutLong(str->delayTotal);
+
+ // fixed_t stairsDelayHeight;
+ StreamOutLong(str->stairsDelayHeight);
+
+ // fixed_t stairsDelayHeightDelta;
+ StreamOutLong(str->stairsDelayHeightDelta);
+
+ // fixed_t resetHeight;
+ StreamOutLong(str->resetHeight);
+
+ // short resetDelay;
+ StreamOutWord(str->resetDelay);
+
+ // short resetDelayCount;
+ StreamOutWord(str->resetDelayCount);
+
+ // byte textureChange;
+ StreamOutByte(str->textureChange);
+}
+
+
+//
+// plat_t
+//
+
+static void StreamIn_plat_t(plat_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ i = GET_LONG;
+ str->sector = sectors + i;
+
+ // fixed_t speed;
+ str->speed = GET_LONG;
+
+ // fixed_t low;
+ str->low = GET_LONG;
+
+ // fixed_t high;
+ str->high = GET_LONG;
+
+ // int wait;
+ str->wait = GET_LONG;
+
+ // int count;
+ str->count = GET_LONG;
+
+ // plat_e status;
+ str->status = GET_LONG;
+
+ // plat_e oldstatus;
+ str->oldstatus = GET_LONG;
+
+ // int crush;
+ str->crush = GET_LONG;
+
+ // int tag;
+ str->tag = GET_LONG;
+
+ // plattype_e type;
+ str->type = GET_LONG;
+}
+
+static void StreamOut_plat_t(plat_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ StreamOutLong(str->sector - sectors);
+
+ // fixed_t speed;
+ StreamOutLong(str->speed);
+
+ // fixed_t low;
+ StreamOutLong(str->low);
+
+ // fixed_t high;
+ StreamOutLong(str->high);
+
+ // int wait;
+ StreamOutLong(str->wait);
+
+ // int count;
+ StreamOutLong(str->count);
+
+ // plat_e status;
+ StreamOutLong(str->status);
+
+ // plat_e oldstatus;
+ StreamOutLong(str->oldstatus);
+
+ // int crush;
+ StreamOutLong(str->crush);
+
+ // int tag;
+ StreamOutLong(str->tag);
+
+ // plattype_e type;
+ StreamOutLong(str->type);
+}
+
+
+//
+// ceiling_t
+//
+
+static void StreamIn_ceiling_t(ceiling_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ i = GET_LONG;
+ str->sector = sectors + i;
+
+ // ceiling_e type;
+ str->type = GET_LONG;
+
+ // fixed_t bottomheight, topheight;
+ str->bottomheight = GET_LONG;
+ str->topheight = GET_LONG;
+
+ // fixed_t speed;
+ str->speed = GET_LONG;
+
+ // int crush;
+ str->crush = GET_LONG;
+
+ // int direction;
+ str->direction = GET_LONG;
+
+ // int tag;
+ str->tag = GET_LONG;
+
+ // int olddirection;
+ str->olddirection = GET_LONG;
+}
+
+static void StreamOut_ceiling_t(ceiling_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ StreamOutLong(str->sector - sectors);
+
+ // ceiling_e type;
+ StreamOutLong(str->type);
+
+ // fixed_t bottomheight, topheight;
+ StreamOutLong(str->bottomheight);
+ StreamOutLong(str->topheight);
+
+ // fixed_t speed;
+ StreamOutLong(str->speed);
+
+ // int crush;
+ StreamOutLong(str->crush);
+
+ // int direction;
+ StreamOutLong(str->direction);
+
+ // int tag;
+ StreamOutLong(str->tag);
+
+ // int olddirection;
+ StreamOutLong(str->olddirection);
+}
+
+
+//
+// light_t
+//
+
+static void StreamIn_light_t(light_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ i = GET_LONG;
+ str->sector = sectors + i;
+
+ // lighttype_t type;
+ str->type = GET_LONG;
+
+ // int value1;
+ str->value1 = GET_LONG;
+
+ // int value2;
+ str->value2 = GET_LONG;
+
+ // int tics1;
+ str->tics1 = GET_LONG;
+
+ // int tics2;
+ str->tics2 = GET_LONG;
+
+ // int count;
+ str->count = GET_LONG;
+}
+
+static void StreamOut_light_t(light_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ StreamOutLong(str->sector - sectors);
+
+ // lighttype_t type;
+ StreamOutLong(str->type);
+
+ // int value1;
+ StreamOutLong(str->value1);
+
+ // int value2;
+ StreamOutLong(str->value2);
+
+ // int tics1;
+ StreamOutLong(str->tics1);
+
+ // int tics2;
+ StreamOutLong(str->tics2);
+
+ // int count;
+ StreamOutLong(str->count);
+}
+
+
+//
+// vldoor_t
+//
+
+static void StreamIn_vldoor_t(vldoor_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ i = GET_LONG;
+ str->sector = &sectors[i];
+
+ // vldoor_e type;
+ str->type = GET_LONG;
+
+ // fixed_t topheight;
+ str->topheight = GET_LONG;
+
+ // fixed_t speed;
+ str->speed = GET_LONG;
+
+ // int direction;
+ str->direction = GET_LONG;
+
+ // int topwait;
+ str->topwait = GET_LONG;
+
+ // int topcountdown;
+ str->topcountdown = GET_LONG;
+}
+
+static void StreamOut_vldoor_t(vldoor_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ StreamOutLong(str->sector - sectors);
+
+ // vldoor_e type;
+ StreamOutLong(str->type);
+
+ // fixed_t topheight;
+ StreamOutLong(str->topheight);
+
+ // fixed_t speed;
+ StreamOutLong(str->speed);
+
+ // int direction;
+ StreamOutLong(str->direction);
+
+ // int topwait;
+ StreamOutLong(str->topwait);
+
+ // int topcountdown;
+ StreamOutLong(str->topcountdown);
+}
+
+
+//
+// phase_t
+//
+
+static void StreamIn_phase_t(phase_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ i = GET_LONG;
+ str->sector = &sectors[i];
+
+ // int index;
+ str->index = GET_LONG;
+
+ // int base;
+ str->base = GET_LONG;
+}
+
+static void StreamOut_phase_t(phase_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ StreamOutLong(str->sector - sectors);
+
+ // int index;
+ StreamOutLong(str->index);
+
+ // int base;
+ StreamOutLong(str->base);
+}
+
+
+//
+// acs_t
+//
+
+static void StreamIn_acs_t(acs_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // mobj_t *activator;
+ i = GET_LONG;
+ SetMobjPtr(&str->activator, i);
+
+ // line_t *line;
+ i = GET_LONG;
+ if (i != -1)
+ {
+ str->line = &lines[i];
+ }
+ else
+ {
+ str->line = NULL;
+ }
+
+ // int side;
+ str->side = GET_LONG;
+
+ // int number;
+ str->number = GET_LONG;
+
+ // int infoIndex;
+ str->infoIndex = GET_LONG;
+
+ // int delayCount;
+ str->delayCount = GET_LONG;
+
+ // int stack[ACS_STACK_DEPTH];
+ for (i=0; i<ACS_STACK_DEPTH; ++i)
+ {
+ str->stack[i] = GET_LONG;
+ }
+
+ // int stackPtr;
+ str->stackPtr = GET_LONG;
+
+ // int vars[MAX_ACS_SCRIPT_VARS];
+ for (i=0; i<MAX_ACS_SCRIPT_VARS; ++i)
+ {
+ str->vars[i] = GET_LONG;
+ }
+
+ // int *ip;
+ i = GET_LONG;
+ str->ip = (int *) (ActionCodeBase + i);
+}
+
+static void StreamOut_acs_t(acs_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // mobj_t *activator;
+ StreamOutLong(GetMobjNum(str->activator));
+
+ // line_t *line;
+ if (str->line != NULL)
+ {
+ StreamOutLong(str->line - lines);
+ }
+ else
+ {
+ StreamOutLong(-1);
+ }
+
+ // int side;
+ StreamOutLong(str->side);
+
+ // int number;
+ StreamOutLong(str->number);
+
+ // int infoIndex;
+ StreamOutLong(str->infoIndex);
+
+ // int delayCount;
+ StreamOutLong(str->delayCount);
+
+ // int stack[ACS_STACK_DEPTH];
+ for (i=0; i<ACS_STACK_DEPTH; ++i)
+ {
+ StreamOutLong(str->stack[i]);
+ }
+
+ // int stackPtr;
+ StreamOutLong(str->stackPtr);
+
+ // int vars[MAX_ACS_SCRIPT_VARS];
+ for (i=0; i<MAX_ACS_SCRIPT_VARS; ++i)
+ {
+ StreamOutLong(str->vars[i]);
+ }
+
+ // int *ip;
+ StreamOutLong((byte *) str->ip - ActionCodeBase);
+}
+
+
+//
+// polyevent_t
+//
+
+static void StreamIn_polyevent_t(polyevent_t *str)
+{
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // int polyobj;
+ str->polyobj = GET_LONG;
+
+ // int speed;
+ str->speed = GET_LONG;
+
+ // unsigned int dist;
+ str->dist = GET_LONG;
+
+ // int angle;
+ str->angle = GET_LONG;
+
+ // fixed_t xSpeed;
+ str->xSpeed = GET_LONG;
+
+ // fixed_t ySpeed;
+ str->ySpeed = GET_LONG;
+}
+
+static void StreamOut_polyevent_t(polyevent_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // int polyobj;
+ StreamOutLong(str->polyobj);
+
+ // int speed;
+ StreamOutLong(str->speed);
+
+ // unsigned int dist;
+ StreamOutLong(str->dist);
+
+ // int angle;
+ StreamOutLong(str->angle);
+
+ // fixed_t xSpeed;
+ StreamOutLong(str->xSpeed);
+
+ // fixed_t ySpeed;
+ StreamOutLong(str->ySpeed);
+}
+
+
+//
+// pillar_t
+//
+
+static void StreamIn_pillar_t(pillar_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ i = GET_LONG;
+ str->sector = &sectors[i];
+
+ // int ceilingSpeed;
+ str->ceilingSpeed = GET_LONG;
+
+ // int floorSpeed;
+ str->floorSpeed = GET_LONG;
+
+ // int floordest;
+ str->floordest = GET_LONG;
+
+ // int ceilingdest;
+ str->ceilingdest = GET_LONG;
+
+ // int direction;
+ str->direction = GET_LONG;
+
+ // int crush;
+ str->crush = GET_LONG;
+}
+
+static void StreamOut_pillar_t(pillar_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ StreamOutLong(str->sector - sectors);
+
+ // int ceilingSpeed;
+ StreamOutLong(str->ceilingSpeed);
+
+ // int floorSpeed;
+ StreamOutLong(str->floorSpeed);
+
+ // int floordest;
+ StreamOutLong(str->floordest);
+
+ // int ceilingdest;
+ StreamOutLong(str->ceilingdest);
+
+ // int direction;
+ StreamOutLong(str->direction);
+
+ // int crush;
+ StreamOutLong(str->crush);
+}
+
+
+//
+// polydoor_t
+//
+
+static void StreamIn_polydoor_t(polydoor_t *str)
+{
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // int polyobj;
+ str->polyobj = GET_LONG;
+
+ // int speed;
+ str->speed = GET_LONG;
+
+ // int dist;
+ str->dist = GET_LONG;
+
+ // int totalDist;
+ str->totalDist = GET_LONG;
+
+ // int direction;
+ str->direction = GET_LONG;
+
+ // fixed_t xSpeed, ySpeed;
+ str->xSpeed = GET_LONG;
+ str->ySpeed = GET_LONG;
+
+ // int tics;
+ str->tics = GET_LONG;
+
+ // int waitTics;
+ str->waitTics = GET_LONG;
+
+ // podoortype_t type;
+ str->type = GET_LONG;
+
+ // boolean close;
+ str->close = GET_LONG;
+}
+
+static void StreamOut_polydoor_t(polydoor_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // int polyobj;
+ StreamOutLong(str->polyobj);
+
+ // int speed;
+ StreamOutLong(str->speed);
+
+ // int dist;
+ StreamOutLong(str->dist);
+
+ // int totalDist;
+ StreamOutLong(str->totalDist);
+
+ // int direction;
+ StreamOutLong(str->direction);
+
+ // fixed_t xSpeed, ySpeed;
+ StreamOutLong(str->xSpeed);
+ StreamOutLong(str->ySpeed);
+
+ // int tics;
+ StreamOutLong(str->tics);
+
+ // int waitTics;
+ StreamOutLong(str->waitTics);
+
+ // podoortype_t type;
+ StreamOutLong(str->type);
+
+ // boolean close;
+ StreamOutLong(str->close);
+}
+
+
+//
+// floorWaggle_t
+//
+
+static void StreamIn_floorWaggle_t(floorWaggle_t *str)
+{
+ int i;
+
+ // thinker_t thinker;
+ StreamIn_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ i = GET_LONG;
+ str->sector = &sectors[i];
+
+ // fixed_t originalHeight;
+ str->originalHeight = GET_LONG;
+
+ // fixed_t accumulator;
+ str->accumulator = GET_LONG;
+
+ // fixed_t accDelta;
+ str->accDelta = GET_LONG;
+
+ // fixed_t targetScale;
+ str->targetScale = GET_LONG;
+
+ // fixed_t scale;
+ str->scale = GET_LONG;
+
+ // fixed_t scaleDelta;
+ str->scaleDelta = GET_LONG;
+
+ // int ticker;
+ str->ticker = GET_LONG;
+
+ // int state;
+ str->state = GET_LONG;
+}
+
+static void StreamOut_floorWaggle_t(floorWaggle_t *str)
+{
+ // thinker_t thinker;
+ StreamOut_thinker_t(&str->thinker);
+
+ // sector_t *sector;
+ StreamOutLong(str->sector - sectors);
+
+ // fixed_t originalHeight;
+ StreamOutLong(str->originalHeight);
+
+ // fixed_t accumulator;
+ StreamOutLong(str->accumulator);
+
+ // fixed_t accDelta;
+ StreamOutLong(str->accDelta);
+
+ // fixed_t targetScale;
+ StreamOutLong(str->targetScale);
+
+ // fixed_t scale;
+ StreamOutLong(str->scale);
+
+ // fixed_t scaleDelta;
+ StreamOutLong(str->scaleDelta);
+
+ // int ticker;
+ StreamOutLong(str->ticker);
+
+ // int state;
+ StreamOutLong(str->state);
+}
+
+
+//==========================================================================
+//
+// SV_SaveGame
+//
+//==========================================================================
+
+void SV_SaveGame(int slot, char *description)
+{
+ char fileName[100];
+ char versionText[HXS_VERSION_TEXT_LENGTH];
+ unsigned int i;
+
+ // Open the output file
+ sprintf(fileName, "%shex6.hxs", SavePath);
+ OpenStreamOut(fileName);
+
+ // Write game save description
+ StreamOutBuffer(description, HXS_DESCRIPTION_LENGTH);
+
+ // Write version info
+ memset(versionText, 0, HXS_VERSION_TEXT_LENGTH);
+ strcpy(versionText, HXS_VERSION_TEXT);
+ StreamOutBuffer(versionText, HXS_VERSION_TEXT_LENGTH);
+
+ // Place a header marker
+ StreamOutLong(ASEG_GAME_HEADER);
+
+ // Write current map and difficulty
+ StreamOutByte(gamemap);
+ StreamOutByte(gameskill);
+
+ // Write global script info
+ for (i = 0; i < MAX_ACS_WORLD_VARS; ++i)
+ {
+ StreamOutLong(WorldVars[i]);
+ }
+
+ for (i = 0; i < MAX_ACS_STORE + 1; ++i)
+ {
+ StreamOut_acsstore_t(&ACSStore[i]);
+ }
+
+ ArchivePlayers();
+
+ // Place a termination marker
+ StreamOutLong(ASEG_END);
+
+ // Close the output file
+ CloseStreamOut();
+
+ // Save out the current map
+ SV_SaveMap(true); // true = save player info
+
+ // Clear all save files at destination slot
+ ClearSaveSlot(slot);
+
+ // Copy base slot to destination slot
+ CopySaveSlot(BASE_SLOT, slot);
+}
+
+//==========================================================================
+//
+// SV_SaveMap
+//
+//==========================================================================
+
+void SV_SaveMap(boolean savePlayers)
+{
+ char fileName[100];
+
+ SavingPlayers = savePlayers;
+
+ // Open the output file
+ sprintf(fileName, "%shex6%02d.hxs", SavePath, gamemap);
+ OpenStreamOut(fileName);
+
+ // Place a header marker
+ StreamOutLong(ASEG_MAP_HEADER);
+
+ // Write the level timer
+ StreamOutLong(leveltime);
+
+ // Set the mobj archive numbers
+ SetMobjArchiveNums();
+
+ ArchiveWorld();
+ ArchivePolyobjs();
+ ArchiveMobjs();
+ ArchiveThinkers();
+ ArchiveScripts();
+ ArchiveSounds();
+ ArchiveMisc();
+
+ // Place a termination marker
+ StreamOutLong(ASEG_END);
+
+ // Close the output file
+ CloseStreamOut();
+}
+
+//==========================================================================
+//
+// SV_LoadGame
+//
+//==========================================================================
+
+void SV_LoadGame(int slot)
+{
+ int i;
+ char fileName[100];
+ player_t playerBackup[MAXPLAYERS];
+ mobj_t *mobj;
+
+ // Copy all needed save files to the base slot
+ if (slot != BASE_SLOT)
+ {
+ ClearSaveSlot(BASE_SLOT);
+ CopySaveSlot(slot, BASE_SLOT);
+ }
+
+ // Create the name
+ sprintf(fileName, "%shex6.hxs", SavePath);
+
+ // Load the file
+ M_ReadFile(fileName, &SaveBuffer);
+
+ // Set the save pointer and skip the description field
+ SavePtr.b = SaveBuffer + HXS_DESCRIPTION_LENGTH;
+
+ // Check the version text
+ if (strcmp((char *) SavePtr.b, HXS_VERSION_TEXT))
+ { // Bad version
+ return;
+ }
+ SavePtr.b += HXS_VERSION_TEXT_LENGTH;
+
+ AssertSegment(ASEG_GAME_HEADER);
+
+ gameepisode = 1;
+ gamemap = GET_BYTE;
+ gameskill = GET_BYTE;
+
+ // Read global script info
+
+ for (i = 0; i < MAX_ACS_WORLD_VARS; ++i)
+ {
+ WorldVars[i] = GET_LONG;
+ }
+
+ for (i = 0; i < MAX_ACS_STORE + 1; ++i)
+ {
+ StreamIn_acsstore_t(&ACSStore[i]);
+ }
+
+ // Read the player structures
+ UnarchivePlayers();
+
+ AssertSegment(ASEG_END);
+
+ Z_Free(SaveBuffer);
+
+ // Save player structs
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ playerBackup[i] = players[i];
+ }
+
+ // Load the current map
+ SV_LoadMap();
+
+ // Don't need the player mobj relocation info for load game
+ Z_Free(TargetPlayerAddrs);
+
+ // Restore player structs
+ inv_ptr = 0;
+ curpos = 0;
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ mobj = players[i].mo;
+ players[i] = playerBackup[i];
+ players[i].mo = mobj;
+ if (i == consoleplayer)
+ {
+ players[i].readyArtifact = players[i].inventory[inv_ptr].type;
+ }
+ }
+}
+
+//==========================================================================
+//
+// SV_UpdateRebornSlot
+//
+// Copies the base slot to the reborn slot.
+//
+//==========================================================================
+
+void SV_UpdateRebornSlot(void)
+{
+ ClearSaveSlot(REBORN_SLOT);
+ CopySaveSlot(BASE_SLOT, REBORN_SLOT);
+}
+
+//==========================================================================
+//
+// SV_ClearRebornSlot
+//
+//==========================================================================
+
+void SV_ClearRebornSlot(void)
+{
+ ClearSaveSlot(REBORN_SLOT);
+}
+
+//==========================================================================
+//
+// SV_MapTeleport
+//
+//==========================================================================
+
+void SV_MapTeleport(int map, int position)
+{
+ int i;
+ int j;
+ char fileName[100];
+ player_t playerBackup[MAXPLAYERS];
+ mobj_t *targetPlayerMobj;
+ mobj_t *mobj;
+ int inventoryPtr;
+ int currentInvPos;
+ boolean rClass;
+ boolean playerWasReborn;
+ boolean oldWeaponowned[NUMWEAPONS];
+ int oldKeys = 0;
+ int oldPieces = 0;
+ int bestWeapon;
+
+ if (!deathmatch)
+ {
+ if (P_GetMapCluster(gamemap) == P_GetMapCluster(map))
+ { // Same cluster - save map without saving player mobjs
+ SV_SaveMap(false);
+ }
+ else
+ { // Entering new cluster - clear base slot
+ ClearSaveSlot(BASE_SLOT);
+ }
+ }
+
+ // Store player structs for later
+ rClass = randomclass;
+ randomclass = false;
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ playerBackup[i] = players[i];
+ }
+
+ // Save some globals that get trashed during the load
+ inventoryPtr = inv_ptr;
+ currentInvPos = curpos;
+
+ // Only SV_LoadMap() uses TargetPlayerAddrs, so it's NULLed here
+ // for the following check (player mobj redirection)
+ TargetPlayerAddrs = NULL;
+
+ gamemap = map;
+ sprintf(fileName, "%shex6%02d.hxs", SavePath, gamemap);
+ if (!deathmatch && ExistingFile(fileName))
+ { // Unarchive map
+ SV_LoadMap();
+ }
+ else
+ { // New map
+ G_InitNew(gameskill, gameepisode, gamemap);
+
+ // Destroy all freshly spawned players
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ if (playeringame[i])
+ {
+ P_RemoveMobj(players[i].mo);
+ }
+ }
+ }
+
+ // Restore player structs
+ targetPlayerMobj = NULL;
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ if (!playeringame[i])
+ {
+ continue;
+ }
+ players[i] = playerBackup[i];
+ P_ClearMessage(&players[i]);
+ players[i].attacker = NULL;
+ players[i].poisoner = NULL;
+
+ if (netgame)
+ {
+ if (players[i].playerstate == PST_DEAD)
+ { // In a network game, force all players to be alive
+ players[i].playerstate = PST_REBORN;
+ }
+ if (!deathmatch)
+ { // Cooperative net-play, retain keys and weapons
+ oldKeys = players[i].keys;
+ oldPieces = players[i].pieces;
+ for (j = 0; j < NUMWEAPONS; j++)
+ {
+ oldWeaponowned[j] = players[i].weaponowned[j];
+ }
+ }
+ }
+ playerWasReborn = (players[i].playerstate == PST_REBORN);
+ if (deathmatch)
+ {
+ memset(players[i].frags, 0, sizeof(players[i].frags));
+ mobj = P_SpawnMobj(playerstarts[0][i].x << 16,
+ playerstarts[0][i].y << 16, 0,
+ MT_PLAYER_FIGHTER);
+ players[i].mo = mobj;
+ G_DeathMatchSpawnPlayer(i);
+ P_RemoveMobj(mobj);
+ }
+ else
+ {
+ P_SpawnPlayer(&playerstarts[position][i]);
+ }
+
+ if (playerWasReborn && netgame && !deathmatch)
+ { // Restore keys and weapons when reborn in co-op
+ players[i].keys = oldKeys;
+ players[i].pieces = oldPieces;
+ for (bestWeapon = 0, j = 0; j < NUMWEAPONS; j++)
+ {
+ if (oldWeaponowned[j])
+ {
+ bestWeapon = j;
+ players[i].weaponowned[j] = true;
+ }
+ }
+ players[i].mana[MANA_1] = 25;
+ players[i].mana[MANA_2] = 25;
+ if (bestWeapon)
+ { // Bring up the best weapon
+ players[i].pendingweapon = bestWeapon;
+ }
+ }
+
+ if (targetPlayerMobj == NULL)
+ { // The poor sap
+ targetPlayerMobj = players[i].mo;
+ }
+ }
+ randomclass = rClass;
+
+ // Redirect anything targeting a player mobj
+ if (TargetPlayerAddrs)
+ {
+ for (i = 0; i < TargetPlayerCount; i++)
+ {
+ *TargetPlayerAddrs[i] = targetPlayerMobj;
+ }
+ Z_Free(TargetPlayerAddrs);
+ }
+
+ // Destroy all things touching players
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ if (playeringame[i])
+ {
+ P_TeleportMove(players[i].mo, players[i].mo->x, players[i].mo->y);
+ }
+ }
+
+ // Restore trashed globals
+ inv_ptr = inventoryPtr;
+ curpos = currentInvPos;
+
+ // Launch waiting scripts
+ if (!deathmatch)
+ {
+ P_CheckACSStore();
+ }
+
+ // For single play, save immediately into the reborn slot
+ if (!netgame)
+ {
+ SV_SaveGame(REBORN_SLOT, REBORN_DESCRIPTION);
+ }
+}
+
+//==========================================================================
+//
+// SV_GetRebornSlot
+//
+//==========================================================================
+
+int SV_GetRebornSlot(void)
+{
+ return (REBORN_SLOT);
+}
+
+//==========================================================================
+//
+// SV_RebornSlotAvailable
+//
+// Returns true if the reborn slot is available.
+//
+//==========================================================================
+
+boolean SV_RebornSlotAvailable(void)
+{
+ char fileName[100];
+
+ sprintf(fileName, "%shex%d.hxs", SavePath, REBORN_SLOT);
+ return ExistingFile(fileName);
+}
+
+//==========================================================================
+//
+// SV_LoadMap
+//
+//==========================================================================
+
+void SV_LoadMap(void)
+{
+ char fileName[100];
+
+ // Load a base level
+ G_InitNew(gameskill, gameepisode, gamemap);
+
+ // Remove all thinkers
+ RemoveAllThinkers();
+
+ // Create the name
+ sprintf(fileName, "%shex6%02d.hxs", SavePath, gamemap);
+
+ // Load the file
+ M_ReadFile(fileName, &SaveBuffer);
+ SavePtr.b = SaveBuffer;
+
+ AssertSegment(ASEG_MAP_HEADER);
+
+ // Read the level timer
+ leveltime = GET_LONG;
+
+ UnarchiveWorld();
+ UnarchivePolyobjs();
+ UnarchiveMobjs();
+ UnarchiveThinkers();
+ UnarchiveScripts();
+ UnarchiveSounds();
+ UnarchiveMisc();
+
+ AssertSegment(ASEG_END);
+
+ // Free mobj list and save buffer
+ Z_Free(MobjList);
+ Z_Free(SaveBuffer);
+}
+
+//==========================================================================
+//
+// SV_InitBaseSlot
+//
+//==========================================================================
+
+void SV_InitBaseSlot(void)
+{
+ ClearSaveSlot(BASE_SLOT);
+}
+
+//==========================================================================
+//
+// ArchivePlayers
+//
+//==========================================================================
+
+static void ArchivePlayers(void)
+{
+ int i;
+
+ StreamOutLong(ASEG_PLAYERS);
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ StreamOutByte(playeringame[i]);
+ }
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ if (!playeringame[i])
+ {
+ continue;
+ }
+ StreamOutByte(PlayerClass[i]);
+ StreamOut_player_t(&players[i]);
+ }
+}
+
+//==========================================================================
+//
+// UnarchivePlayers
+//
+//==========================================================================
+
+static void UnarchivePlayers(void)
+{
+ int i;
+
+ AssertSegment(ASEG_PLAYERS);
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ playeringame[i] = GET_BYTE;
+ }
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ if (!playeringame[i])
+ {
+ continue;
+ }
+ PlayerClass[i] = GET_BYTE;
+ StreamIn_player_t(&players[i]);
+ P_ClearMessage(&players[i]);
+ }
+}
+
+//==========================================================================
+//
+// ArchiveWorld
+//
+//==========================================================================
+
+static void ArchiveWorld(void)
+{
+ int i;
+ int j;
+ sector_t *sec;
+ line_t *li;
+ side_t *si;
+
+ StreamOutLong(ASEG_WORLD);
+ for (i = 0, sec = sectors; i < numsectors; i++, sec++)
+ {
+ StreamOutWord(sec->floorheight >> FRACBITS);
+ StreamOutWord(sec->ceilingheight >> FRACBITS);
+ StreamOutWord(sec->floorpic);
+ StreamOutWord(sec->ceilingpic);
+ StreamOutWord(sec->lightlevel);
+ StreamOutWord(sec->special);
+ StreamOutWord(sec->tag);
+ StreamOutWord(sec->seqType);
+ }
+ for (i = 0, li = lines; i < numlines; i++, li++)
+ {
+ StreamOutWord(li->flags);
+ StreamOutByte(li->special);
+ StreamOutByte(li->arg1);
+ StreamOutByte(li->arg2);
+ StreamOutByte(li->arg3);
+ StreamOutByte(li->arg4);
+ StreamOutByte(li->arg5);
+ for (j = 0; j < 2; j++)
+ {
+ if (li->sidenum[j] == -1)
+ {
+ continue;
+ }
+ si = &sides[li->sidenum[j]];
+ StreamOutWord(si->textureoffset >> FRACBITS);
+ StreamOutWord(si->rowoffset >> FRACBITS);
+ StreamOutWord(si->toptexture);
+ StreamOutWord(si->bottomtexture);
+ StreamOutWord(si->midtexture);
+ }
+ }
+}
+
+//==========================================================================
+//
+// UnarchiveWorld
+//
+//==========================================================================
+
+static void UnarchiveWorld(void)
+{
+ int i;
+ int j;
+ sector_t *sec;
+ line_t *li;
+ side_t *si;
+
+ AssertSegment(ASEG_WORLD);
+ for (i = 0, sec = sectors; i < numsectors; i++, sec++)
+ {
+ sec->floorheight = GET_WORD << FRACBITS;
+ sec->ceilingheight = GET_WORD << FRACBITS;
+ sec->floorpic = GET_WORD;
+ sec->ceilingpic = GET_WORD;
+ sec->lightlevel = GET_WORD;
+ sec->special = GET_WORD;
+ sec->tag = GET_WORD;
+ sec->seqType = GET_WORD;
+ sec->specialdata = 0;
+ sec->soundtarget = 0;
+ }
+ for (i = 0, li = lines; i < numlines; i++, li++)
+ {
+ li->flags = GET_WORD;
+ li->special = GET_BYTE;
+ li->arg1 = GET_BYTE;
+ li->arg2 = GET_BYTE;
+ li->arg3 = GET_BYTE;
+ li->arg4 = GET_BYTE;
+ li->arg5 = GET_BYTE;
+ for (j = 0; j < 2; j++)
+ {
+ if (li->sidenum[j] == -1)
+ {
+ continue;
+ }
+ si = &sides[li->sidenum[j]];
+ si->textureoffset = GET_WORD << FRACBITS;
+ si->rowoffset = GET_WORD << FRACBITS;
+ si->toptexture = GET_WORD;
+ si->bottomtexture = GET_WORD;
+ si->midtexture = GET_WORD;
+ }
+ }
+}
+
+//==========================================================================
+//
+// SetMobjArchiveNums
+//
+// Sets the archive numbers in all mobj structs. Also sets the MobjCount
+// global. Ignores player mobjs if SavingPlayers is false.
+//
+//==========================================================================
+
+static void SetMobjArchiveNums(void)
+{
+ mobj_t *mobj;
+ thinker_t *thinker;
+
+ MobjCount = 0;
+ for (thinker = thinkercap.next; thinker != &thinkercap;
+ thinker = thinker->next)
+ {
+ if (thinker->function == P_MobjThinker)
+ {
+ mobj = (mobj_t *) thinker;
+ if (mobj->player && !SavingPlayers)
+ { // Skipping player mobjs
+ continue;
+ }
+ mobj->archiveNum = MobjCount++;
+ }
+ }
+}
+
+//==========================================================================
+//
+// ArchiveMobjs
+//
+//==========================================================================
+
+static void ArchiveMobjs(void)
+{
+ int count;
+ thinker_t *thinker;
+
+ StreamOutLong(ASEG_MOBJS);
+ StreamOutLong(MobjCount);
+ count = 0;
+ for (thinker = thinkercap.next; thinker != &thinkercap;
+ thinker = thinker->next)
+ {
+ if (thinker->function != P_MobjThinker)
+ { // Not a mobj thinker
+ continue;
+ }
+ if (((mobj_t *) thinker)->player && !SavingPlayers)
+ { // Skipping player mobjs
+ continue;
+ }
+ count++;
+ StreamOut_mobj_t((mobj_t *) thinker);
+ }
+ if (count != MobjCount)
+ {
+ I_Error("ArchiveMobjs: bad mobj count");
+ }
+}
+
+//==========================================================================
+//
+// UnarchiveMobjs
+//
+//==========================================================================
+
+static void UnarchiveMobjs(void)
+{
+ int i;
+ mobj_t *mobj;
+
+ AssertSegment(ASEG_MOBJS);
+ TargetPlayerAddrs = Z_Malloc(MAX_TARGET_PLAYERS * sizeof(mobj_t **),
+ PU_STATIC, NULL);
+ TargetPlayerCount = 0;
+ MobjCount = GET_LONG;
+ MobjList = Z_Malloc(MobjCount * sizeof(mobj_t *), PU_STATIC, NULL);
+ for (i = 0; i < MobjCount; i++)
+ {
+ MobjList[i] = Z_Malloc(sizeof(mobj_t), PU_LEVEL, NULL);
+ }
+ for (i = 0; i < MobjCount; i++)
+ {
+ mobj = MobjList[i];
+ StreamIn_mobj_t(mobj);
+
+ // Restore broken pointers.
+ mobj->info = &mobjinfo[mobj->type];
+ P_SetThingPosition(mobj);
+ mobj->floorz = mobj->subsector->sector->floorheight;
+ mobj->ceilingz = mobj->subsector->sector->ceilingheight;
+
+ mobj->thinker.function = P_MobjThinker;
+ P_AddThinker(&mobj->thinker);
+ }
+ P_CreateTIDList();
+ P_InitCreatureCorpseQueue(true); // true = scan for corpses
+}
+
+//==========================================================================
+//
+// GetMobjNum
+//
+//==========================================================================
+
+static int GetMobjNum(mobj_t * mobj)
+{
+ if (mobj == NULL)
+ {
+ return MOBJ_NULL;
+ }
+ if (mobj->player && !SavingPlayers)
+ {
+ return MOBJ_XX_PLAYER;
+ }
+ return mobj->archiveNum;
+}
+
+//==========================================================================
+//
+// SetMobjPtr
+//
+//==========================================================================
+
+static void SetMobjPtr(mobj_t **ptr, unsigned int archiveNum)
+{
+ if (archiveNum == MOBJ_NULL)
+ {
+ *ptr = NULL;
+ }
+ else if (archiveNum == MOBJ_XX_PLAYER)
+ {
+ if (TargetPlayerCount == MAX_TARGET_PLAYERS)
+ {
+ I_Error("RestoreMobj: exceeded MAX_TARGET_PLAYERS");
+ }
+ TargetPlayerAddrs[TargetPlayerCount++] = ptr;
+ *ptr = NULL;
+ }
+ else
+ {
+ *ptr = MobjList[archiveNum];
+ }
+}
+
+//==========================================================================
+//
+// Thinker types list.
+//
+// This is used by ArchiveThinkers and UnarchiveThinkers, below.
+//
+// Original comment:
+// "This list has been prioritized using frequency estimates"
+//
+//==========================================================================
+
+static thinkInfo_t ThinkerInfo[] = {
+ {
+ TC_MOVE_FLOOR,
+ T_MoveFloor,
+ StreamOut_floormove_t,
+ StreamIn_floormove_t,
+ RestoreSSThinker,
+ sizeof(floormove_t)
+ },
+ {
+ TC_PLAT_RAISE,
+ T_PlatRaise,
+ StreamOut_plat_t,
+ StreamIn_plat_t,
+ RestorePlatRaise,
+ sizeof(plat_t)
+ },
+ {
+ TC_MOVE_CEILING,
+ T_MoveCeiling,
+ StreamOut_ceiling_t,
+ StreamIn_ceiling_t,
+ RestoreMoveCeiling,
+ sizeof(ceiling_t)
+ },
+ {
+ TC_LIGHT,
+ T_Light,
+ StreamOut_light_t,
+ StreamIn_light_t,
+ NULL,
+ sizeof(light_t)
+ },
+ {
+ TC_VERTICAL_DOOR,
+ T_VerticalDoor,
+ StreamOut_vldoor_t,
+ StreamIn_vldoor_t,
+ RestoreSSThinker,
+ sizeof(vldoor_t)
+ },
+ {
+ TC_PHASE,
+ T_Phase,
+ StreamOut_phase_t,
+ StreamIn_phase_t,
+ NULL,
+ sizeof(phase_t)
+ },
+ {
+ TC_INTERPRET_ACS,
+ T_InterpretACS,
+ StreamOut_acs_t,
+ StreamIn_acs_t,
+ NULL,
+ sizeof(acs_t)
+ },
+ {
+ TC_ROTATE_POLY,
+ T_RotatePoly,
+ StreamOut_polyevent_t,
+ StreamIn_polyevent_t,
+ NULL,
+ sizeof(polyevent_t)
+ },
+ {
+ TC_BUILD_PILLAR,
+ T_BuildPillar,
+ StreamOut_pillar_t,
+ StreamIn_pillar_t,
+ RestoreSSThinker,
+ sizeof(pillar_t)
+ },
+ {
+ TC_MOVE_POLY,
+ T_MovePoly,
+ StreamOut_polyevent_t,
+ StreamIn_polyevent_t,
+ NULL,
+ sizeof(polyevent_t)
+ },
+ {
+ TC_POLY_DOOR,
+ T_PolyDoor,
+ StreamOut_polydoor_t,
+ StreamIn_polydoor_t,
+ NULL,
+ sizeof(polydoor_t)
+ },
+ {
+ TC_FLOOR_WAGGLE,
+ T_FloorWaggle,
+ StreamOut_floorWaggle_t,
+ StreamIn_floorWaggle_t,
+ RestoreSSThinker,
+ sizeof(floorWaggle_t)
+ },
+ { TC_NULL, NULL, NULL, NULL, NULL, 0},
+};
+
+//==========================================================================
+//
+// ArchiveThinkers
+//
+//==========================================================================
+
+static void ArchiveThinkers(void)
+{
+ thinker_t *thinker;
+ thinkInfo_t *info;
+
+ StreamOutLong(ASEG_THINKERS);
+ for (thinker = thinkercap.next; thinker != &thinkercap;
+ thinker = thinker->next)
+ {
+ for (info = ThinkerInfo; info->tClass != TC_NULL; info++)
+ {
+ if (thinker->function == info->thinkerFunc)
+ {
+ StreamOutByte(info->tClass);
+ info->writeFunc(thinker);
+ break;
+ }
+ }
+ }
+ // Add a termination marker
+ StreamOutByte(TC_NULL);
+}
+
+//==========================================================================
+//
+// UnarchiveThinkers
+//
+//==========================================================================
+
+static void UnarchiveThinkers(void)
+{
+ int tClass;
+ thinker_t *thinker;
+ thinkInfo_t *info;
+
+ AssertSegment(ASEG_THINKERS);
+ while ((tClass = GET_BYTE) != TC_NULL)
+ {
+ for (info = ThinkerInfo; info->tClass != TC_NULL; info++)
+ {
+ if (tClass == info->tClass)
+ {
+ thinker = Z_Malloc(info->size, PU_LEVEL, NULL);
+ info->readFunc(thinker);
+ thinker->function = info->thinkerFunc;
+ if (info->restoreFunc)
+ {
+ info->restoreFunc(thinker);
+ }
+ P_AddThinker(thinker);
+ break;
+ }
+ }
+ if (info->tClass == TC_NULL)
+ {
+ I_Error("UnarchiveThinkers: Unknown tClass %d in "
+ "savegame", tClass);
+ }
+ }
+}
+
+//==========================================================================
+//
+// RestoreSSThinker
+//
+//==========================================================================
+
+static void RestoreSSThinker(ssthinker_t *sst)
+{
+ sst->sector->specialdata = sst->thinker.function;
+}
+
+//==========================================================================
+//
+// RestorePlatRaise
+//
+//==========================================================================
+
+static void RestorePlatRaise(plat_t *plat)
+{
+ plat->sector->specialdata = T_PlatRaise;
+ P_AddActivePlat(plat);
+}
+
+//==========================================================================
+//
+// RestoreMoveCeiling
+//
+//==========================================================================
+
+static void RestoreMoveCeiling(ceiling_t *ceiling)
+{
+ ceiling->sector->specialdata = T_MoveCeiling;
+ P_AddActiveCeiling(ceiling);
+}
+
+//==========================================================================
+//
+// ArchiveScripts
+//
+//==========================================================================
+
+static void ArchiveScripts(void)
+{
+ int i;
+
+ StreamOutLong(ASEG_SCRIPTS);
+ for (i = 0; i < ACScriptCount; i++)
+ {
+ StreamOutWord(ACSInfo[i].state);
+ StreamOutWord(ACSInfo[i].waitValue);
+ }
+
+ for (i = 0; i< MAX_ACS_MAP_VARS; ++i)
+ {
+ StreamOutLong(MapVars[i]);
+ }
+}
+
+//==========================================================================
+//
+// UnarchiveScripts
+//
+//==========================================================================
+
+static void UnarchiveScripts(void)
+{
+ int i;
+
+ AssertSegment(ASEG_SCRIPTS);
+ for (i = 0; i < ACScriptCount; i++)
+ {
+ ACSInfo[i].state = GET_WORD;
+ ACSInfo[i].waitValue = GET_WORD;
+ }
+
+ for (i = 0; i < MAX_ACS_MAP_VARS; ++i)
+ {
+ MapVars[i] = GET_LONG;
+ }
+}
+
+//==========================================================================
+//
+// ArchiveMisc
+//
+//==========================================================================
+
+static void ArchiveMisc(void)
+{
+ int ix;
+
+ StreamOutLong(ASEG_MISC);
+ for (ix = 0; ix < MAXPLAYERS; ix++)
+ {
+ StreamOutLong(localQuakeHappening[ix]);
+ }
+}
+
+//==========================================================================
+//
+// UnarchiveMisc
+//
+//==========================================================================
+
+static void UnarchiveMisc(void)
+{
+ int ix;
+
+ AssertSegment(ASEG_MISC);
+ for (ix = 0; ix < MAXPLAYERS; ix++)
+ {
+ localQuakeHappening[ix] = GET_LONG;
+ }
+}
+
+//==========================================================================
+//
+// RemoveAllThinkers
+//
+//==========================================================================
+
+static void RemoveAllThinkers(void)
+{
+ thinker_t *thinker;
+ thinker_t *nextThinker;
+
+ thinker = thinkercap.next;
+ while (thinker != &thinkercap)
+ {
+ nextThinker = thinker->next;
+ if (thinker->function == P_MobjThinker)
+ {
+ P_RemoveMobj((mobj_t *) thinker);
+ }
+ else
+ {
+ Z_Free(thinker);
+ }
+ thinker = nextThinker;
+ }
+ P_InitThinkers();
+}
+
+//==========================================================================
+//
+// ArchiveSounds
+//
+//==========================================================================
+
+static void ArchiveSounds(void)
+{
+ seqnode_t *node;
+ sector_t *sec;
+ int difference;
+ int i;
+
+ StreamOutLong(ASEG_SOUNDS);
+
+ // Save the sound sequences
+ StreamOutLong(ActiveSequences);
+ for (node = SequenceListHead; node; node = node->next)
+ {
+ StreamOutLong(node->sequence);
+ StreamOutLong(node->delayTics);
+ StreamOutLong(node->volume);
+ StreamOutLong(SN_GetSequenceOffset(node->sequence,
+ node->sequencePtr));
+ StreamOutLong(node->currentSoundID);
+ for (i = 0; i < po_NumPolyobjs; i++)
+ {
+ if (node->mobj == (mobj_t *) & polyobjs[i].startSpot)
+ {
+ break;
+ }
+ }
+ if (i == po_NumPolyobjs)
+ { // Sound is attached to a sector, not a polyobj
+ sec = R_PointInSubsector(node->mobj->x, node->mobj->y)->sector;
+ difference = (int) ((byte *) sec
+ - (byte *) & sectors[0]) / sizeof(sector_t);
+ StreamOutLong(0); // 0 -- sector sound origin
+ }
+ else
+ {
+ StreamOutLong(1); // 1 -- polyobj sound origin
+ difference = i;
+ }
+ StreamOutLong(difference);
+ }
+}
+
+//==========================================================================
+//
+// UnarchiveSounds
+//
+//==========================================================================
+
+static void UnarchiveSounds(void)
+{
+ int i;
+ int numSequences;
+ int sequence;
+ int delayTics;
+ int volume;
+ int seqOffset;
+ int soundID;
+ int polySnd;
+ int secNum;
+ mobj_t *sndMobj;
+
+ AssertSegment(ASEG_SOUNDS);
+
+ // Reload and restart all sound sequences
+ numSequences = GET_LONG;
+ i = 0;
+ while (i < numSequences)
+ {
+ sequence = GET_LONG;
+ delayTics = GET_LONG;
+ volume = GET_LONG;
+ seqOffset = GET_LONG;
+
+ soundID = GET_LONG;
+ polySnd = GET_LONG;
+ secNum = GET_LONG;
+ if (!polySnd)
+ {
+ sndMobj = (mobj_t *) & sectors[secNum].soundorg;
+ }
+ else
+ {
+ sndMobj = (mobj_t *) & polyobjs[secNum].startSpot;
+ }
+ SN_StartSequence(sndMobj, sequence);
+ SN_ChangeNodeData(i, seqOffset, delayTics, volume, soundID);
+ i++;
+ }
+}
+
+//==========================================================================
+//
+// ArchivePolyobjs
+//
+//==========================================================================
+
+static void ArchivePolyobjs(void)
+{
+ int i;
+
+ StreamOutLong(ASEG_POLYOBJS);
+ StreamOutLong(po_NumPolyobjs);
+ for (i = 0; i < po_NumPolyobjs; i++)
+ {
+ StreamOutLong(polyobjs[i].tag);
+ StreamOutLong(polyobjs[i].angle);
+ StreamOutLong(polyobjs[i].startSpot.x);
+ StreamOutLong(polyobjs[i].startSpot.y);
+ }
+}
+
+//==========================================================================
+//
+// UnarchivePolyobjs
+//
+//==========================================================================
+
+static void UnarchivePolyobjs(void)
+{
+ int i;
+ fixed_t deltaX;
+ fixed_t deltaY;
+
+ AssertSegment(ASEG_POLYOBJS);
+ if (GET_LONG != po_NumPolyobjs)
+ {
+ I_Error("UnarchivePolyobjs: Bad polyobj count");
+ }
+ for (i = 0; i < po_NumPolyobjs; i++)
+ {
+ if (GET_LONG != polyobjs[i].tag)
+ {
+ I_Error("UnarchivePolyobjs: Invalid polyobj tag");
+ }
+ PO_RotatePolyobj(polyobjs[i].tag, (angle_t) GET_LONG);
+ deltaX = GET_LONG - polyobjs[i].startSpot.x;
+ deltaY = GET_LONG - polyobjs[i].startSpot.y;
+ PO_MovePolyobj(polyobjs[i].tag, deltaX, deltaY);
+ }
+}
+
+//==========================================================================
+//
+// AssertSegment
+//
+//==========================================================================
+
+static void AssertSegment(gameArchiveSegment_t segType)
+{
+ if (GET_LONG != segType)
+ {
+ I_Error("Corrupt save game: Segment [%d] failed alignment check",
+ segType);
+ }
+}
+
+//==========================================================================
+//
+// ClearSaveSlot
+//
+// Deletes all save game files associated with a slot number.
+//
+//==========================================================================
+
+static void ClearSaveSlot(int slot)
+{
+ int i;
+ char fileName[100];
+
+ for (i = 0; i < MAX_MAPS; i++)
+ {
+ sprintf(fileName, "%shex%d%02d.hxs", SavePath, slot, i);
+ remove(fileName);
+ }
+ sprintf(fileName, "%shex%d.hxs", SavePath, slot);
+ remove(fileName);
+}
+
+//==========================================================================
+//
+// CopySaveSlot
+//
+// Copies all the save game files from one slot to another.
+//
+//==========================================================================
+
+static void CopySaveSlot(int sourceSlot, int destSlot)
+{
+ int i;
+ char sourceName[100];
+ char destName[100];
+
+ for (i = 0; i < MAX_MAPS; i++)
+ {
+ sprintf(sourceName, "%shex%d%02d.hxs", SavePath, sourceSlot, i);
+ if (ExistingFile(sourceName))
+ {
+ sprintf(destName, "%shex%d%02d.hxs", SavePath, destSlot, i);
+ CopyFile(sourceName, destName);
+ }
+ }
+ sprintf(sourceName, "%shex%d.hxs", SavePath, sourceSlot);
+ if (ExistingFile(sourceName))
+ {
+ sprintf(destName, "%shex%d.hxs", SavePath, destSlot);
+ CopyFile(sourceName, destName);
+ }
+}
+
+//==========================================================================
+//
+// CopyFile
+//
+//==========================================================================
+
+static void CopyFile(char *sourceName, char *destName)
+{
+ int length;
+ byte *buffer;
+
+ length = M_ReadFile(sourceName, &buffer);
+ M_WriteFile(destName, buffer, length);
+ Z_Free(buffer);
+}
+
+//==========================================================================
+//
+// ExistingFile
+//
+//==========================================================================
+
+static boolean ExistingFile(char *name)
+{
+ FILE *fp;
+
+ if ((fp = fopen(name, "rb")) != NULL)
+ {
+ fclose(fp);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//==========================================================================
+//
+// OpenStreamOut
+//
+//==========================================================================
+
+static void OpenStreamOut(char *fileName)
+{
+ SavingFP = fopen(fileName, "wb");
+}
+
+//==========================================================================
+//
+// CloseStreamOut
+//
+//==========================================================================
+
+static void CloseStreamOut(void)
+{
+ if (SavingFP)
+ {
+ fclose(SavingFP);
+ }
+}
+
+//==========================================================================
+//
+// StreamOutBuffer
+//
+//==========================================================================
+
+static void StreamOutBuffer(void *buffer, int size)
+{
+ fwrite(buffer, size, 1, SavingFP);
+}
+
+//==========================================================================
+//
+// StreamOutByte
+//
+//==========================================================================
+
+static void StreamOutByte(byte val)
+{
+ fwrite(&val, sizeof(byte), 1, SavingFP);
+}
+
+//==========================================================================
+//
+// StreamOutWord
+//
+//==========================================================================
+
+static void StreamOutWord(unsigned short val)
+{
+ val = SHORT(val);
+ fwrite(&val, sizeof(unsigned short), 1, SavingFP);
+}
+
+//==========================================================================
+//
+// StreamOutLong
+//
+//==========================================================================
+
+static void StreamOutLong(unsigned int val)
+{
+ val = LONG(val);
+ fwrite(&val, sizeof(int), 1, SavingFP);
+}
+
+//==========================================================================
+//
+// StreamOutPtr
+//
+//==========================================================================
+
+static void StreamOutPtr(void *val)
+{
+ long ptr;
+
+ // Write a pointer value. In Vanilla Hexen pointers are 32-bit but
+ // nowadays they might be larger. Whatever value we write here isn't
+ // going to be much use when we reload the game.
+
+ ptr = (long) val;
+ StreamOutLong((unsigned int) (ptr & 0xffffffff));
+}
+
+