shithub: choc

ref: 69be33fe88d0a55dce27214e7543cded8ff51175
dir: /src/strife/p_saveg.c/

View raw version
// Emacs style mode select   -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005 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.
//
// DESCRIPTION:
//	Archiving: SaveGame I/O.
//
//-----------------------------------------------------------------------------


#include <stdio.h>
#include <stdlib.h>

#include "dstrings.h"
#include "deh_main.h"
#include "i_system.h"
#include "z_zone.h"
#include "p_local.h"
#include "p_saveg.h"

// State.
#include "doomstat.h"
#include "r_state.h"

#define SAVEGAME_EOF 0x1d

// haleyjd 09/28/10: [STRIFE] VERSIONSIZE == 8
#define VERSIONSIZE 8 

FILE *save_stream;
int savegamelength;
boolean savegame_error;

// Get the filename of a temporary file to write the savegame to.  After
// the file has been successfully saved, it will be renamed to the 
// real file.

char *P_TempSaveGameFile(void)
{
    static char *filename = NULL;

    if (filename == NULL)
    {
        filename = malloc(strlen(savegamedir) + 32);
    }

    sprintf(filename, "%stemp.dsg", savegamedir);

    return filename;
}

// Get the filename of the save game file to use for the specified slot.

char *P_SaveGameFile(int slot)
{
    static char *filename = NULL;
    char basename[32];

    if (filename == NULL)
    {
        filename = malloc(strlen(savegamedir) + 32);
    }

    DEH_snprintf(basename, 32, SAVEGAMENAME "%d.dsg", slot);

    sprintf(filename, "%s%s", savegamedir, basename);

    return filename;
}

// Endian-safe integer read/write functions

static byte saveg_read8(void)
{
    byte result;

    if (fread(&result, 1, 1, save_stream) < 1)
    {
        if (!savegame_error)
        {
            fprintf(stderr, "saveg_read8: Unexpected end of file while "
                            "reading save game\n");

            savegame_error = true;
        }
    }

    return result;
}

static void saveg_write8(byte value)
{
    if (fwrite(&value, 1, 1, save_stream) < 1)
    {
        if (!savegame_error)
        {
            fprintf(stderr, "saveg_write8: Error while writing save game\n");

            savegame_error = true;
        }
    }
}

static short saveg_read16(void)
{
    int result;

    result = saveg_read8();
    result |= saveg_read8() << 8;

    return result;
}

static void saveg_write16(short value)
{
    saveg_write8(value & 0xff);
    saveg_write8((value >> 8) & 0xff);
}

static int saveg_read32(void)
{
    int result;

    result = saveg_read8();
    result |= saveg_read8() << 8;
    result |= saveg_read8() << 16;
    result |= saveg_read8() << 24;

    return result;
}

static void saveg_write32(int value)
{
    saveg_write8(value & 0xff);
    saveg_write8((value >> 8) & 0xff);
    saveg_write8((value >> 16) & 0xff);
    saveg_write8((value >> 24) & 0xff);
}

// Pad to 4-byte boundaries

static void saveg_read_pad(void)
{
    unsigned long pos;
    int padding;
    int i;

    pos = ftell(save_stream);

    padding = (4 - (pos & 3)) & 3;

    for (i=0; i<padding; ++i)
    {
        saveg_read8();
    }
}

static void saveg_write_pad(void)
{
    unsigned long pos;
    int padding;
    int i;

    pos = ftell(save_stream);

    padding = (4 - (pos & 3)) & 3;

    for (i=0; i<padding; ++i)
    {
        saveg_write8(0);
    }
}


// Pointers

static void *saveg_readp(void)
{
    return (void *) saveg_read32();
}

static void saveg_writep(void *p)
{
    saveg_write32((int) p);
}

// Enum values are 32-bit integers.

#define saveg_read_enum saveg_read32
#define saveg_write_enum saveg_write32

//
// Structure read/write functions
//

//
// mapthing_t
//

static void saveg_read_mapthing_t(mapthing_t *str)
{
    // short x;
    str->x = saveg_read16();

    // short y;
    str->y = saveg_read16();

    // short angle;
    str->angle = saveg_read16();

    // short type;
    str->type = saveg_read16();

    // short options;
    str->options = saveg_read16();
}

static void saveg_write_mapthing_t(mapthing_t *str)
{
    // short x;
    saveg_write16(str->x);

    // short y;
    saveg_write16(str->y);

    // short angle;
    saveg_write16(str->angle);

    // short type;
    saveg_write16(str->type);

    // short options;
    saveg_write16(str->options);
}

//
// actionf_t
// 

static void saveg_read_actionf_t(actionf_t *str)
{
    // actionf_p1 acp1;
    str->acp1 = saveg_readp();
}

static void saveg_write_actionf_t(actionf_t *str)
{
    // actionf_p1 acp1;
    saveg_writep(str->acp1);
}

//
// think_t
//
// This is just an actionf_t.
//

#define saveg_read_think_t saveg_read_actionf_t
#define saveg_write_think_t saveg_write_actionf_t

//
// thinker_t
//

static void saveg_read_thinker_t(thinker_t *str)
{
    // struct thinker_s* prev;
    str->prev = saveg_readp();

    // struct thinker_s* next;
    str->next = saveg_readp();

    // think_t function;
    saveg_read_think_t(&str->function);
}

static void saveg_write_thinker_t(thinker_t *str)
{
    // struct thinker_s* prev;
    saveg_writep(str->prev);

    // struct thinker_s* next;
    saveg_writep(str->next);

    // think_t function;
    saveg_write_think_t(&str->function);
}

//
// mobj_t
//
// haleyjd 09/28/10: [STRIFE] Changed to match Strife binary mobj_t structure.
// 

