ref: ea810eeed93cbc7e988f47841d26fc10f8c44c7c
dir: /src/hexen/sv_save.c/
// 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); }