hexen/Hexen Source/SV_SAVE.C
2008-09-04 00:00:00 +00:00

1756 lines
38 KiB
C

//**************************************************************************
//**
//** sv_save.c : Heretic 2 : Raven Software, Corp.
//**
//** $RCSfile: sv_save.c,v $
//** $Revision: 1.36 $
//** $Date: 95/10/17 00:10:02 $
//** $Author: bgokey $
//**
//**************************************************************************
// HEADER FILES ------------------------------------------------------------
#include "h2def.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 -------------------------------------------------
char *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(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;
int oldPieces;
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 = &sectors[(int)sst->sector];
sst->sector->specialdata = sst->thinker.function;
}
//==========================================================================
//
// RestoreSSThinkerNoSD
//
//==========================================================================
static void RestoreSSThinkerNoSD(ssthinker_t *sst)
{
sst->sector = &sectors[(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 = &sectors[(int)plat->sector];
plat->sector->specialdata = T_PlatRaise;
P_AddActivePlat(plat);
}
//==========================================================================
//
// RestoreMoveCeiling
//
//==========================================================================
static void RestoreMoveCeiling(ceiling_t *ceiling)
{
ceiling->sector = &sectors[(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);
}