static void saveg_read_mobj_t(mobj_t *str)
{
    int pl;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // fixed_t x;
    str->x = saveg_read32();

    // fixed_t y;
    str->y = saveg_read32();

    // fixed_t z;
    str->z = saveg_read32();

    // struct mobj_s* snext;
    str->snext = saveg_readp();

    // struct mobj_s* sprev;
    str->sprev = saveg_readp();

    // angle_t angle;
    str->angle = saveg_read32();

    // spritenum_t sprite;
    str->sprite = saveg_read_enum();

    // int frame;
    str->frame = saveg_read32();

    // struct mobj_s* bnext;
    str->bnext = saveg_readp();

    // struct mobj_s* bprev;
    str->bprev = saveg_readp();

    // struct subsector_s* subsector;
    str->subsector = saveg_readp();

    // fixed_t floorz;
    str->floorz = saveg_read32();

    // fixed_t ceilingz;
    str->ceilingz = saveg_read32();

    // fixed_t radius;
    str->radius = saveg_read32();

    // fixed_t height;
    str->height = saveg_read32();

    // fixed_t momx;
    str->momx = saveg_read32();

    // fixed_t momy;
    str->momy = saveg_read32();

    // fixed_t momz;
    str->momz = saveg_read32();

    // int validcount;
    str->validcount = saveg_read32();

    // mobjtype_t type;
    str->type = saveg_read_enum();

    // mobjinfo_t* info;
    str->info = saveg_readp();

    // int tics;
    str->tics = saveg_read32();

    // state_t* state;
    str->state = &states[saveg_read32()];

    // int flags;
    str->flags = saveg_read32();

    // int health;
    str->health = saveg_read32();

    // int movedir;
    str->movedir = saveg_read32();

    // int movecount;
    str->movecount = saveg_read32();

    // struct mobj_s* target;
    str->target = saveg_readp();

    // int reactiontime;
    str->reactiontime = saveg_read32();

    // int threshold;
    str->threshold = saveg_read32();

    // struct player_s* player;
    pl = saveg_read32();

    if (pl > 0)
    {
        str->player = &players[pl - 1];
        str->player->mo = str;
    }
    else
    {
        str->player = NULL;
    }

    // int lastlook;
    str->lastlook = saveg_read32();

    // mapthing_t spawnpoint;
    saveg_read_mapthing_t(&str->spawnpoint);

    // struct mobj_s* tracer;
    str->tracer = saveg_readp();

    // byte miscdata;
    str->miscdata = saveg_read8(); // [STRIFE] Only change to mobj_t.
}

static void saveg_write_mobj_t(mobj_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // fixed_t x;
    saveg_write32(str->x);

    // fixed_t y;
    saveg_write32(str->y);

    // fixed_t z;
    saveg_write32(str->z);

    // struct mobj_s* snext;
    saveg_writep(str->snext);

    // struct mobj_s* sprev;
    saveg_writep(str->sprev);

    // angle_t angle;
    saveg_write32(str->angle);

    // spritenum_t sprite;
    saveg_write_enum(str->sprite);

    // int frame;
    saveg_write32(str->frame);

    // struct mobj_s* bnext;
    saveg_writep(str->bnext);

    // struct mobj_s* bprev;
    saveg_writep(str->bprev);

    // struct subsector_s* subsector;
    saveg_writep(str->subsector);

    // fixed_t floorz;
    saveg_write32(str->floorz);

    // fixed_t ceilingz;
    saveg_write32(str->ceilingz);

    // fixed_t radius;
    saveg_write32(str->radius);

    // fixed_t height;
    saveg_write32(str->height);

    // fixed_t momx;
    saveg_write32(str->momx);

    // fixed_t momy;
    saveg_write32(str->momy);

    // fixed_t momz;
    saveg_write32(str->momz);

    // int validcount;
    saveg_write32(str->validcount);

    // mobjtype_t type;
    saveg_write_enum(str->type);

    // mobjinfo_t* info;
    saveg_writep(str->info);

    // int tics;
    saveg_write32(str->tics);

    // state_t* state;
    saveg_write32(str->state - states);

    // int flags;
    saveg_write32(str->flags);

    // int health;
    saveg_write32(str->health);

    // int movedir;
    saveg_write32(str->movedir);

    // int movecount;
    saveg_write32(str->movecount);

    // struct mobj_s* target;
    saveg_writep(str->target);

    // int reactiontime;
    saveg_write32(str->reactiontime);

    // int threshold;
    saveg_write32(str->threshold);

    // struct player_s* player;
    if (str->player)
    {
        saveg_write32(str->player - players + 1);
    }
    else
    {
        saveg_write32(0);
    }

    // int lastlook;
    saveg_write32(str->lastlook);

    // mapthing_t spawnpoint;
    saveg_write_mapthing_t(&str->spawnpoint);

    // struct mobj_s* tracer;
    saveg_writep(str->tracer);

    // byte miscdata;
    saveg_write8(str->miscdata); // [STRIFE] Only change to mobj_t.
}


//
// ticcmd_t
//
// haleyjd 09/28/10: [STRIFE] Modified for Strife binary ticcmd_t structure.
//

static void saveg_read_ticcmd_t(ticcmd_t *str)
{
    // signed char forwardmove;
    str->forwardmove = saveg_read8();

    // signed char sidemove;
    str->sidemove = saveg_read8();

    // short angleturn;
    str->angleturn = saveg_read16();

    // short consistancy;
    // STRIFE-FIXME: throwing away top byte of consistancy until
    // the true Strife ticcmd_t structure is available.
    str->consistancy = (byte)saveg_read16();

    // byte chatchar;
    str->chatchar = saveg_read8();

    // byte buttons;
    str->buttons = saveg_read8();

    // byte buttons2;
    str->buttons2 = saveg_read8(); // [STRIFE]

    // int inventory;
    str->inventory = saveg_read32(); // [STRIFE]
}

static void saveg_write_ticcmd_t(ticcmd_t *str)
{
    // signed char forwardmove;
    saveg_write8(str->forwardmove);

    // signed char sidemove;
    saveg_write8(str->sidemove);

    // short angleturn;
    saveg_write16(str->angleturn);

    // short consistancy;
    saveg_write16(str->consistancy);

    // byte chatchar;
    saveg_write8(str->chatchar);

    // byte buttons;
    saveg_write8(str->buttons);

    // byte buttons2;
    saveg_write8(str->buttons2); // [STRIFE]

    // int inventory;
    saveg_write32(str->inventory); // [STRIFE]
}

//
// pspdef_t
//

