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