diff options
Diffstat (limited to 'src/hexen/sv_save.c')
-rw-r--r-- | src/hexen/sv_save.c | 1772 |
1 files changed, 1772 insertions, 0 deletions
diff --git a/src/hexen/sv_save.c b/src/hexen/sv_save.c new file mode 100644 index 00000000..63b0190b --- /dev/null +++ b/src/hexen/sv_save.c @@ -0,0 +1,1772 @@ +// 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 (*SavePtr.w++) +#define GET_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 (*mangleFunc) (); + 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 void MangleMobj(mobj_t * mobj); +static void RestoreMobj(mobj_t * mobj); +static int GetMobjNum(mobj_t * mobj); +static void SetMobjPtr(int *archiveNum); +static void MangleSSThinker(ssthinker_t * sst); +static void RestoreSSThinker(ssthinker_t * sst); +static void RestoreSSThinkerNoSD(ssthinker_t * sst); +static void MangleScript(acs_t * script); +static void RestoreScript(acs_t * script); +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); + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern int ACScriptCount; +extern byte *ActionCodeBase; +extern acsInfo_t *ACSInfo; + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +#define DEFAULT_SAVEPATH "hexndata/" + +char *SavePath = DEFAULT_SAVEPATH; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static int MobjCount; +static mobj_t **MobjList; +static int **TargetPlayerAddrs; +static int TargetPlayerCount; +static byte *SaveBuffer; +static boolean SavingPlayers; +static union +{ + byte *b; + short *w; + int *l; +} SavePtr; +static FILE *SavingFP; + +// This list has been prioritized using frequency estimates +static thinkInfo_t ThinkerInfo[] = { + { + TC_MOVE_FLOOR, + T_MoveFloor, + MangleSSThinker, + RestoreSSThinker, + sizeof(floormove_t)} + , + { + TC_PLAT_RAISE, + T_PlatRaise, + MangleSSThinker, + RestorePlatRaise, + sizeof(plat_t)} + , + { + TC_MOVE_CEILING, + T_MoveCeiling, + MangleSSThinker, + RestoreMoveCeiling, + sizeof(ceiling_t)} + , + { + TC_LIGHT, + T_Light, + MangleSSThinker, + RestoreSSThinkerNoSD, + sizeof(light_t)} + , + { + TC_VERTICAL_DOOR, + T_VerticalDoor, + MangleSSThinker, + RestoreSSThinker, + sizeof(vldoor_t)} + , + { + TC_PHASE, + T_Phase, + MangleSSThinker, + RestoreSSThinkerNoSD, + sizeof(phase_t)} + , + { + TC_INTERPRET_ACS, + T_InterpretACS, + MangleScript, + RestoreScript, + sizeof(acs_t)} + , + { + TC_ROTATE_POLY, + T_RotatePoly, + NULL, + NULL, + sizeof(polyevent_t)} + , + { + TC_BUILD_PILLAR, + T_BuildPillar, + MangleSSThinker, + RestoreSSThinker, + sizeof(pillar_t)} + , + { + TC_MOVE_POLY, + T_MovePoly, + NULL, + NULL, + sizeof(polyevent_t)} + , + { + TC_POLY_DOOR, + T_PolyDoor, + NULL, + NULL, + sizeof(polydoor_t)} + , + { + TC_FLOOR_WAGGLE, + T_FloorWaggle, + MangleSSThinker, + RestoreSSThinker, + sizeof(floorWaggle_t)} + , + { // Terminator + TC_NULL, NULL, NULL, NULL, 0} +}; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// SV_SaveGame +// +//========================================================================== + +void SV_SaveGame(int slot, char *description) +{ + char fileName[100]; + char versionText[HXS_VERSION_TEXT_LENGTH]; + + // 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 + StreamOutBuffer(WorldVars, sizeof(WorldVars)); + StreamOutBuffer(ACSStore, sizeof(ACSStore)); + + 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 + memcpy(WorldVars, SavePtr.b, sizeof(WorldVars)); + SavePtr.b += sizeof(WorldVars); + memcpy(ACSStore, SavePtr.b, sizeof(ACSStore)); + SavePtr.b += sizeof(ACSStore); + + // 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] = (int) 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; + int j; + player_t tempPlayer; + + 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]); + tempPlayer = players[i]; + for (j = 0; j < NUMPSPRITES; j++) + { + if (tempPlayer.psprites[j].state) + { + tempPlayer.psprites[j].state = + (state_t *) (tempPlayer.psprites[j].state - states); + } + } + StreamOutBuffer(&tempPlayer, sizeof(player_t)); + } +} + +//========================================================================== +// +// UnarchivePlayers +// +//========================================================================== + +static void UnarchivePlayers(void) +{ + int i, j; + + 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; + memcpy(&players[i], SavePtr.b, sizeof(player_t)); + SavePtr.b += sizeof(player_t); + players[i].mo = NULL; // Will be set when unarc thinker + P_ClearMessage(&players[i]); + players[i].attacker = NULL; + players[i].poisoner = NULL; + for (j = 0; j < NUMPSPRITES; j++) + { + if (players[i].psprites[j].state) + { + players[i].psprites[j].state = + &states[(int) players[i].psprites[j].state]; + } + } + } +} + +//========================================================================== +// +// 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; + mobj_t tempMobj; + + 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++; + memcpy(&tempMobj, thinker, sizeof(mobj_t)); + MangleMobj(&tempMobj); + StreamOutBuffer(&tempMobj, sizeof(mobj_t)); + } + 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(int *), + 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]; + memcpy(mobj, SavePtr.b, sizeof(mobj_t)); + SavePtr.b += sizeof(mobj_t); + mobj->thinker.function = P_MobjThinker; + RestoreMobj(mobj); + P_AddThinker(&mobj->thinker); + } + P_CreateTIDList(); + P_InitCreatureCorpseQueue(true); // true = scan for corpses +} + +//========================================================================== +// +// MangleMobj +// +//========================================================================== + +static void MangleMobj(mobj_t * mobj) +{ + boolean corpse; + + corpse = mobj->flags & MF_CORPSE; + mobj->state = (state_t *) (mobj->state - states); + if (mobj->player) + { + mobj->player = (player_t *) ((mobj->player - players) + 1); + } + if (corpse) + { + mobj->target = (mobj_t *) MOBJ_NULL; + } + else + { + mobj->target = (mobj_t *) GetMobjNum(mobj->target); + } + 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) + { + mobj->special1 = MOBJ_NULL; + } + else + { + mobj->special1 = GetMobjNum((mobj_t *) mobj->special1); + } + break; + + // Just special2 + case MT_LIGHTNING_FLOOR: + case MT_LIGHTNING_ZAP: + if (corpse) + { + mobj->special2 = MOBJ_NULL; + } + else + { + mobj->special2 = GetMobjNum((mobj_t *) mobj->special2); + } + break; + + // Both special1 and special2 + case MT_HOLY_TAIL: + case MT_LIGHTNING_CEILING: + if (corpse) + { + mobj->special1 = MOBJ_NULL; + mobj->special2 = MOBJ_NULL; + } + else + { + mobj->special1 = GetMobjNum((mobj_t *) mobj->special1); + mobj->special2 = GetMobjNum((mobj_t *) mobj->special2); + } + break; + + // Miscellaneous + case MT_KORAX: + mobj->special1 = 0; // Searching index + break; + + default: + break; + } +} + +//========================================================================== +// +// GetMobjNum +// +//========================================================================== + +static int GetMobjNum(mobj_t * mobj) +{ + if (mobj == NULL) + { + return MOBJ_NULL; + } + if (mobj->player && !SavingPlayers) + { + return MOBJ_XX_PLAYER; + } + return mobj->archiveNum; +} + +//========================================================================== +// +// RestoreMobj +// +//========================================================================== + +static void RestoreMobj(mobj_t * mobj) +{ + mobj->state = &states[(int) mobj->state]; + if (mobj->player) + { + mobj->player = &players[(int) mobj->player - 1]; + mobj->player->mo = mobj; + } + P_SetThingPosition(mobj); + mobj->info = &mobjinfo[mobj->type]; + mobj->floorz = mobj->subsector->sector->floorheight; + mobj->ceilingz = mobj->subsector->sector->ceilingheight; + SetMobjPtr((int *) &mobj->target); + 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); + break; + + // Just special2 + case MT_LIGHTNING_FLOOR: + case MT_LIGHTNING_ZAP: + SetMobjPtr(&mobj->special2); + break; + + // Both special1 and special2 + case MT_HOLY_TAIL: + case MT_LIGHTNING_CEILING: + SetMobjPtr(&mobj->special1); + SetMobjPtr(&mobj->special2); + break; + + default: + break; + } +} + +//========================================================================== +// +// SetMobjPtr +// +//========================================================================== + +static void SetMobjPtr(int *archiveNum) +{ + if (*archiveNum == MOBJ_NULL) + { + *archiveNum = 0; + return; + } + if (*archiveNum == MOBJ_XX_PLAYER) + { + if (TargetPlayerCount == MAX_TARGET_PLAYERS) + { + I_Error("RestoreMobj: exceeded MAX_TARGET_PLAYERS"); + } + TargetPlayerAddrs[TargetPlayerCount++] = archiveNum; + *archiveNum = 0; + return; + } + *archiveNum = (int) MobjList[*archiveNum]; +} + +//========================================================================== +// +// ArchiveThinkers +// +//========================================================================== + +static void ArchiveThinkers(void) +{ + thinker_t *thinker; + thinkInfo_t *info; + byte buffer[MAX_THINKER_SIZE]; + + 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); + memcpy(buffer, thinker, info->size); + if (info->mangleFunc) + { + info->mangleFunc(buffer); + } + StreamOutBuffer(buffer, info->size); + 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); + memcpy(thinker, SavePtr.b, info->size); + SavePtr.b += info->size; + 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); + } + } +} + +//========================================================================== +// +// MangleSSThinker +// +//========================================================================== + +static void MangleSSThinker(ssthinker_t * sst) +{ + sst->sector = (sector_t *) (sst->sector - sectors); +} + +//========================================================================== +// +// RestoreSSThinker +// +//========================================================================== + +static void RestoreSSThinker(ssthinker_t * sst) +{ + sst->sector = §ors[(int) sst->sector]; + sst->sector->specialdata = sst->thinker.function; +} + +//========================================================================== +// +// RestoreSSThinkerNoSD +// +//========================================================================== + +static void RestoreSSThinkerNoSD(ssthinker_t * sst) +{ + sst->sector = §ors[(int) sst->sector]; +} + +//========================================================================== +// +// MangleScript +// +//========================================================================== + +static void MangleScript(acs_t * script) +{ + script->ip = (int *) ((int) (script->ip) - (int) ActionCodeBase); + script->line = script->line ? + (line_t *) (script->line - lines) : (line_t *) - 1; + script->activator = (mobj_t *) GetMobjNum(script->activator); +} + +//========================================================================== +// +// RestoreScript +// +//========================================================================== + +static void RestoreScript(acs_t * script) +{ + script->ip = (int *) (ActionCodeBase + (int) script->ip); + if ((int) script->line == -1) + { + script->line = NULL; + } + else + { + script->line = &lines[(int) script->line]; + } + SetMobjPtr((int *) &script->activator); +} + +//========================================================================== +// +// RestorePlatRaise +// +//========================================================================== + +static void RestorePlatRaise(plat_t * plat) +{ + plat->sector = §ors[(int) plat->sector]; + plat->sector->specialdata = T_PlatRaise; + P_AddActivePlat(plat); +} + +//========================================================================== +// +// RestoreMoveCeiling +// +//========================================================================== + +static void RestoreMoveCeiling(ceiling_t * ceiling) +{ + ceiling->sector = §ors[(int) ceiling->sector]; + 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); + } + StreamOutBuffer(MapVars, sizeof(MapVars)); +} + +//========================================================================== +// +// 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; + } + memcpy(MapVars, SavePtr.b, sizeof(MapVars)); + SavePtr.b += sizeof(MapVars); +} + +//========================================================================== +// +// 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) +{ + fwrite(&val, sizeof(unsigned short), 1, SavingFP); +} + +//========================================================================== +// +// StreamOutLong +// +//========================================================================== + +static void StreamOutLong(unsigned int val) +{ + fwrite(&val, sizeof(int), 1, SavingFP); +} |