static void saveg_read_pspdef_t(pspdef_t *str)
{
    int state;

    // state_t* state;
    state = saveg_read32();

    if (state > 0)
    {
        str->state = &states[state];
    }
    else
    {
        str->state = NULL;
    }

    // int tics;
    str->tics = saveg_read32();

    // fixed_t sx;
    str->sx = saveg_read32();

    // fixed_t sy;
    str->sy = saveg_read32();
}

static void saveg_write_pspdef_t(pspdef_t *str)
{
    // state_t* state;
    if (str->state)
    {
        saveg_write32(str->state - states);
    }
    else
    {
        saveg_write32(0);
    }

    // int tics;
    saveg_write32(str->tics);

    // fixed_t sx;
    saveg_write32(str->sx);

    // fixed_t sy;
    saveg_write32(str->sy);
}

//
// inventory_t 
//
// haleyjd 09/28/10: [STRIFE] handle inventory input/output
//

static void saveg_read_inventory_t(inventory_t *str)
{
    //int sprite;
    str->sprite = saveg_read32();

    //int type;
    str->type = saveg_read32();

    //int amount;
    str->amount = saveg_read32();
}

static void saveg_write_inventory_t(inventory_t *str)
{
    saveg_write32(str->sprite);
    saveg_write32(str->type);
    saveg_write32(str->amount);
}

//
// player_t
//
// haleyjd 09/28/10: [STRIFE] Modified for Strife binary player_t structure.
//

static void saveg_read_player_t(player_t *str)
{
    int i;

    // mobj_t* mo;
    str->mo = saveg_readp();

    // playerstate_t playerstate;
    str->playerstate = saveg_read_enum();

    // ticcmd_t cmd;
    saveg_read_ticcmd_t(&str->cmd);

    // fixed_t viewz;
    str->viewz = saveg_read32();

    // fixed_t viewheight;
    str->viewheight = saveg_read32();

    // fixed_t deltaviewheight;
    str->deltaviewheight = saveg_read32();

    // fixed_t bob;
    str->bob = saveg_read32();

    // int health;
    str->health = saveg_read32();

    // int armorpoints;
    str->armorpoints = saveg_read16(); // [STRIFE] 32 -> 16

    // int armortype;
    str->armortype = saveg_read16(); // [STRIFE] 32 -> 16

    // int powers[NUMPOWERS];
    for (i=0; i<NUMPOWERS; ++i)
    {
        str->powers[i] = saveg_read32();
    }

    // int sigiltype;
    str->sigiltype = saveg_read32(); // [STRIFE]

    // int nukagecount;
    str->nukagecount = saveg_read32(); // [STRIFE]

    // int questflags;
    str->questflags = saveg_read32(); // [STRIFE]

    // int pitch;
    str->pitch = saveg_read32(); // [STRIFE]

    // int centerview;
    str->centerview = saveg_read32(); // [STRIFE]

    // inventory_t inventory[NUMINVENTORY];
    for(i = 0; i < NUMINVENTORY; i++)
    {
        saveg_read_inventory_t(&(str->inventory[i])); // [STRIFE]
    }

    // int st_update;
    str->st_update = saveg_read32(); // [STRIFE]

    // short numinventory;
    str->numinventory = saveg_read16(); // [STRIFE]

    // short inventorycursor;
    str->inventorycursor = saveg_read16(); // [STRIFE]

    // short accuracy;
    str->accuracy = saveg_read16(); // [STRIFE]

    // short stamina;
    str->stamina = saveg_read16(); // [STRIFE]

    // boolean cards[NUMCARDS];
    for (i=0; i<NUMCARDS; ++i)
    {
        str->cards[i] = saveg_read32();
    }

    // boolean backpack;
    str->backpack = saveg_read32();

    // int attackdown;
    str->attackdown = saveg_read32();

    // int usedown;
    str->usedown = saveg_read32();

    // int inventorydown;
    str->inventorydown = saveg_read32(); // [STRIFE]

    // int frags[MAXPLAYERS];
    for (i=0; i<MAXPLAYERS; ++i)
    {
        str->frags[i] = saveg_read32();
    }

    // weapontype_t readyweapon;
    str->readyweapon = saveg_read_enum();

    // weapontype_t pendingweapon;
    str->pendingweapon = saveg_read_enum();

    // boolean weaponowned[NUMWEAPONS];
    for (i=0; i<NUMWEAPONS; ++i)
    {
        str->weaponowned[i] = saveg_read32();
    }

    // int ammo[NUMAMMO];
    for (i=0; i<NUMAMMO; ++i)
    {
        str->ammo[i] = saveg_read32();
    }

    // int maxammo[NUMAMMO];
    for (i=0; i<NUMAMMO; ++i)
    {
        str->maxammo[i] = saveg_read32();
    }

    // int cheats;
    str->cheats = saveg_read32();

    // int refire;
    str->refire = saveg_read32();

    // short killcount;
    str->killcount = saveg_read16(); // [STRIFE] 32 -> 16

    // haleyjd 08/30/10 [STRIFE] No itemcount.
    // int itemcount;
    //str->itemcount = saveg_read32();

    // haleyjd 08/30/10 [STRIFE] No secretcount.
    // int secretcount;
    //str->secretcount = saveg_read32();

    // char* message;
    str->message = saveg_readp();

    // int damagecount;
    str->damagecount = saveg_read32();

    // int bonuscount;
    str->bonuscount = saveg_read32();

    // mobj_t* attacker;
    str->attacker = saveg_readp();

    // int extralight;
    str->extralight = saveg_read32();

    // int fixedcolormap;
    str->fixedcolormap = saveg_read32();

    // int colormap; - [STRIFE] no such field
    //str->colormap = saveg_read32();

    // short allegiance;
    str->allegiance = saveg_read16(); // [STRIFE]

    // pspdef_t psprites[NUMPSPRITES];
    for (i=0; i<NUMPSPRITES; ++i)
    {
        saveg_read_pspdef_t(&str->psprites[i]);
    }

    // int mapstate[40];
    for(i = 0; i < 40; ++i) // [STRIFE]
    {
        str->mapstate[i] = saveg_read32();
    }

    // haleyjd 08/30/10: [STRIFE] No intermission, no didsecret.
    // boolean didsecret;
    //str->didsecret = saveg_read32();
}

static void saveg_write_player_t(player_t *str)
{
    int i;

    // mobj_t* mo;
    saveg_writep(str->mo);

    // playerstate_t playerstate;
    saveg_write_enum(str->playerstate);

    // ticcmd_t cmd;
    saveg_write_ticcmd_t(&str->cmd);

    // fixed_t viewz;
    saveg_write32(str->viewz);

    // fixed_t viewheight;
    saveg_write32(str->viewheight);

    // fixed_t deltaviewheight;
    saveg_write32(str->deltaviewheight);

    // fixed_t bob;
    saveg_write32(str->bob);

    // int health;
    saveg_write32(str->health);

    // int armorpoints;
    saveg_write16(str->armorpoints); // [STRIFE] 32 -> 16

    // int armortype;
    saveg_write16(str->armortype); // [STRIFE] 32 -> 16

    // int powers[NUMPOWERS];
    for (i=0; i<NUMPOWERS; ++i)
    {
        saveg_write32(str->powers[i]);
    }

    // int sigiltype;
    saveg_write32(str->sigiltype); // [STRIFE]

    // int nukagecount;
    saveg_write32(str->nukagecount); // [STRIFE]

    // int questflags;
    saveg_write32(str->questflags); // [STRIFE]

    // int pitch;
    saveg_write32(str->pitch); // [STRIFE]

    // int centerview;
    saveg_write32(str->centerview); // [STRIFE]

    // inventory_t inventory[NUMINVENTORY];
    for(i = 0; i < NUMINVENTORY; ++i) // [STRIFE]
    {
        saveg_write_inventory_t(&str->inventory[i]);
    }

    // int st_update;
    saveg_write32(str->st_update); // [STRIFE]

    // short numinventory;
    saveg_write16(str->numinventory); // [STRIFE]

    // short inventorycursor;
    saveg_write16(str->inventorycursor); // [STRIFE]

    // short accuracy;
    saveg_write16(str->accuracy); // [STRIFE]

    // short stamina;
    saveg_write16(str->stamina); // [STRIFE]

    // boolean cards[NUMCARDS];
    for (i=0; i<NUMCARDS; ++i)
    {
        saveg_write32(str->cards[i]);
    }

    // boolean backpack;
    saveg_write32(str->backpack);

    // int attackdown;
    saveg_write32(str->attackdown);

    // int usedown;
    saveg_write32(str->usedown);

    // int inventorydown;
    saveg_write32(str->inventorydown); // [STRIFE]

    // int frags[MAXPLAYERS];
    for (i=0; i<MAXPLAYERS; ++i)
    {
        saveg_write32(str->frags[i]);
    }

    // weapontype_t readyweapon;
    saveg_write_enum(str->readyweapon);

    // weapontype_t pendingweapon;
    saveg_write_enum(str->pendingweapon);

    // boolean weaponowned[NUMWEAPONS];
    for (i=0; i<NUMWEAPONS; ++i)
    {
        saveg_write32(str->weaponowned[i]);
    }

    // int ammo[NUMAMMO];
    for (i=0; i<NUMAMMO; ++i)
    {
        saveg_write32(str->ammo[i]);
    }

    // int maxammo[NUMAMMO];
    for (i=0; i<NUMAMMO; ++i)
    {
        saveg_write32(str->maxammo[i]);
    }


    // int cheats;
    saveg_write32(str->cheats);

    // int refire;
    saveg_write32(str->refire);

    // short killcount;
    saveg_write16(str->killcount); // [STRIFE] 32 -> 16

    // haleyjd 08/30/10 [STRIFE] No itemcount
    // int itemcount;
    //saveg_write32(str->itemcount);

    // haleyjd 08/30/10 [STRIFE] No secretcount
    // int secretcount;
    //saveg_write32(str->secretcount);

    // char* message;
    saveg_writep(str->message);

    // int damagecount;
    saveg_write32(str->damagecount);

    // int bonuscount;
    saveg_write32(str->bonuscount);

    // mobj_t* attacker;
    saveg_writep(str->attacker);

    // int extralight;
    saveg_write32(str->extralight);

    // int fixedcolormap;
    saveg_write32(str->fixedcolormap);

    // int colormap; [STRIFE] no such field
    //saveg_write32(str->colormap);

    // short allegiance;
    saveg_write16(str->allegiance); // [STRIFE]

    // pspdef_t psprites[NUMPSPRITES];
    for (i=0; i<NUMPSPRITES; ++i)
    {
        saveg_write_pspdef_t(&str->psprites[i]);
    }

    // int mapstate[40];
    for(i = 0; i < 40; ++i) // [STRIFE]
    {
        saveg_write32(str->mapstate[i]);
    }

    // haleyjd 08/30/10: [STRIFE] No intermission, no secret.
    // boolean didsecret;
    //saveg_write32(str->didsecret);
}


//
// ceiling_t
//

static void saveg_read_ceiling_t(ceiling_t *str)
{
    int sector;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // ceiling_e type;
    str->type = saveg_read_enum();

    // sector_t* sector;
    sector = saveg_read32();
    str->sector = &sectors[sector];

    // fixed_t bottomheight;
    str->bottomheight = saveg_read32();

    // fixed_t topheight;
    str->topheight = saveg_read32();

    // fixed_t speed;
    str->speed = saveg_read32();

    // boolean crush;
    str->crush = saveg_read32();

    // int direction;
    str->direction = saveg_read32();

    // int tag;
    str->tag = saveg_read32();

    // int olddirection;
    str->olddirection = saveg_read32();
}

static void saveg_write_ceiling_t(ceiling_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // ceiling_e type;
    saveg_write_enum(str->type);

    // sector_t* sector;
    saveg_write32(str->sector - sectors);

    // fixed_t bottomheight;
    saveg_write32(str->bottomheight);

    // fixed_t topheight;
    saveg_write32(str->topheight);

    // fixed_t speed;
    saveg_write32(str->speed);

    // boolean crush;
    saveg_write32(str->crush);

    // int direction;
    saveg_write32(str->direction);

    // int tag;
    saveg_write32(str->tag);

    // int olddirection;
    saveg_write32(str->olddirection);
}

//
// vldoor_t
//
// haleyjd 09/28/10: [STRIFE] Modified for Strife binary vldoor_t structure.
//

static void saveg_read_vldoor_t(vldoor_t *str)
{
    int sector;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // vldoor_e type;
    str->type = saveg_read_enum();

    // sector_t* sector;
    sector = saveg_read32();
    str->sector = &sectors[sector];

    // fixed_t topheight;
    str->topheight = saveg_read32();

    // fixed_t speed;
    str->speed = saveg_read32();

    // int direction;
    str->direction = saveg_read32();

    // int topwait;
    str->topwait = saveg_read32();

    // int topcountdown;
    str->topcountdown = saveg_read32();

    // villsa [STRIFE] new field - sound to play when opening
    //int         opensound;
    str->opensound = saveg_read32();

    // villsa [STRIFE] new field - sound to play when closing
    //int         closesound;
    str->closesound = saveg_read32();
}

static void saveg_write_vldoor_t(vldoor_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // vldoor_e type;
    saveg_write_enum(str->type);

    // sector_t* sector;
    saveg_write32(str->sector - sectors);

    // fixed_t topheight;
    saveg_write32(str->topheight);

    // fixed_t speed;
    saveg_write32(str->speed);

    // int direction;
    saveg_write32(str->direction);

    // int topwait;
    saveg_write32(str->topwait);

    // int topcountdown;
    saveg_write32(str->topcountdown);

    // villsa [STRIFE] new field - sound to play when opening
    //int         opensound;
    saveg_write32(str->opensound);

    // villsa [STRIFE] new field - sound to play when closing
    //int         closesound;
    saveg_write32(str->closesound);
}

//
// slidedoor_t [STRIFE]: new thinker type
//

static void saveg_read_slidedoor_t(slidedoor_t *str)
{
    int sector;
    int line;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // sdt_e type;
    str->type = saveg_read_enum();

    // line_t *line1;
    line = saveg_read32();
    str->line1 = &lines[line];

    // line_t *line2;
    line = saveg_read32();
    str->line2 = &lines[line];

    // int frame;
    str->frame = saveg_read32();

    // int whichDoorIndex;
    str->whichDoorIndex = saveg_read32();

    // int timer;
    str->timer = saveg_read32();

    // sector_t *frontsector;
    sector = saveg_read32();
    str->frontsector = &sectors[sector];

    // sd_e status;
    str->status = saveg_read_enum();
}

static void saveg_write_slidedoor_t(slidedoor_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // sdt_e type;
    saveg_write_enum(str->type);

    // line_t *line1;
    saveg_write32(str->line1 - lines);

    // line_t *line2;
    saveg_write32(str->line2 - lines);

    // int frame;
    saveg_write32(str->frame);

    // int whichDoorIndex;
    saveg_write32(str->whichDoorIndex);

    // int timer;
    saveg_write32(str->timer);

    // sector_t *frontsector;
    saveg_write32(str->frontsector - sectors);

    // sd_e status;
    saveg_write_enum(str->status);
}

//
// floormove_t
//

static void saveg_read_floormove_t(floormove_t *str)
{
    int sector;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // floor_e type;
    str->type = saveg_read_enum();

    // boolean crush;
    str->crush = saveg_read32();

    // sector_t* sector;
    sector = saveg_read32();
    str->sector = &sectors[sector];

    // int direction;
    str->direction = saveg_read32();

    // int newspecial;
    str->newspecial = saveg_read32();

    // short texture;
    str->texture = saveg_read16();

    // fixed_t floordestheight;
    str->floordestheight = saveg_read32();

    // fixed_t speed;
    str->speed = saveg_read32();
}

static void saveg_write_floormove_t(floormove_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // floor_e type;
    saveg_write_enum(str->type);

    // boolean crush;
    saveg_write32(str->crush);

    // sector_t* sector;
    saveg_write32(str->sector - sectors);

    // int direction;
    saveg_write32(str->direction);

    // int newspecial;
    saveg_write32(str->newspecial);

    // short texture;
    saveg_write16(str->texture);

    // fixed_t floordestheight;
    saveg_write32(str->floordestheight);

    // fixed_t speed;
    saveg_write32(str->speed);
}

//
// plat_t
//

static void saveg_read_plat_t(plat_t *str)
{
    int sector;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // sector_t* sector;
    sector = saveg_read32();
    str->sector = &sectors[sector];

    // fixed_t speed;
    str->speed = saveg_read32();

    // fixed_t low;
    str->low = saveg_read32();

    // fixed_t high;
    str->high = saveg_read32();

    // int wait;
    str->wait = saveg_read32();

    // int count;
    str->count = saveg_read32();

    // plat_e status;
    str->status = saveg_read_enum();

    // plat_e oldstatus;
    str->oldstatus = saveg_read_enum();

    // boolean crush;
    str->crush = saveg_read32();

    // int tag;
    str->tag = saveg_read32();

    // plattype_e type;
    str->type = saveg_read_enum();
}

static void saveg_write_plat_t(plat_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // sector_t* sector;
    saveg_write32(str->sector - sectors);

    // fixed_t speed;
    saveg_write32(str->speed);

    // fixed_t low;
    saveg_write32(str->low);

    // fixed_t high;
    saveg_write32(str->high);

    // int wait;
    saveg_write32(str->wait);

    // int count;
    saveg_write32(str->count);

    // plat_e status;
    saveg_write_enum(str->status);

    // plat_e oldstatus;
    saveg_write_enum(str->oldstatus);

    // boolean crush;
    saveg_write32(str->crush);

    // int tag;
    saveg_write32(str->tag);

    // plattype_e type;
    saveg_write_enum(str->type);
}

//
// lightflash_t
//

static void saveg_read_lightflash_t(lightflash_t *str)
{
    int sector;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // sector_t* sector;
    sector = saveg_read32();
    str->sector = &sectors[sector];

    // int count;
    str->count = saveg_read32();

    // int maxlight;
    str->maxlight = saveg_read32();

    // int minlight;
    str->minlight = saveg_read32();

    // int maxtime;
    str->maxtime = saveg_read32();

    // int mintime;
    str->mintime = saveg_read32();
}

static void saveg_write_lightflash_t(lightflash_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // sector_t* sector;
    saveg_write32(str->sector - sectors);

    // int count;
    saveg_write32(str->count);

    // int maxlight;
    saveg_write32(str->maxlight);

    // int minlight;
    saveg_write32(str->minlight);

    // int maxtime;
    saveg_write32(str->maxtime);

    // int mintime;
    saveg_write32(str->mintime);
}

//
// strobe_t
//

static void saveg_read_strobe_t(strobe_t *str)
{
    int sector;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // sector_t* sector;
    sector = saveg_read32();
    str->sector = &sectors[sector];

    // int count;
    str->count = saveg_read32();

    // int minlight;
    str->minlight = saveg_read32();

    // int maxlight;
    str->maxlight = saveg_read32();

    // int darktime;
    str->darktime = saveg_read32();

    // int brighttime;
    str->brighttime = saveg_read32();
}

static void saveg_write_strobe_t(strobe_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // sector_t* sector;
    saveg_write32(str->sector - sectors);

    // int count;
    saveg_write32(str->count);

    // int minlight;
    saveg_write32(str->minlight);

    // int maxlight;
    saveg_write32(str->maxlight);

    // int darktime;
    saveg_write32(str->darktime);

    // int brighttime;
    saveg_write32(str->brighttime);
}

//
// glow_t
//

static void saveg_read_glow_t(glow_t *str)
{
    int sector;

    // thinker_t thinker;
    saveg_read_thinker_t(&str->thinker);

    // sector_t* sector;
    sector = saveg_read32();
    str->sector = &sectors[sector];

    // int minlight;
    str->minlight = saveg_read32();

    // int maxlight;
    str->maxlight = saveg_read32();

    // int direction;
    str->direction = saveg_read32();
}

static void saveg_write_glow_t(glow_t *str)
{
    // thinker_t thinker;
    saveg_write_thinker_t(&str->thinker);

    // sector_t* sector;
    saveg_write32(str->sector - sectors);

    // int minlight;
    saveg_write32(str->minlight);

    // int maxlight;
    saveg_write32(str->maxlight);

    // int direction;
    saveg_write32(str->direction);
}

//
// Write the header for a savegame
//
// haleyjd 09/28/10: [STRIFE] numerous modifications.
//

void P_WriteSaveGameHeader(char *description)
{
    char name[VERSIONSIZE]; 
    int i; 

    /*
    [STRIFE] This is in the "NAME" file in a Strife save directory.
    for (i=0; description[i] != '\0'; ++i)
        saveg_write8(description[i]);
    for (; i<SAVESTRINGSIZE; ++i)
        saveg_write8(0);
    */

    memset (name,0,sizeof(name)); 
    sprintf (name,"ver %i",STRIFE_VERSION); 

    for (i=0; i<VERSIONSIZE; ++i)
        saveg_write8(name[i]);

    saveg_write8(gameskill);
    
    // [STRIFE] This information is implicit in the file being loaded.
    //saveg_write8(gameepisode);
    //saveg_write8(gamemap);

    for (i=0 ; i<MAXPLAYERS ; i++) 
        saveg_write8(playeringame[i]);

    saveg_write8((leveltime >> 16) & 0xff);
    saveg_write8((leveltime >> 8) & 0xff);
    saveg_write8(leveltime & 0xff);
}

// 
// Read the header for a savegame
//

boolean P_ReadSaveGameHeader(void)
{
    int	 i; 
    byte a, b, c; 
    char vcheck[VERSIONSIZE]; 
    char read_vcheck[VERSIONSIZE];

    // skip the description field 
    /*
    for (i=0; i<SAVESTRINGSIZE; ++i)
        saveg_read8();
    */
    
    for (i=0; i<VERSIONSIZE; ++i)
        read_vcheck[i] = saveg_read8();

    memset (vcheck,0,sizeof(vcheck)); 
    sprintf (vcheck,"ver %i",STRIFE_VERSION); 
    if (strcmp(read_vcheck, vcheck) != 0)
        return false;                       // bad version 

    gameskill = saveg_read8();

    // [STRIFE] This info is implicit in the file being read.
    //gameepisode = saveg_read8();
    //gamemap = saveg_read8();

    for (i=0 ; i<MAXPLAYERS ; i++) 
        playeringame[i] = saveg_read8();

    // get the times 
    a = saveg_read8();
    b = saveg_read8();
    c = saveg_read8();
    leveltime = (a<<16) + (b<<8) + c; 

    return true;
}

//
// Read the end of file marker.  Returns true if read successfully.
// 

boolean P_ReadSaveGameEOF(void)
{
    int value;

    value = saveg_read8();

    return value == SAVEGAME_EOF;
}

//
// Write the end of file marker
//

void P_WriteSaveGameEOF(void)
{
    saveg_write8(SAVEGAME_EOF);
}

//
// P_ArchivePlayers
//
// [STRIFE] Verified unmodified.
//
void P_ArchivePlayers (void)
{
    int         i;

    for (i=0 ; i<MAXPLAYERS ; i++)
    {
        if (!playeringame[i])
            continue;

        saveg_write_pad();

        saveg_write_player_t(&players[i]);
    }
}



//
// P_UnArchivePlayers
//
// [STRIFE] Verified unmodified.
//
void P_UnArchivePlayers (boolean userload)
{
    int         i;

    for (i=0 ; i<MAXPLAYERS ; i++)
    {
        player_t dummy;

        if (!playeringame[i])
            continue;

        saveg_read_pad();

        // haleyjd [STRIFE]: not exactly how vanilla did it, but this is
        // necessary because of Choco's change to the savegame code which
        // reads it directly from file. When not a userload, all the player_t
        // data loaded from the save is thrown away.
        if(userload)
        {
            saveg_read_player_t(&players[i]);
            players[i].mo = NULL;
        }
        else
            saveg_read_player_t(&dummy);

        // will be set when unarc thinker
        players[i].message = NULL;
        players[i].attacker = NULL;
    }
}


//
// P_ArchiveWorld
//
// haleyjd 09/28/10: [STRIFE] Minor modifications.
//
void P_ArchiveWorld (void)
{
    int                 i;
    int                 j;
    sector_t*           sec;
    line_t*             li;
    side_t*             si;
    
    // do sectors
    for (i=0, sec = sectors ; i<numsectors ; i++,sec++)
    {
        saveg_write16(sec->floorheight >> FRACBITS);
        saveg_write16(sec->ceilingheight >> FRACBITS);
        saveg_write16(sec->floorpic);
        //saveg_write16(sec->ceilingpic); [STRIFE] not saved.
        saveg_write16(sec->lightlevel);
        saveg_write16(sec->special);            // needed?
        //saveg_write16(sec->tag);                // needed? [STRIFE] not saved.
    }

    
    // do lines
    for (i=0, li = lines ; i<numlines ; i++,li++)
    {
        saveg_write16(li->flags);
        saveg_write16(li->special);
        //saveg_write16(li->tag); [STRIFE] not saved.
        for (j=0 ; j<2 ; j++)
        {
            if (li->sidenum[j] == -1)
                continue;

            si = &sides[li->sidenum[j]];

            // [STRIFE] offsets not saved.
            //saveg_write16(si->textureoffset >> FRACBITS);
            //saveg_write16(si->rowoffset >> FRACBITS);
            saveg_write16(si->toptexture);
            saveg_write16(si->bottomtexture);
            saveg_write16(si->midtexture);
        }
    }
}



//
// P_UnArchiveWorld
//
void P_UnArchiveWorld (void)
{
    int			i;
    int			j;
    sector_t*		sec;
    line_t*		li;
    side_t*		si;
    
    // do sectors
    for (i=0, sec = sectors ; i<numsectors ; i++,sec++)
    {
        sec->floorheight = saveg_read16() << FRACBITS;
        sec->ceilingheight = saveg_read16() << FRACBITS;
        sec->floorpic = saveg_read16();
        //sec->ceilingpic = saveg_read16(); [STRIFE] not saved
        sec->lightlevel = saveg_read16();
        sec->special = saveg_read16();          // needed?
        //sec->tag = saveg_read16();              // needed? [STRIFE] not saved
        sec->specialdata = 0;
        sec->soundtarget = 0;
    }
    
    // do lines
    for (i=0, li = lines ; i<numlines ; i++,li++)
    {
        li->flags = saveg_read16();
        li->special = saveg_read16();
        //li->tag = saveg_read16(); [STRIFE] not saved
        for (j=0 ; j<2 ; j++)
        {
            if (li->sidenum[j] == -1)
                continue;
            si = &sides[li->sidenum[j]];
            // [STRIFE] offsets not saved.
            //si->textureoffset = saveg_read16() << FRACBITS;
            //si->rowoffset = saveg_read16() << FRACBITS;
            si->toptexture = saveg_read16();
            si->bottomtexture = saveg_read16();
            si->midtexture = saveg_read16();
        }
    }
}





//
// Thinkers
//
typedef enum
{
    tc_end,
    tc_mobj

} thinkerclass_t;


//
// P_ArchiveThinkers
//
// [STRIFE] Verified unmodified.
//
void P_ArchiveThinkers (void)
{
    thinker_t*          th;

    // save off the current thinkers
    for (th = thinkercap.next ; th != &thinkercap ; th=th->next)
    {
        if (th->function.acp1 == (actionf_p1)P_MobjThinker)
        {
            saveg_write8(tc_mobj);
            saveg_write_pad();
            saveg_write_mobj_t((mobj_t *) th);

            continue;
        }

        // haleyjd: This may seem mysterious but in the DOOM prebeta, 
        // different types of things used different thinker functions. 
        // Those would have all been handled here and this message is 
        // probably a relic of that old system, not to mention the odd
        // name of this function, and use of an enumeration with only
        // two values in it.

        // I_Error ("P_ArchiveThinkers: Unknown thinker function");
    }

    // add a terminating marker
    saveg_write8(tc_end);
}



//
// P_UnArchiveThinkers
//
void P_UnArchiveThinkers (void)
{
    byte                tclass;
    thinker_t*          currentthinker;
    thinker_t*          next;
    mobj_t*             mobj;
    
    // remove all the current thinkers
    currentthinker = thinkercap.next;
    while (currentthinker != &thinkercap)
    {
        next = currentthinker->next;

        if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
            P_RemoveMobj ((mobj_t *)currentthinker);
        else
            Z_Free (currentthinker);

        currentthinker = next;
    }
    P_InitThinkers ();
    
    // read in saved thinkers
    while (1)
    {
        tclass = saveg_read8();
        switch (tclass)
        {
        case tc_end:
            return; 	// end of list

        case tc_mobj:
            saveg_read_pad();
            mobj = Z_Malloc (sizeof(*mobj), PU_LEVEL, NULL);
            saveg_read_mobj_t(mobj);

            // haleyjd 09/29/10: Strife sets the targets of non-allied creatures
            // who had a non-NULL target at save time to players[0].mo so that
            // they won't fall back asleep.
            //
            // BUG: As the player may not have been spawned yet, we could be
            // setting monsters' targets to the mobj which was spawned by 
            // P_SetupLevel and then removed just above. Due to a subtle glitch
            // in the DOOM engine whereby all things removed in this function
            // are leaked until the next time P_SetupLevel is called, this is a
            // safe operation - the call to P_InitThinkers above stops any of
            // the objects removed, including the player's previous body, from
            // being passed to Z_Free. One glitch relying on another!

            if(mobj->target != NULL && (mobj->flags & MF_ALLY) != MF_ALLY)
                mobj->target = players[0].mo;
            else
                mobj->target = NULL;

            // WARNING! Strife does not seem to set tracer! I am leaving it be
            // for now because so far no crashes have been observed, and failing
            // to set this here will almost certainly crash Choco.
            mobj->tracer = NULL;
            P_SetThingPosition (mobj);
            mobj->info = &mobjinfo[mobj->type];
            // [STRIFE]: doesn't set these
            //mobj->floorz = mobj->subsector->sector->floorheight;
            //mobj->ceilingz = mobj->subsector->sector->ceilingheight;
            mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
            P_AddThinker (&mobj->thinker);
            break;

        default:
            I_Error ("Unknown tclass %i in savegame",tclass);
        }
    }
}


//
// P_ArchiveSpecials
//
enum
{
    tc_ceiling,
    tc_door,
    tc_floor,
    tc_plat,
    tc_flash,
    tc_strobe,
    tc_glow,
    tc_slidingdoor, // [STRIFE]
    tc_endspecials

} specials_e;



//
// Things to handle:
//
// T_MoveCeiling, (ceiling_t: sector_t * swizzle), - active list
// T_VerticalDoor, (vldoor_t: sector_t * swizzle),
// T_SlidingDoor, (slidedoor_t: sector_t *, line_t * x 2 swizzle) [STRIFE]
// T_MoveFloor, (floormove_t: sector_t * swizzle),
// T_LightFlash, (lightflash_t: sector_t * swizzle),
// T_StrobeFlash, (strobe_t: sector_t *),
// T_Glow, (glow_t: sector_t *),
// T_PlatRaise, (plat_t: sector_t *), - active list
//
void P_ArchiveSpecials (void)
{
    thinker_t*          th;
    int                 i;

    // save off the current thinkers
    for (th = thinkercap.next ; th != &thinkercap ; th=th->next)
    {
        if (th->function.acv == (actionf_v)NULL)
        {
            for (i = 0; i < MAXCEILINGS;i++)
                if (activeceilings[i] == (ceiling_t *)th)
                    break;

            if (i<MAXCEILINGS)
            {
                saveg_write8(tc_ceiling);
                saveg_write_pad();
                saveg_write_ceiling_t((ceiling_t *) th);
            }
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_MoveCeiling)
        {
            saveg_write8(tc_ceiling);
            saveg_write_pad();
            saveg_write_ceiling_t((ceiling_t *) th);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_VerticalDoor)
        {
            saveg_write8(tc_door);
            saveg_write_pad();
            saveg_write_vldoor_t((vldoor_t *) th);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_SlidingDoor)
        {
            saveg_write8(tc_slidingdoor);
            saveg_write_pad();
            saveg_write_slidedoor_t((slidedoor_t *)th);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_MoveFloor)
        {
            saveg_write8(tc_floor);
            saveg_write_pad();
            saveg_write_floormove_t((floormove_t *) th);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_PlatRaise)
        {
            saveg_write8(tc_plat);
            saveg_write_pad();
            saveg_write_plat_t((plat_t *) th);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_LightFlash)
        {
            saveg_write8(tc_flash);
            saveg_write_pad();
            saveg_write_lightflash_t((lightflash_t *) th);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_StrobeFlash)
        {
            saveg_write8(tc_strobe);
            saveg_write_pad();
            saveg_write_strobe_t((strobe_t *) th);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_Glow)
        {
            saveg_write8(tc_glow);
            saveg_write_pad();
            saveg_write_glow_t((glow_t *) th);
            continue;
        }
    }

    // add a terminating marker
    saveg_write8(tc_endspecials);
}


//
// P_UnArchiveSpecials
//
void P_UnArchiveSpecials (void)
{
    byte                tclass;
    ceiling_t*          ceiling;
    vldoor_t*           door;
    slidedoor_t*        slidedoor; // haleyjd [STRIFE]
    floormove_t*        floor;
    plat_t*             plat;
    lightflash_t*       flash;
    strobe_t*           strobe;
    glow_t*             glow;


    // read in saved thinkers
    while (1)
    {
        tclass = saveg_read8();

        switch (tclass)
        {
        case tc_endspecials:
            return;	// end of list

        case tc_ceiling:
            saveg_read_pad();
            ceiling = Z_Malloc (sizeof(*ceiling), PU_LEVEL, NULL);
            saveg_read_ceiling_t(ceiling);
            ceiling->sector->specialdata = ceiling;

            if (ceiling->thinker.function.acp1)
                ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling;

            P_AddThinker (&ceiling->thinker);
            P_AddActiveCeiling(ceiling);
            break;

        case tc_door:
            saveg_read_pad();
            door = Z_Malloc (sizeof(*door), PU_LEVEL, NULL);
            saveg_read_vldoor_t(door);
            door->sector->specialdata = door;
            door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor;
            P_AddThinker (&door->thinker);
            break;

        case tc_slidingdoor:
            // haleyjd 09/29/10: [STRIFE] New thinker type for sliding doors
            saveg_read_pad();
            slidedoor = Z_Malloc(sizeof(*slidedoor), PU_LEVEL, NULL);
            saveg_read_slidedoor_t(slidedoor);
            slidedoor->frontsector->specialdata = slidedoor;
            slidedoor->thinker.function.acp1 = (actionf_p1)T_SlidingDoor;
            P_AddThinker(&slidedoor->thinker);
            break;

        case tc_floor:
            saveg_read_pad();
            floor = Z_Malloc (sizeof(*floor), PU_LEVEL, NULL);
            saveg_read_floormove_t(floor);
            floor->sector->specialdata = floor;
            floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor;
            P_AddThinker (&floor->thinker);
            break;

        case tc_plat:
            saveg_read_pad();
            plat = Z_Malloc (sizeof(*plat), PU_LEVEL, NULL);
            saveg_read_plat_t(plat);
            plat->sector->specialdata = plat;

            if (plat->thinker.function.acp1)
                plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise;

            P_AddThinker (&plat->thinker);
            P_AddActivePlat(plat);
            break;

        case tc_flash:
            saveg_read_pad();
            flash = Z_Malloc (sizeof(*flash), PU_LEVEL, NULL);
            saveg_read_lightflash_t(flash);
            flash->thinker.function.acp1 = (actionf_p1)T_LightFlash;
            P_AddThinker (&flash->thinker);
            break;

        case tc_strobe:
            saveg_read_pad();
            strobe = Z_Malloc (sizeof(*strobe), PU_LEVEL, NULL);
            saveg_read_strobe_t(strobe);
            strobe->thinker.function.acp1 = (actionf_p1)T_StrobeFlash;
            P_AddThinker (&strobe->thinker);
            break;

        case tc_glow:
            saveg_read_pad();
            glow = Z_Malloc (sizeof(*glow), PU_LEVEL, NULL);
            saveg_read_glow_t(glow);
            glow->thinker.function.acp1 = (actionf_p1)T_Glow;
            P_AddThinker (&glow->thinker);
            break;

        default:
            I_Error ("P_UnarchiveSpecials:Unknown tclass %i "
                     "in savegame",tclass);
        }
    }
}