// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file p_saveg.c /// \brief Archiving: SaveGame I/O #include "doomdef.h" #include "byteptr.h" #include "d_main.h" #include "doomstat.h" #include "g_game.h" #include "m_random.h" #include "m_misc.h" #include "p_local.h" #include "p_setup.h" #include "p_saveg.h" #include "r_data.h" #include "r_fps.h" #include "r_textures.h" #include "r_things.h" #include "r_skins.h" #include "r_state.h" #include "w_wad.h" #include "y_inter.h" #include "z_zone.h" #include "r_main.h" #include "r_sky.h" #include "p_polyobj.h" #include "lua_script.h" #include "p_slopes.h" #include "hu_stuff.h" savedata_t savedata; #define ALLOC_SIZE(p, z) \ if ((p)->pos + (z) > (p)->size) \ { \ while ((p)->pos + (z) > (p)->size) \ (p)->size <<= 1; \ (p)->buf = realloc((p)->buf, (p)->size); \ } void P_WriteUINT8(save_t *p, UINT8 v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteSINT8(save_t *p, SINT8 v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteUINT16(save_t *p, UINT16 v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteINT16(save_t *p, INT16 v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteUINT32(save_t *p, UINT32 v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteINT32(save_t *p, INT32 v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteChar(save_t *p, char v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteFixed(save_t *p, fixed_t v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteAngle(save_t *p, angle_t v) { ALLOC_SIZE(p, sizeof(v)); memcpy(&p->buf[p->pos], &v, sizeof(v)); p->pos += sizeof(v); } void P_WriteStringN(save_t *p, char const *s, size_t n) { size_t i; for (i = 0; i < n && s[i] != '\0'; i++) P_WriteChar(p, s[i]); if (i < n) P_WriteChar(p, '\0'); } void P_WriteStringL(save_t *p, char const *s, size_t n) { size_t i; for (i = 0; i < n - 1 && s[i] != '\0'; i++) P_WriteChar(p, s[i]); P_WriteChar(p, '\0'); } void P_WriteString(save_t *p, char const *s) { size_t i; for (i = 0; s[i] != '\0'; i++) P_WriteChar(p, s[i]); P_WriteChar(p, '\0'); } void P_WriteMem(save_t *p, void const *s, size_t n) { ALLOC_SIZE(p, n); memcpy(&p->buf[p->pos], s, n); p->pos += n; } void P_SkipStringN(save_t *p, size_t n) { size_t i; for (i = 0; p->pos < p->size && i < n && P_ReadChar(p) != '\0'; i++); } void P_SkipStringL(save_t *p, size_t n) { P_SkipStringN(p, n); } void P_SkipString(save_t *p) { P_SkipStringN(p, SIZE_MAX); } UINT8 P_ReadUINT8(save_t *p) { UINT8 v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } SINT8 P_ReadSINT8(save_t *p) { SINT8 v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } UINT16 P_ReadUINT16(save_t *p) { UINT16 v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } INT16 P_ReadINT16(save_t *p) { INT16 v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } UINT32 P_ReadUINT32(save_t *p) { UINT32 v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } INT32 P_ReadINT32(save_t *p) { INT32 v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } char P_ReadChar(save_t *p) { char v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } fixed_t P_ReadFixed(save_t *p) { fixed_t v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } angle_t P_ReadAngle(save_t *p) { angle_t v; if (p->pos + sizeof(v) > p->size) return 0; memcpy(&v, &p->buf[p->pos], sizeof(v)); p->pos += sizeof(v); return v; } void P_ReadStringN(save_t *p, char *s, size_t n) { size_t i; for (i = 0; p->pos < p->size && i < n && (s[i] = P_ReadChar(p)) != '\0'; i++); s[i] = '\0'; } void P_ReadStringL(save_t *p, char *s, size_t n) { P_ReadStringN(p, s, n - 1); } void P_ReadString(save_t *p, char *s) { P_ReadStringN(p, s, SIZE_MAX); } void P_ReadMem(save_t *p, void *s, size_t n) { if (p->pos + n > p->size) return; memcpy(s, &p->buf[p->pos], n); p->pos += n; } // Block UINT32s to attempt to ensure that the correct data is // being sent and received #define ARCHIVEBLOCK_MISC 0x7FEEDEED #define ARCHIVEBLOCK_PLAYERS 0x7F448008 #define ARCHIVEBLOCK_WORLD 0x7F8C08C0 #define ARCHIVEBLOCK_POBJS 0x7F928546 #define ARCHIVEBLOCK_THINKERS 0x7F37037C #define ARCHIVEBLOCK_SPECIALS 0x7F228378 #define ARCHIVEBLOCK_EMBLEMS 0x7F4A5445 #define ARCHIVEBLOCK_SECPORTALS 0x7FBE34C9 // Note: This cannot be bigger // than an UINT16 typedef enum { CAPSULE = 0x04, AWAYVIEW = 0x08, FIRSTAXIS = 0x10, SECONDAXIS = 0x20, FOLLOW = 0x40, DRONE = 0x80, } player_saveflags; static inline void P_ArchivePlayer(save_t *save_p) { const player_t *player = &players[consoleplayer]; SINT8 pllives = player->lives; if (pllives < startinglivesbalance[numgameovers]) // Bump up to 3 lives if the player pllives = startinglivesbalance[numgameovers]; // has less than that. #ifdef NEWSKINSAVES // Write a specific value into the old skininfo location. // If we read something other than this, it's an older save file that used skin numbers. P_WriteUINT16(save_p, NEWSKINSAVES); #endif // Write skin names, so that loading skins in different orders // doesn't change who the save file is for! P_WriteStringN(save_p, skins[player->skin]->name, SKINNAMESIZE); if (botskin != 0) { P_WriteStringN(save_p, skins[botskin-1]->name, SKINNAMESIZE); } else { P_WriteStringN(save_p, "\0", SKINNAMESIZE); } P_WriteUINT8(save_p, numgameovers); P_WriteSINT8(save_p, pllives); P_WriteUINT32(save_p, player->score); P_WriteINT32(save_p, player->continues); } static inline void P_UnArchivePlayer(save_t *save_p) { #ifdef NEWSKINSAVES INT16 backwardsCompat = P_ReadUINT16(save_p); if (backwardsCompat != NEWSKINSAVES) { // This is an older save file, which used direct skin numbers. savedata.skin = backwardsCompat & ((1<<5) - 1); savedata.botskin = backwardsCompat >> 5; } else #endif { char ourSkinName[SKINNAMESIZE+1]; char botSkinName[SKINNAMESIZE+1]; P_ReadStringN(save_p, ourSkinName, SKINNAMESIZE); savedata.skin = R_SkinAvailable(ourSkinName); P_ReadStringN(save_p, botSkinName, SKINNAMESIZE); savedata.botskin = R_SkinAvailable(botSkinName) + 1; } savedata.numgameovers = P_ReadUINT8(save_p); savedata.lives = P_ReadSINT8(save_p); savedata.score = P_ReadUINT32(save_p); savedata.continues = P_ReadINT32(save_p); } static void P_NetArchivePlayers(save_t *save_p) { INT32 i, j; UINT16 flags; // size_t q; P_WriteUINT32(save_p, ARCHIVEBLOCK_PLAYERS); for (i = 0; i < MAXPLAYERS; i++) { P_WriteSINT8(save_p, (SINT8)adminplayers[i]); if (!playeringame[i]) continue; flags = 0; // no longer send ticcmds P_WriteStringN(save_p, player_names[i], MAXPLAYERNAME); P_WriteINT16(save_p, players[i].angleturn); P_WriteINT16(save_p, players[i].oldrelangleturn); P_WriteAngle(save_p, players[i].aiming); P_WriteAngle(save_p, players[i].drawangle); P_WriteAngle(save_p, players[i].viewrollangle); P_WriteAngle(save_p, players[i].awayviewaiming); P_WriteINT32(save_p, players[i].awayviewtics); P_WriteINT16(save_p, players[i].rings); P_WriteINT16(save_p, players[i].spheres); P_WriteSINT8(save_p, players[i].pity); P_WriteINT32(save_p, players[i].currentweapon); P_WriteINT32(save_p, players[i].ringweapons); P_WriteUINT16(save_p, players[i].ammoremoval); P_WriteUINT32(save_p, players[i].ammoremovaltimer); P_WriteINT32(save_p, players[i].ammoremovaltimer); for (j = 0; j < NUMPOWERS; j++) P_WriteUINT16(save_p, players[i].powers[j]); P_WriteUINT8(save_p, players[i].playerstate); P_WriteUINT32(save_p, players[i].pflags); P_WriteUINT8(save_p, players[i].panim); P_WriteUINT8(save_p, players[i].stronganim); P_WriteUINT8(save_p, players[i].spectator); P_WriteUINT8(save_p, players[i].muted); P_WriteUINT16(save_p, players[i].flashpal); P_WriteUINT16(save_p, players[i].flashcount); P_WriteUINT16(save_p, players[i].skincolor); P_WriteINT32(save_p, players[i].skin); P_WriteUINT32(save_p, players[i].availabilities); P_WriteUINT32(save_p, players[i].score); P_WriteUINT32(save_p, players[i].recordscore); P_WriteFixed(save_p, players[i].dashspeed); P_WriteSINT8(save_p, players[i].lives); P_WriteSINT8(save_p, players[i].continues); P_WriteSINT8(save_p, players[i].xtralife); P_WriteUINT8(save_p, players[i].gotcontinue); P_WriteFixed(save_p, players[i].speed); P_WriteUINT8(save_p, players[i].secondjump); P_WriteUINT8(save_p, players[i].fly1); P_WriteUINT8(save_p, players[i].scoreadd); P_WriteUINT32(save_p, players[i].glidetime); P_WriteUINT8(save_p, players[i].climbing); P_WriteINT32(save_p, players[i].deadtimer); P_WriteUINT32(save_p, players[i].exiting); P_WriteUINT8(save_p, players[i].homing); P_WriteUINT32(save_p, players[i].dashmode); P_WriteUINT32(save_p, players[i].skidtime); ////////// // Bots // ////////// P_WriteUINT8(save_p, players[i].bot); P_WriteUINT8(save_p, players[i].botmem.lastForward); P_WriteUINT8(save_p, players[i].botmem.lastBlocked); P_WriteUINT8(save_p, players[i].botmem.catchup_tics); P_WriteUINT8(save_p, players[i].botmem.thinkstate); P_WriteUINT8(save_p, players[i].removing); P_WriteUINT8(save_p, players[i].blocked); P_WriteUINT16(save_p, players[i].lastbuttons); //////////////////////////// // Conveyor Belt Movement // //////////////////////////// P_WriteFixed(save_p, players[i].cmomx); // Conveyor momx P_WriteFixed(save_p, players[i].cmomy); // Conveyor momy P_WriteFixed(save_p, players[i].rmomx); // "Real" momx (momx - cmomx) P_WriteFixed(save_p, players[i].rmomy); // "Real" momy (momy - cmomy) ///////////////////// // Race Mode Stuff // ///////////////////// P_WriteINT16(save_p, players[i].numboxes); P_WriteINT16(save_p, players[i].totalring); P_WriteUINT32(save_p, players[i].realtime); P_WriteUINT8(save_p, players[i].laps); //////////////////// // CTF Mode Stuff // //////////////////// P_WriteINT32(save_p, players[i].ctfteam); P_WriteUINT16(save_p, players[i].gotflag); P_WriteINT32(save_p, players[i].weapondelay); P_WriteINT32(save_p, players[i].tossdelay); P_WriteUINT32(save_p, players[i].starposttime); P_WriteINT16(save_p, players[i].starpostx); P_WriteINT16(save_p, players[i].starposty); P_WriteINT16(save_p, players[i].starpostz); P_WriteINT32(save_p, players[i].starpostnum); P_WriteAngle(save_p, players[i].starpostangle); P_WriteFixed(save_p, players[i].starpostscale); P_WriteAngle(save_p, players[i].angle_pos); P_WriteAngle(save_p, players[i].old_angle_pos); P_WriteINT32(save_p, players[i].flyangle); P_WriteUINT32(save_p, players[i].drilltimer); P_WriteINT32(save_p, players[i].linkcount); P_WriteUINT32(save_p, players[i].linktimer); P_WriteINT32(save_p, players[i].anotherflyangle); P_WriteUINT32(save_p, players[i].nightstime); P_WriteUINT32(save_p, players[i].bumpertime); P_WriteINT32(save_p, players[i].drillmeter); P_WriteUINT8(save_p, players[i].drilldelay); P_WriteUINT8(save_p, players[i].bonustime); P_WriteFixed(save_p, players[i].oldscale); P_WriteUINT8(save_p, players[i].mare); P_WriteUINT8(save_p, players[i].marelap); P_WriteUINT8(save_p, players[i].marebonuslap); P_WriteUINT32(save_p, players[i].marebegunat); P_WriteUINT32(save_p, players[i].lastmaretime); P_WriteUINT32(save_p, players[i].startedtime); P_WriteUINT32(save_p, players[i].finishedtime); P_WriteUINT32(save_p, players[i].lapbegunat); P_WriteUINT32(save_p, players[i].lapstartedtime); P_WriteINT16(save_p, players[i].finishedspheres); P_WriteINT16(save_p, players[i].finishedrings); P_WriteUINT32(save_p, players[i].marescore); P_WriteUINT32(save_p, players[i].lastmarescore); P_WriteUINT32(save_p, players[i].totalmarescore); P_WriteUINT8(save_p, players[i].lastmare); P_WriteUINT8(save_p, players[i].lastmarelap); P_WriteUINT8(save_p, players[i].lastmarebonuslap); P_WriteUINT8(save_p, players[i].totalmarelap); P_WriteUINT8(save_p, players[i].totalmarebonuslap); P_WriteINT32(save_p, players[i].maxlink); P_WriteUINT8(save_p, players[i].texttimer); P_WriteUINT8(save_p, players[i].textvar); if (players[i].capsule) flags |= CAPSULE; if (players[i].awayviewmobj) flags |= AWAYVIEW; if (players[i].axis1) flags |= FIRSTAXIS; if (players[i].axis2) flags |= SECONDAXIS; if (players[i].followmobj) flags |= FOLLOW; if (players[i].drone) flags |= DRONE; P_WriteINT16(save_p, players[i].lastsidehit); P_WriteINT16(save_p, players[i].lastlinehit); P_WriteUINT32(save_p, players[i].losstime); P_WriteUINT8(save_p, players[i].timeshit); P_WriteINT32(save_p, players[i].onconveyor); P_WriteUINT32(save_p, players[i].jointime); P_WriteUINT32(save_p, players[i].quittime); P_WriteUINT16(save_p, flags); if (flags & CAPSULE) P_WriteUINT32(save_p, players[i].capsule->mobjnum); if (flags & FIRSTAXIS) P_WriteUINT32(save_p, players[i].axis1->mobjnum); if (flags & SECONDAXIS) P_WriteUINT32(save_p, players[i].axis2->mobjnum); if (flags & AWAYVIEW) P_WriteUINT32(save_p, players[i].awayviewmobj->mobjnum); if (flags & FOLLOW) P_WriteUINT32(save_p, players[i].followmobj->mobjnum); if (flags & DRONE) P_WriteUINT32(save_p, players[i].drone->mobjnum); P_WriteFixed(save_p, players[i].camerascale); P_WriteFixed(save_p, players[i].shieldscale); P_WriteUINT8(save_p, players[i].charability); P_WriteUINT8(save_p, players[i].charability2); P_WriteUINT32(save_p, players[i].charflags); P_WriteUINT32(save_p, (UINT32)players[i].thokitem); P_WriteUINT32(save_p, (UINT32)players[i].spinitem); P_WriteUINT32(save_p, (UINT32)players[i].revitem); P_WriteUINT32(save_p, (UINT32)players[i].followitem); P_WriteFixed(save_p, players[i].actionspd); P_WriteFixed(save_p, players[i].mindash); P_WriteFixed(save_p, players[i].maxdash); P_WriteFixed(save_p, players[i].normalspeed); P_WriteFixed(save_p, players[i].runspeed); P_WriteUINT8(save_p, players[i].thrustfactor); P_WriteUINT8(save_p, players[i].accelstart); P_WriteUINT8(save_p, players[i].acceleration); P_WriteFixed(save_p, players[i].jumpfactor); P_WriteFixed(save_p, players[i].height); P_WriteFixed(save_p, players[i].spinheight); } } static void P_NetUnArchivePlayers(save_t *save_p) { INT32 i, j; UINT16 flags; if (P_ReadUINT32(save_p) != ARCHIVEBLOCK_PLAYERS) I_Error("Bad $$$.sav at archive block Players"); for (i = 0; i < MAXPLAYERS; i++) { adminplayers[i] = (INT32)P_ReadSINT8(save_p); // Do NOT memset player struct to 0 // other areas may initialize data elsewhere //memset(&players[i], 0, sizeof (player_t)); if (!playeringame[i]) continue; // NOTE: sending tics should (hopefully) no longer be necessary P_ReadStringN(save_p, player_names[i], MAXPLAYERNAME); players[i].angleturn = P_ReadINT16(save_p); players[i].oldrelangleturn = P_ReadINT16(save_p); players[i].aiming = P_ReadAngle(save_p); players[i].drawangle = P_ReadAngle(save_p); players[i].viewrollangle = P_ReadAngle(save_p); players[i].awayviewaiming = P_ReadAngle(save_p); players[i].awayviewtics = P_ReadINT32(save_p); players[i].rings = P_ReadINT16(save_p); players[i].spheres = P_ReadINT16(save_p); players[i].pity = P_ReadSINT8(save_p); players[i].currentweapon = P_ReadINT32(save_p); players[i].ringweapons = P_ReadINT32(save_p); players[i].ammoremoval = P_ReadUINT16(save_p); players[i].ammoremovaltimer = P_ReadUINT32(save_p); players[i].ammoremovalweapon = P_ReadINT32(save_p); for (j = 0; j < NUMPOWERS; j++) players[i].powers[j] = P_ReadUINT16(save_p); players[i].playerstate = P_ReadUINT8(save_p); players[i].pflags = P_ReadUINT32(save_p); players[i].panim = P_ReadUINT8(save_p); players[i].stronganim = P_ReadUINT8(save_p); players[i].spectator = P_ReadUINT8(save_p); players[i].muted = P_ReadUINT8(save_p); players[i].flashpal = P_ReadUINT16(save_p); players[i].flashcount = P_ReadUINT16(save_p); players[i].skincolor = P_ReadUINT16(save_p); players[i].skin = P_ReadINT32(save_p); players[i].availabilities = P_ReadUINT32(save_p); players[i].score = P_ReadUINT32(save_p); players[i].recordscore = P_ReadUINT32(save_p); players[i].dashspeed = P_ReadFixed(save_p); // dashing speed players[i].lives = P_ReadSINT8(save_p); players[i].continues = P_ReadSINT8(save_p); // continues that player has acquired players[i].xtralife = P_ReadSINT8(save_p); // Ring Extra Life counter players[i].gotcontinue = P_ReadUINT8(save_p); // got continue from stage players[i].speed = P_ReadFixed(save_p); // Player's speed (distance formula of MOMX and MOMY values) players[i].secondjump = P_ReadUINT8(save_p); players[i].fly1 = P_ReadUINT8(save_p); // Tails flying players[i].scoreadd = P_ReadUINT8(save_p); // Used for multiple enemy attack bonus players[i].glidetime = P_ReadUINT32(save_p); // Glide counter for thrust players[i].climbing = P_ReadUINT8(save_p); // Climbing on the wall players[i].deadtimer = P_ReadINT32(save_p); // End game if game over lasts too long players[i].exiting = P_ReadUINT32(save_p); // Exitlevel timer players[i].homing = P_ReadUINT8(save_p); // Are you homing? players[i].dashmode = P_ReadUINT32(save_p); // counter for dashmode ability players[i].skidtime = P_ReadUINT32(save_p); // Skid timer ////////// // Bots // ////////// players[i].bot = P_ReadUINT8(save_p); players[i].botmem.lastForward = P_ReadUINT8(save_p); players[i].botmem.lastBlocked = P_ReadUINT8(save_p); players[i].botmem.catchup_tics = P_ReadUINT8(save_p); players[i].botmem.thinkstate = P_ReadUINT8(save_p); players[i].removing = P_ReadUINT8(save_p); players[i].blocked = P_ReadUINT8(save_p); players[i].lastbuttons = P_ReadUINT16(save_p); //////////////////////////// // Conveyor Belt Movement // //////////////////////////// players[i].cmomx = P_ReadFixed(save_p); // Conveyor momx players[i].cmomy = P_ReadFixed(save_p); // Conveyor momy players[i].rmomx = P_ReadFixed(save_p); // "Real" momx (momx - cmomx) players[i].rmomy = P_ReadFixed(save_p); // "Real" momy (momy - cmomy) ///////////////////// // Race Mode Stuff // ///////////////////// players[i].numboxes = P_ReadINT16(save_p); // Number of item boxes obtained for Race Mode players[i].totalring = P_ReadINT16(save_p); // Total number of rings obtained for Race Mode players[i].realtime = P_ReadUINT32(save_p); // integer replacement for leveltime players[i].laps = P_ReadUINT8(save_p); // Number of laps (optional) //////////////////// // CTF Mode Stuff // //////////////////// players[i].ctfteam = P_ReadINT32(save_p); // 1 == Red, 2 == Blue players[i].gotflag = P_ReadUINT16(save_p); // 1 == Red, 2 == Blue Do you have the flag? players[i].weapondelay = P_ReadINT32(save_p); players[i].tossdelay = P_ReadINT32(save_p); players[i].starposttime = P_ReadUINT32(save_p); players[i].starpostx = P_ReadINT16(save_p); players[i].starposty = P_ReadINT16(save_p); players[i].starpostz = P_ReadINT16(save_p); players[i].starpostnum = P_ReadINT32(save_p); players[i].starpostangle = P_ReadAngle(save_p); players[i].starpostscale = P_ReadFixed(save_p); players[i].angle_pos = P_ReadAngle(save_p); players[i].old_angle_pos = P_ReadAngle(save_p); players[i].flyangle = P_ReadINT32(save_p); players[i].drilltimer = P_ReadUINT32(save_p); players[i].linkcount = P_ReadINT32(save_p); players[i].linktimer = P_ReadUINT32(save_p); players[i].anotherflyangle = P_ReadINT32(save_p); players[i].nightstime = P_ReadUINT32(save_p); players[i].bumpertime = P_ReadUINT32(save_p); players[i].drillmeter = P_ReadINT32(save_p); players[i].drilldelay = P_ReadUINT8(save_p); players[i].bonustime = (boolean)P_ReadUINT8(save_p); players[i].oldscale = P_ReadFixed(save_p); players[i].mare = P_ReadUINT8(save_p); players[i].marelap = P_ReadUINT8(save_p); players[i].marebonuslap = P_ReadUINT8(save_p); players[i].marebegunat = P_ReadUINT32(save_p); players[i].lastmaretime = P_ReadUINT32(save_p); players[i].startedtime = P_ReadUINT32(save_p); players[i].finishedtime = P_ReadUINT32(save_p); players[i].lapbegunat = P_ReadUINT32(save_p); players[i].lapstartedtime = P_ReadUINT32(save_p); players[i].finishedspheres = P_ReadINT16(save_p); players[i].finishedrings = P_ReadINT16(save_p); players[i].marescore = P_ReadUINT32(save_p); players[i].lastmarescore = P_ReadUINT32(save_p); players[i].totalmarescore = P_ReadUINT32(save_p); players[i].lastmare = P_ReadUINT8(save_p); players[i].lastmarelap = P_ReadUINT8(save_p); players[i].lastmarebonuslap = P_ReadUINT8(save_p); players[i].totalmarelap = P_ReadUINT8(save_p); players[i].totalmarebonuslap = P_ReadUINT8(save_p); players[i].maxlink = P_ReadINT32(save_p); players[i].texttimer = P_ReadUINT8(save_p); players[i].textvar = P_ReadUINT8(save_p); players[i].lastsidehit = P_ReadINT16(save_p); players[i].lastlinehit = P_ReadINT16(save_p); players[i].losstime = P_ReadUINT32(save_p); players[i].timeshit = P_ReadUINT8(save_p); players[i].onconveyor = P_ReadINT32(save_p); players[i].jointime = P_ReadUINT32(save_p); players[i].quittime = P_ReadUINT32(save_p); flags = P_ReadUINT16(save_p); if (flags & CAPSULE) players[i].capsule = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (flags & FIRSTAXIS) players[i].axis1 = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (flags & SECONDAXIS) players[i].axis2 = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (flags & AWAYVIEW) players[i].awayviewmobj = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (flags & FOLLOW) players[i].followmobj = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (flags & DRONE) players[i].drone = (mobj_t *)(size_t)P_ReadUINT32(save_p); players[i].camerascale = P_ReadFixed(save_p); players[i].shieldscale = P_ReadFixed(save_p); //SetPlayerSkinByNum(i, players[i].skin); players[i].charability = P_ReadUINT8(save_p); players[i].charability2 = P_ReadUINT8(save_p); players[i].charflags = P_ReadUINT32(save_p); players[i].thokitem = P_ReadUINT32(save_p); players[i].spinitem = P_ReadUINT32(save_p); players[i].revitem = P_ReadUINT32(save_p); players[i].followitem = P_ReadUINT32(save_p); players[i].actionspd = P_ReadFixed(save_p); players[i].mindash = P_ReadFixed(save_p); players[i].maxdash = P_ReadFixed(save_p); players[i].normalspeed = P_ReadFixed(save_p); players[i].runspeed = P_ReadFixed(save_p); players[i].thrustfactor = P_ReadUINT8(save_p); players[i].accelstart = P_ReadUINT8(save_p); players[i].acceleration = P_ReadUINT8(save_p); players[i].jumpfactor = P_ReadFixed(save_p); players[i].height = P_ReadFixed(save_p); players[i].spinheight = P_ReadFixed(save_p); players[i].viewheight = 41*players[i].height/48; // scale cannot be factored in at this point } } /// /// Colormaps /// static extracolormap_t *net_colormaps = NULL; static UINT32 num_net_colormaps = 0; static UINT32 num_ffloors = 0; // for loading // Copypasta from r_data.c AddColormapToList // But also check for equality and return the matching index static UINT32 CheckAddNetColormapToList(extracolormap_t *extra_colormap) { extracolormap_t *exc, *exc_prev = NULL; UINT32 i = 0; if (!net_colormaps) { net_colormaps = R_CopyColormap(extra_colormap, false); net_colormaps->next = 0; net_colormaps->prev = 0; num_net_colormaps = i+1; return i; } for (exc = net_colormaps; exc; exc_prev = exc, exc = exc->next) { if (R_CheckEqualColormaps(exc, extra_colormap, true, true, true)) return i; i++; } exc_prev->next = R_CopyColormap(extra_colormap, false); extra_colormap->prev = exc_prev; extra_colormap->next = 0; num_net_colormaps = i+1; return i; } static extracolormap_t *GetNetColormapFromList(UINT32 index) { // For loading, we have to be tricky: // We load the sectors BEFORE knowing the colormap values // So if an index doesn't exist, fill our list with dummy colormaps // until we get the index we want // Then when we load the color data, we set up the dummy colormaps extracolormap_t *exc, *last_exc = NULL; UINT32 i = 0; if (!net_colormaps) // initialize our list net_colormaps = R_CreateDefaultColormap(false); for (exc = net_colormaps; exc; last_exc = exc, exc = exc->next) { if (i++ == index) return exc; } // LET'S HOPE that index is a sane value, because we create up to [index] // entries in net_colormaps. At this point, we don't know // what the total colormap count is if (index >= numsectors*3 + num_ffloors) // if every sector had a unique colormap change AND a fade color thinker which has two colormap entries // AND every ffloor had a fade FOF thinker with one colormap entry I_Error("Colormap %d from server is too high for sectors %d", index, (UINT32)numsectors); // our index doesn't exist, so just make the entry for (; i <= index; i++) { exc = R_CreateDefaultColormap(false); if (last_exc) last_exc->next = exc; exc->prev = last_exc; exc->next = NULL; last_exc = exc; } return exc; } static void ClearNetColormaps(void) { // We're actually Z_Freeing each entry here, // so don't call this in P_NetUnArchiveColormaps (where entries will be used in-game) extracolormap_t *exc, *exc_next; for (exc = net_colormaps; exc; exc = exc_next) { exc_next = exc->next; Z_Free(exc); } num_net_colormaps = 0; num_ffloors = 0; net_colormaps = NULL; } static void P_NetArchiveColormaps(save_t *save_p) { // We save and then we clean up our colormap mess extracolormap_t *exc, *exc_next; UINT32 i = 0; P_WriteUINT32(save_p, num_net_colormaps); // save for safety for (exc = net_colormaps; i < num_net_colormaps; i++, exc = exc_next) { // We must save num_net_colormaps worth of data // So fill non-existent entries with default. if (!exc) exc = R_CreateDefaultColormap(false); P_WriteUINT8(save_p, exc->fadestart); P_WriteUINT8(save_p, exc->fadeend); P_WriteUINT8(save_p, exc->flags); P_WriteINT32(save_p, exc->rgba); P_WriteINT32(save_p, exc->fadergba); #ifdef EXTRACOLORMAPLUMPS P_WriteStringN(save_p, exc->lumpname, 9); #endif exc_next = exc->next; Z_Free(exc); // don't need anymore } num_net_colormaps = 0; num_ffloors = 0; net_colormaps = NULL; } static void P_NetUnArchiveColormaps(save_t *save_p) { // When we reach this point, we already populated our list with // dummy colormaps. Now that we are loading the color data, // set up the dummies. extracolormap_t *exc, *existing_exc, *exc_next = NULL; UINT32 i = 0; num_net_colormaps = P_ReadUINT32(save_p); for (exc = net_colormaps; i < num_net_colormaps; i++, exc = exc_next) { UINT8 fadestart, fadeend, flags; INT32 rgba, fadergba; #ifdef EXTRACOLORMAPLUMPS char lumpname[9]; #endif fadestart = P_ReadUINT8(save_p); fadeend = P_ReadUINT8(save_p); flags = P_ReadUINT8(save_p); rgba = P_ReadINT32(save_p); fadergba = P_ReadINT32(save_p); #ifdef EXTRACOLORMAPLUMPS P_ReadStringN(save_p, lumpname, 9); if (lumpname[0]) { if (!exc) // no point making a new entry since nothing points to it, // but we needed to read the data so now continue continue; exc_next = exc->next; // this gets overwritten during our operations here, so get it now existing_exc = R_ColormapForName(lumpname); *exc = *existing_exc; R_AddColormapToList(exc); // see HACK note below on why we're adding duplicates continue; } #endif if (!exc) // no point making a new entry since nothing points to it, // but we needed to read the data so now continue continue; exc_next = exc->next; // this gets overwritten during our operations here, so get it now exc->fadestart = fadestart; exc->fadeend = fadeend; exc->flags = flags; exc->rgba = rgba; exc->fadergba = fadergba; #ifdef EXTRACOLORMAPLUMPS exc->lump = LUMPERROR; exc->lumpname[0] = 0; #endif existing_exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags); if (existing_exc) exc->colormap = existing_exc->colormap; else // CONS_Debug(DBG_RENDER, "Creating Colormap: rgba(%d,%d,%d,%d) fadergba(%d,%d,%d,%d)\n", // R_GetRgbaR(rgba), R_GetRgbaG(rgba), R_GetRgbaB(rgba), R_GetRgbaA(rgba), // R_GetRgbaR(fadergba), R_GetRgbaG(fadergba), R_GetRgbaB(fadergba), R_GetRgbaA(fadergba)); exc->colormap = R_CreateLightTable(exc); // HACK: If this dummy is a duplicate, we're going to add it // to the extra_colormaps list anyway. I think this is faster // than going through every loaded sector and correcting their // colormap address to the pre-existing one, PER net_colormap entry R_AddColormapToList(exc); if (i < num_net_colormaps-1 && !exc_next) exc_next = R_CreateDefaultColormap(false); } // if we still have a valid net_colormap after iterating up to num_net_colormaps, // some sector had a colormap index higher than num_net_colormaps. We done goofed or $$$ was corrupted. // In any case, add them to the colormap list too so that at least the sectors' colormap // addresses are valid and accounted properly if (exc_next) { existing_exc = R_GetDefaultColormap(); for (exc = exc_next; exc; exc = exc->next) { exc->colormap = existing_exc->colormap; // all our dummies are default values R_AddColormapToList(exc); } } // Don't need these anymore num_net_colormaps = 0; num_ffloors = 0; net_colormaps = NULL; } static void P_NetArchiveWaypoints(save_t *save_p) { INT32 i, j; for (i = 0; i < NUMWAYPOINTSEQUENCES; i++) { P_WriteUINT16(save_p, numwaypoints[i]); for (j = 0; j < numwaypoints[i]; j++) P_WriteUINT32(save_p, waypoints[i][j] ? waypoints[i][j]->mobjnum : 0); } } static void P_NetUnArchiveWaypoints(save_t *save_p) { INT32 i, j; UINT32 mobjnum; for (i = 0; i < NUMWAYPOINTSEQUENCES; i++) { numwaypoints[i] = P_ReadUINT16(save_p); for (j = 0; j < numwaypoints[i]; j++) { mobjnum = P_ReadUINT32(save_p); waypoints[i][j] = (mobjnum == 0) ? NULL : P_FindNewPosition(mobjnum); } } } /// /// World Archiving /// #define SD_FLOORHT 0x01 #define SD_CEILHT 0x02 #define SD_FLOORPIC 0x04 #define SD_CEILPIC 0x08 #define SD_LIGHT 0x10 #define SD_SPECIAL 0x20 #define SD_DIFF2 0x40 #define SD_FFLOORS 0x80 // diff2 flags #define SD_FXOFFS 0x01 #define SD_FYOFFS 0x02 #define SD_CXOFFS 0x04 #define SD_CYOFFS 0x08 #define SD_FLOORANG 0x10 #define SD_CEILANG 0x20 #define SD_TAG 0x40 #define SD_DIFF3 0x80 // diff3 flags #define SD_TAGLIST 0x01 #define SD_COLORMAP 0x02 #define SD_CRUMBLESTATE 0x04 #define SD_FLOORLIGHT 0x08 #define SD_CEILLIGHT 0x10 #define SD_FLAG 0x20 #define SD_SPECIALFLAG 0x40 #define SD_DIFF4 0x80 // diff4 flags #define SD_DAMAGETYPE 0x01 #define SD_TRIGGERTAG 0x02 #define SD_TRIGGERER 0x04 #define SD_FXSCALE 0x08 #define SD_FYSCALE 0x10 #define SD_CXSCALE 0x20 #define SD_CYSCALE 0x40 #define SD_DIFF5 0x80 // diff5 flags #define SD_GRAVITY 0x01 #define SD_FLOORPORTAL 0x02 #define SD_CEILPORTAL 0x04 // diff1 flags #define LD_FLAG 0x01 #define LD_SPECIAL 0x02 #define LD_CLLCOUNT 0x04 #define LD_ARGS 0x08 #define LD_STRINGARGS 0x10 #define LD_SIDE1 0x20 #define LD_SIDE2 0x40 #define LD_DIFF2 0x80 // diff2 flags #define LD_EXECUTORDELAY 0x01 #define LD_TRANSFPORTAL 0x02 // sidedef flags enum { LD_SDTEXOFFX = 1, LD_SDTEXOFFY = 1<<1, LD_SDTOPTEX = 1<<2, LD_SDBOTTEX = 1<<3, LD_SDMIDTEX = 1<<4, LD_SDTOPOFFX = 1<<5, LD_SDTOPOFFY = 1<<6, LD_SDMIDOFFX = 1<<7, LD_SDMIDOFFY = 1<<8, LD_SDBOTOFFX = 1<<9, LD_SDBOTOFFY = 1<<10, LD_SDTOPSCALEX = 1<<11, LD_SDTOPSCALEY = 1<<12, LD_SDMIDSCALEX = 1<<13, LD_SDMIDSCALEY = 1<<14, LD_SDBOTSCALEX = 1<<15, LD_SDBOTSCALEY = 1<<16, LD_SDLIGHT = 1<<17, LD_SDTOPLIGHT = 1<<18, LD_SDMIDLIGHT = 1<<19, LD_SDBOTLIGHT = 1<<20, LD_SDREPEATCNT = 1<<21, LD_SDFLAGS = 1<<22, LD_SDLIGHTABS = 1<<23, LD_SDTOPLIGHTABS = 1<<24, LD_SDMIDLIGHTABS = 1<<25, LD_SDBOTLIGHTABS = 1<<26 }; static boolean P_AreArgsEqual(const line_t *li, const line_t *spawnli) { UINT8 i; for (i = 0; i < NUMLINEARGS; i++) if (li->args[i] != spawnli->args[i]) return false; return true; } static boolean P_AreStringArgsEqual(const line_t *li, const line_t *spawnli) { UINT8 i; for (i = 0; i < NUMLINESTRINGARGS; i++) { if (!li->stringargs[i]) return !spawnli->stringargs[i]; if (strcmp(li->stringargs[i], spawnli->stringargs[i])) return false; } return true; } #define FD_FLAGS 0x01 #define FD_ALPHA 0x02 // Check if any of the sector's FOFs differ from how they spawned static boolean CheckFFloorDiff(const sector_t *ss) { ffloor_t *rover; for (rover = ss->ffloors; rover; rover = rover->next) { if (rover->fofflags != rover->spawnflags || rover->alpha != rover->spawnalpha) { return true; // we found an FOF that changed! // don't bother checking for more, we do that later } } return false; } // Special case: save the stats of all modified ffloors along with their ffloor "number"s // we don't bother with ffloors that haven't changed, that would just add to savegame even more than is really needed static void ArchiveFFloors(save_t *save_p, const sector_t *ss) { size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc ffloor_t *rover; UINT8 fflr_diff; for (rover = ss->ffloors; rover; rover = rover->next) { fflr_diff = 0; // reset diff flags if (rover->fofflags != rover->spawnflags) fflr_diff |= FD_FLAGS; if (rover->alpha != rover->spawnalpha) fflr_diff |= FD_ALPHA; if (fflr_diff) { P_WriteUINT16(save_p, j); // save ffloor "number" P_WriteUINT8(save_p, fflr_diff); if (fflr_diff & FD_FLAGS) P_WriteUINT32(save_p, rover->fofflags); if (fflr_diff & FD_ALPHA) P_WriteINT16(save_p, rover->alpha); } j++; } P_WriteUINT16(save_p, 0xffff); } static void UnArchiveFFloors(save_t *save_p, const sector_t *ss) { UINT16 j = 0; // number of current ffloor in loop UINT16 fflr_i; // saved ffloor "number" of next modified ffloor UINT16 fflr_diff; // saved ffloor diff ffloor_t *rover; rover = ss->ffloors; if (!rover) // it is assumed sectors[i].ffloors actually exists, but just in case... I_Error("Sector does not have any ffloors!"); fflr_i = P_ReadUINT16(save_p); // get first modified ffloor's number ready for (;;) // for some reason the usual for (rover = x; ...) thing doesn't work here? { if (fflr_i == 0xffff) // end of modified ffloors list, let's stop already break; // should NEVER need to be checked //if (rover == NULL) //break; if (j != fflr_i) // this ffloor was not modified { j++; rover = rover->next; continue; } fflr_diff = P_ReadUINT8(save_p); if (fflr_diff & FD_FLAGS) rover->fofflags = P_ReadUINT32(save_p); if (fflr_diff & FD_ALPHA) rover->alpha = P_ReadINT16(save_p); fflr_i = P_ReadUINT16(save_p); // get next ffloor "number" ready j++; rover = rover->next; } } static void ArchiveSectors(save_t *save_p) { size_t i, j; const sector_t *ss = sectors; const sector_t *spawnss = spawnsectors; UINT8 diff, diff2, diff3, diff4, diff5; for (i = 0; i < numsectors; i++, ss++, spawnss++) { diff = diff2 = diff3 = diff4 = diff5 = 0; if (ss->floorheight != spawnss->floorheight) diff |= SD_FLOORHT; if (ss->ceilingheight != spawnss->ceilingheight) diff |= SD_CEILHT; // // flats // if (ss->floorpic != spawnss->floorpic) diff |= SD_FLOORPIC; if (ss->ceilingpic != spawnss->ceilingpic) diff |= SD_CEILPIC; if (ss->lightlevel != spawnss->lightlevel) diff |= SD_LIGHT; if (ss->special != spawnss->special) diff |= SD_SPECIAL; if (ss->floorxoffset != spawnss->floorxoffset) diff2 |= SD_FXOFFS; if (ss->flooryoffset != spawnss->flooryoffset) diff2 |= SD_FYOFFS; if (ss->ceilingxoffset != spawnss->ceilingxoffset) diff2 |= SD_CXOFFS; if (ss->ceilingyoffset != spawnss->ceilingyoffset) diff2 |= SD_CYOFFS; if (ss->floorxscale != spawnss->floorxscale) diff2 |= SD_FXSCALE; if (ss->flooryscale != spawnss->flooryscale) diff2 |= SD_FYSCALE; if (ss->ceilingxscale != spawnss->ceilingxscale) diff2 |= SD_CXSCALE; if (ss->ceilingyscale != spawnss->ceilingyscale) diff2 |= SD_CYSCALE; if (ss->floorangle != spawnss->floorangle) diff2 |= SD_FLOORANG; if (ss->ceilingangle != spawnss->ceilingangle) diff2 |= SD_CEILANG; if (!Tag_Compare(&ss->tags, &spawnss->tags)) diff2 |= SD_TAG; if (ss->extra_colormap != spawnss->extra_colormap) diff3 |= SD_COLORMAP; if (ss->crumblestate) diff3 |= SD_CRUMBLESTATE; if (ss->floorlightlevel != spawnss->floorlightlevel || ss->floorlightabsolute != spawnss->floorlightabsolute) diff3 |= SD_FLOORLIGHT; if (ss->ceilinglightlevel != spawnss->ceilinglightlevel || ss->ceilinglightabsolute != spawnss->ceilinglightabsolute) diff3 |= SD_CEILLIGHT; if (ss->flags != spawnss->flags) diff3 |= SD_FLAG; if (ss->specialflags != spawnss->specialflags) diff3 |= SD_SPECIALFLAG; if (ss->damagetype != spawnss->damagetype) diff4 |= SD_DAMAGETYPE; if (ss->triggertag != spawnss->triggertag) diff4 |= SD_TRIGGERTAG; if (ss->triggerer != spawnss->triggerer) diff4 |= SD_TRIGGERER; if (ss->gravity != spawnss->gravity) diff5 |= SD_GRAVITY; if (ss->portal_floor != spawnss->portal_floor) diff5 |= SD_FLOORPORTAL; if (ss->portal_ceiling != spawnss->portal_ceiling) diff5 |= SD_CEILPORTAL; if (ss->ffloors && CheckFFloorDiff(ss)) diff |= SD_FFLOORS; if (diff5) diff4 |= SD_DIFF5; if (diff4) diff3 |= SD_DIFF4; if (diff3) diff2 |= SD_DIFF3; if (diff2) diff |= SD_DIFF2; if (diff) { P_WriteUINT32(save_p, i); P_WriteUINT8(save_p, diff); if (diff & SD_DIFF2) P_WriteUINT8(save_p, diff2); if (diff2 & SD_DIFF3) P_WriteUINT8(save_p, diff3); if (diff3 & SD_DIFF4) P_WriteUINT8(save_p, diff4); if (diff4 & SD_DIFF5) P_WriteUINT8(save_p, diff5); if (diff & SD_FLOORHT) P_WriteFixed(save_p, ss->floorheight); if (diff & SD_CEILHT) P_WriteFixed(save_p, ss->ceilingheight); if (diff & SD_FLOORPIC) P_WriteMem(save_p, levelflats[ss->floorpic].name, 8); if (diff & SD_CEILPIC) P_WriteMem(save_p, levelflats[ss->ceilingpic].name, 8); if (diff & SD_LIGHT) P_WriteINT16(save_p, ss->lightlevel); if (diff & SD_SPECIAL) P_WriteINT16(save_p, ss->special); if (diff2 & SD_FXOFFS) P_WriteFixed(save_p, ss->floorxoffset); if (diff2 & SD_FYOFFS) P_WriteFixed(save_p, ss->flooryoffset); if (diff2 & SD_CXOFFS) P_WriteFixed(save_p, ss->ceilingxoffset); if (diff2 & SD_CYOFFS) P_WriteFixed(save_p, ss->ceilingyoffset); if (diff2 & SD_FLOORANG) P_WriteAngle(save_p, ss->floorangle); if (diff2 & SD_CEILANG) P_WriteAngle(save_p, ss->ceilingangle); if (diff2 & SD_TAG) { P_WriteUINT32(save_p, ss->tags.count); for (j = 0; j < ss->tags.count; j++) P_WriteINT16(save_p, ss->tags.tags[j]); } if (diff3 & SD_COLORMAP) P_WriteUINT32(save_p, CheckAddNetColormapToList(ss->extra_colormap)); // returns existing index if already added, or appends to net_colormaps and returns new index if (diff3 & SD_CRUMBLESTATE) P_WriteINT32(save_p, ss->crumblestate); if (diff3 & SD_FLOORLIGHT) { P_WriteINT16(save_p, ss->floorlightlevel); P_WriteUINT8(save_p, ss->floorlightabsolute); } if (diff3 & SD_CEILLIGHT) { P_WriteINT16(save_p, ss->ceilinglightlevel); P_WriteUINT8(save_p, ss->ceilinglightabsolute); } if (diff3 & SD_FLAG) P_WriteUINT32(save_p, ss->flags); if (diff3 & SD_SPECIALFLAG) P_WriteUINT32(save_p, ss->specialflags); if (diff4 & SD_DAMAGETYPE) P_WriteUINT8(save_p, ss->damagetype); if (diff4 & SD_TRIGGERTAG) P_WriteINT16(save_p, ss->triggertag); if (diff4 & SD_TRIGGERER) P_WriteUINT8(save_p, ss->triggerer); if (diff4 & SD_FXSCALE) P_WriteFixed(save_p, ss->floorxscale); if (diff4 & SD_FYSCALE) P_WriteFixed(save_p, ss->flooryscale); if (diff4 & SD_CXSCALE) P_WriteFixed(save_p, ss->ceilingxscale); if (diff4 & SD_CYSCALE) P_WriteFixed(save_p, ss->ceilingyscale); if (diff5 & SD_GRAVITY) P_WriteFixed(save_p, ss->gravity); if (diff5 & SD_FLOORPORTAL) P_WriteUINT32(save_p, ss->portal_floor); if (diff5 & SD_CEILPORTAL) P_WriteUINT32(save_p, ss->portal_ceiling); if (diff & SD_FFLOORS) ArchiveFFloors(save_p, ss); } } P_WriteUINT32(save_p, 0xffffffff); } static void UnArchiveSectors(save_t *save_p) { UINT32 i; UINT16 j; UINT8 diff, diff2, diff3, diff4, diff5; for (;;) { i = P_ReadUINT32(save_p); if (i == 0xffffffff) break; if (i > numsectors) I_Error("Invalid sector number %u from server (expected end at %s)", i, sizeu1(numsectors)); diff = P_ReadUINT8(save_p); if (diff & SD_DIFF2) diff2 = P_ReadUINT8(save_p); else diff2 = 0; if (diff2 & SD_DIFF3) diff3 = P_ReadUINT8(save_p); else diff3 = 0; if (diff3 & SD_DIFF4) diff4 = P_ReadUINT8(save_p); else diff4 = 0; if (diff4 & SD_DIFF5) diff5 = P_ReadUINT8(save_p); else diff5 = 0; if (diff & SD_FLOORHT) sectors[i].floorheight = P_ReadFixed(save_p); if (diff & SD_CEILHT) sectors[i].ceilingheight = P_ReadFixed(save_p); if (diff & SD_FLOORPIC) { sectors[i].floorpic = P_AddLevelFlatRuntime((char *)&save_p->buf[save_p->pos]); save_p->pos += 8; } if (diff & SD_CEILPIC) { sectors[i].ceilingpic = P_AddLevelFlatRuntime((char *)&save_p->buf[save_p->pos]); save_p->pos += 8; } if (diff & SD_LIGHT) sectors[i].lightlevel = P_ReadINT16(save_p); if (diff & SD_SPECIAL) sectors[i].special = P_ReadINT16(save_p); if (diff2 & SD_FXOFFS) sectors[i].floorxoffset = P_ReadFixed(save_p); if (diff2 & SD_FYOFFS) sectors[i].flooryoffset = P_ReadFixed(save_p); if (diff2 & SD_CXOFFS) sectors[i].ceilingxoffset = P_ReadFixed(save_p); if (diff2 & SD_CYOFFS) sectors[i].ceilingyoffset = P_ReadFixed(save_p); if (diff2 & SD_FLOORANG) sectors[i].floorangle = P_ReadAngle(save_p); if (diff2 & SD_CEILANG) sectors[i].ceilingangle = P_ReadAngle(save_p); if (diff2 & SD_TAG) { size_t ncount = P_ReadUINT32(save_p); // Remove entries from global lists. for (j = 0; j < sectors[i].tags.count; j++) Taggroup_Remove(tags_sectors, sectors[i].tags.tags[j], i); // Reallocate if size differs. if (ncount != sectors[i].tags.count) { sectors[i].tags.count = ncount; sectors[i].tags.tags = Z_Realloc(sectors[i].tags.tags, ncount*sizeof(mtag_t), PU_LEVEL, NULL); } for (j = 0; j < ncount; j++) sectors[i].tags.tags[j] = P_ReadINT16(save_p); // Add new entries. for (j = 0; j < sectors[i].tags.count; j++) Taggroup_Remove(tags_sectors, sectors[i].tags.tags[j], i); } if (diff3 & SD_COLORMAP) sectors[i].extra_colormap = GetNetColormapFromList(P_ReadUINT32(save_p)); if (diff3 & SD_CRUMBLESTATE) sectors[i].crumblestate = P_ReadINT32(save_p); if (diff3 & SD_FLOORLIGHT) { sectors[i].floorlightlevel = P_ReadINT16(save_p); sectors[i].floorlightabsolute = P_ReadUINT8(save_p); } if (diff3 & SD_CEILLIGHT) { sectors[i].ceilinglightlevel = P_ReadINT16(save_p); sectors[i].ceilinglightabsolute = P_ReadUINT8(save_p); } if (diff3 & SD_FLAG) { sectors[i].flags = P_ReadUINT32(save_p); CheckForReverseGravity |= (sectors[i].flags & MSF_GRAVITYFLIP); } if (diff3 & SD_SPECIALFLAG) sectors[i].specialflags = P_ReadUINT32(save_p); if (diff4 & SD_DAMAGETYPE) sectors[i].damagetype = P_ReadUINT8(save_p); if (diff4 & SD_TRIGGERTAG) sectors[i].triggertag = P_ReadINT16(save_p); if (diff4 & SD_TRIGGERER) sectors[i].triggerer = P_ReadUINT8(save_p); if (diff4 & SD_FXSCALE) sectors[i].floorxscale = P_ReadFixed(save_p); if (diff4 & SD_FYSCALE) sectors[i].flooryscale = P_ReadFixed(save_p); if (diff4 & SD_CXSCALE) sectors[i].ceilingxscale = P_ReadFixed(save_p); if (diff4 & SD_CYSCALE) sectors[i].ceilingyscale = P_ReadFixed(save_p); if (diff5 & SD_GRAVITY) sectors[i].gravity = P_ReadFixed(save_p); if (diff5 & SD_FLOORPORTAL) sectors[i].portal_floor = P_ReadUINT32(save_p); if (diff5 & SD_CEILPORTAL) sectors[i].portal_ceiling = P_ReadUINT32(save_p); if (diff & SD_FFLOORS) UnArchiveFFloors(save_p, §ors[i]); } } static UINT32 GetSideDiff(const side_t *si, const side_t *spawnsi) { UINT32 diff = 0; if (si->textureoffset != spawnsi->textureoffset) diff |= LD_SDTEXOFFX; if (si->rowoffset != spawnsi->rowoffset) diff |= LD_SDTEXOFFY; //SoM: 4/1/2000: Some textures are colormaps. Don't worry about invalid textures. if (si->toptexture != spawnsi->toptexture) diff |= LD_SDTOPTEX; if (si->bottomtexture != spawnsi->bottomtexture) diff |= LD_SDBOTTEX; if (si->midtexture != spawnsi->midtexture) diff |= LD_SDMIDTEX; if (si->offsetx_top != spawnsi->offsetx_top) diff |= LD_SDTOPOFFX; if (si->offsetx_mid != spawnsi->offsetx_mid) diff |= LD_SDMIDOFFX; if (si->offsetx_bottom != spawnsi->offsetx_bottom) diff |= LD_SDBOTOFFX; if (si->offsety_top != spawnsi->offsety_top) diff |= LD_SDTOPOFFY; if (si->offsety_mid != spawnsi->offsety_mid) diff |= LD_SDMIDOFFY; if (si->offsety_bottom != spawnsi->offsety_bottom) diff |= LD_SDBOTOFFY; if (si->scalex_top != spawnsi->scalex_top) diff |= LD_SDTOPSCALEX; if (si->scalex_mid != spawnsi->scalex_mid) diff |= LD_SDMIDSCALEX; if (si->scalex_bottom != spawnsi->scalex_bottom) diff |= LD_SDBOTSCALEX; if (si->scaley_top != spawnsi->scaley_top) diff |= LD_SDTOPSCALEY; if (si->scaley_mid != spawnsi->scaley_mid) diff |= LD_SDMIDSCALEY; if (si->scaley_bottom != spawnsi->scaley_bottom) diff |= LD_SDBOTSCALEY; if (si->repeatcnt != spawnsi->repeatcnt) diff |= LD_SDREPEATCNT; if (si->light != spawnsi->light) diff |= LD_SDLIGHT; if (si->light_top != spawnsi->light_top) diff |= LD_SDTOPLIGHT; if (si->light_mid != spawnsi->light_mid) diff |= LD_SDMIDLIGHT; if (si->light_bottom != spawnsi->light_bottom) diff |= LD_SDBOTLIGHT; if (si->lightabsolute != spawnsi->lightabsolute) diff |= LD_SDLIGHTABS; if (si->lightabsolute_top != spawnsi->lightabsolute_top) diff |= LD_SDTOPLIGHTABS; if (si->lightabsolute_mid != spawnsi->lightabsolute_mid) diff |= LD_SDMIDLIGHTABS; if (si->lightabsolute_bottom != spawnsi->lightabsolute_bottom) diff |= LD_SDBOTLIGHTABS; return diff; } static void ArchiveSide(save_t *save_p, const side_t *si, UINT32 diff) { P_WriteUINT32(save_p, diff); if (diff & LD_SDTEXOFFX) P_WriteFixed(save_p, si->textureoffset); if (diff & LD_SDTEXOFFY) P_WriteFixed(save_p, si->rowoffset); if (diff & LD_SDTOPTEX) P_WriteINT32(save_p, si->toptexture); if (diff & LD_SDBOTTEX) P_WriteINT32(save_p, si->bottomtexture); if (diff & LD_SDMIDTEX) P_WriteINT32(save_p, si->midtexture); if (diff & LD_SDTOPOFFX) P_WriteFixed(save_p, si->offsetx_top); if (diff & LD_SDMIDOFFX) P_WriteFixed(save_p, si->offsetx_mid); if (diff & LD_SDBOTOFFX) P_WriteFixed(save_p, si->offsetx_bottom); if (diff & LD_SDTOPOFFY) P_WriteFixed(save_p, si->offsety_top); if (diff & LD_SDMIDOFFY) P_WriteFixed(save_p, si->offsety_mid); if (diff & LD_SDBOTOFFY) P_WriteFixed(save_p, si->offsety_bottom); if (diff & LD_SDTOPSCALEX) P_WriteFixed(save_p, si->scalex_top); if (diff & LD_SDMIDSCALEX) P_WriteFixed(save_p, si->scalex_mid); if (diff & LD_SDBOTSCALEX) P_WriteFixed(save_p, si->scalex_bottom); if (diff & LD_SDTOPSCALEY) P_WriteFixed(save_p, si->scaley_top); if (diff & LD_SDMIDSCALEY) P_WriteFixed(save_p, si->scaley_mid); if (diff & LD_SDBOTSCALEY) P_WriteFixed(save_p, si->scaley_bottom); if (diff & LD_SDREPEATCNT) P_WriteINT16(save_p, si->repeatcnt); if (diff & LD_SDLIGHT) P_WriteINT16(save_p, si->light); if (diff & LD_SDTOPLIGHT) P_WriteINT16(save_p, si->light_top); if (diff & LD_SDMIDLIGHT) P_WriteINT16(save_p, si->light_mid); if (diff & LD_SDBOTLIGHT) P_WriteINT16(save_p, si->light_bottom); if (diff & LD_SDLIGHTABS) P_WriteUINT8(save_p, si->lightabsolute); if (diff & LD_SDTOPLIGHTABS) P_WriteUINT8(save_p, si->lightabsolute_top); if (diff & LD_SDMIDLIGHTABS) P_WriteUINT8(save_p, si->lightabsolute_mid); if (diff & LD_SDBOTLIGHTABS) P_WriteUINT8(save_p, si->lightabsolute_bottom); } static void ArchiveLines(save_t *save_p) { size_t i; const line_t *li = lines; const line_t *spawnli = spawnlines; UINT8 diff, diff2; UINT32 side1diff; UINT32 side2diff; for (i = 0; i < numlines; i++, spawnli++, li++) { diff = diff2 = 0; side1diff = side2diff = 0; if (li->special != spawnli->special) diff |= LD_SPECIAL; if (spawnli->special == 321 || spawnli->special == 322) // only reason li->callcount would be non-zero is if either of these are involved diff |= LD_CLLCOUNT; if (!P_AreArgsEqual(li, spawnli)) diff |= LD_ARGS; if (!P_AreStringArgsEqual(li, spawnli)) diff |= LD_STRINGARGS; if (li->executordelay != spawnli->executordelay) diff2 |= LD_EXECUTORDELAY; if (li->secportal != spawnli->secportal) diff2 |= LD_TRANSFPORTAL; if (li->sidenum[0] != NO_SIDEDEF) { side1diff = GetSideDiff(&sides[li->sidenum[0]], &spawnsides[li->sidenum[0]]); if (side1diff) diff |= LD_SIDE1; } if (li->sidenum[1] != NO_SIDEDEF) { side2diff = GetSideDiff(&sides[li->sidenum[1]], &spawnsides[li->sidenum[1]]); if (side2diff) diff |= LD_SIDE2; } if (diff2) diff |= LD_DIFF2; if (diff) { P_WriteUINT32(save_p, i); P_WriteUINT8(save_p, diff); if (diff & LD_DIFF2) P_WriteUINT8(save_p, diff2); if (diff & LD_FLAG) P_WriteINT16(save_p, li->flags); if (diff & LD_SPECIAL) P_WriteINT16(save_p, li->special); if (diff & LD_CLLCOUNT) P_WriteINT16(save_p, li->callcount); if (diff & LD_ARGS) { UINT8 j; for (j = 0; j < NUMLINEARGS; j++) P_WriteINT32(save_p, li->args[j]); } if (diff & LD_STRINGARGS) { UINT8 j; for (j = 0; j < NUMLINESTRINGARGS; j++) { size_t len, k; if (!li->stringargs[j]) { P_WriteINT32(save_p, 0); continue; } len = strlen(li->stringargs[j]); P_WriteINT32(save_p, len); for (k = 0; k < len; k++) P_WriteChar(save_p, li->stringargs[j][k]); } } if (diff & LD_SIDE1) ArchiveSide(save_p, &sides[li->sidenum[0]], side1diff); if (diff & LD_SIDE2) ArchiveSide(save_p, &sides[li->sidenum[1]], side2diff); if (diff2 & LD_EXECUTORDELAY) P_WriteINT32(save_p, li->executordelay); if (diff2 & LD_TRANSFPORTAL) P_WriteUINT32(save_p, li->secportal); } } P_WriteUINT32(save_p, 0xffffffff); } static void UnArchiveSide(save_t *save_p, side_t *si) { UINT32 diff = P_ReadUINT32(save_p); if (diff & LD_SDTEXOFFX) si->textureoffset = P_ReadFixed(save_p); if (diff & LD_SDTEXOFFY) si->rowoffset = P_ReadFixed(save_p); if (diff & LD_SDTOPTEX) si->toptexture = P_ReadINT32(save_p); if (diff & LD_SDBOTTEX) si->bottomtexture = P_ReadINT32(save_p); if (diff & LD_SDMIDTEX) si->midtexture = P_ReadINT32(save_p); if (diff & LD_SDTOPOFFX) si->offsetx_top = P_ReadFixed(save_p); if (diff & LD_SDMIDOFFX) si->offsetx_mid = P_ReadFixed(save_p); if (diff & LD_SDBOTOFFX) si->offsetx_bottom = P_ReadFixed(save_p); if (diff & LD_SDTOPOFFY) si->offsety_top = P_ReadFixed(save_p); if (diff & LD_SDMIDOFFY) si->offsety_mid = P_ReadFixed(save_p); if (diff & LD_SDBOTOFFY) si->offsety_bottom = P_ReadFixed(save_p); if (diff & LD_SDTOPSCALEX) si->scalex_top = P_ReadFixed(save_p); if (diff & LD_SDMIDSCALEX) si->scalex_mid = P_ReadFixed(save_p); if (diff & LD_SDBOTSCALEX) si->scalex_bottom = P_ReadFixed(save_p); if (diff & LD_SDTOPSCALEY) si->scaley_top = P_ReadFixed(save_p); if (diff & LD_SDMIDSCALEY) si->scaley_mid = P_ReadFixed(save_p); if (diff & LD_SDBOTSCALEY) si->scaley_bottom = P_ReadFixed(save_p); if (diff & LD_SDREPEATCNT) si->repeatcnt = P_ReadINT16(save_p); if (diff & LD_SDLIGHT) si->light = P_ReadINT16(save_p); if (diff & LD_SDTOPLIGHT) si->light_top = P_ReadINT16(save_p); if (diff & LD_SDMIDLIGHT) si->light_mid = P_ReadINT16(save_p); if (diff & LD_SDBOTLIGHT) si->light_bottom = P_ReadINT16(save_p); if (diff & LD_SDLIGHTABS) si->lightabsolute = P_ReadUINT8(save_p); if (diff & LD_SDTOPLIGHTABS) si->lightabsolute_top = P_ReadUINT8(save_p); if (diff & LD_SDMIDLIGHTABS) si->lightabsolute_mid = P_ReadUINT8(save_p); if (diff & LD_SDBOTLIGHTABS) si->lightabsolute_bottom = P_ReadUINT8(save_p); } static void UnArchiveLines(save_t *save_p) { UINT32 i; line_t *li; UINT8 diff, diff2; for (;;) { i = P_ReadUINT32(save_p); if (i == 0xffffffff) break; if (i > numlines) I_Error("Invalid line number %u from server", i); diff = P_ReadUINT8(save_p); if (diff & LD_DIFF2) diff2 = P_ReadUINT8(save_p); else diff2 = 0; li = &lines[i]; if (diff & LD_FLAG) li->flags = P_ReadINT16(save_p); if (diff & LD_SPECIAL) li->special = P_ReadINT16(save_p); if (diff & LD_CLLCOUNT) li->callcount = P_ReadINT16(save_p); if (diff & LD_ARGS) { UINT8 j; for (j = 0; j < NUMLINEARGS; j++) li->args[j] = P_ReadINT32(save_p); } if (diff & LD_STRINGARGS) { UINT8 j; for (j = 0; j < NUMLINESTRINGARGS; j++) { size_t len = P_ReadINT32(save_p); size_t k; if (!len) { Z_Free(li->stringargs[j]); li->stringargs[j] = NULL; continue; } li->stringargs[j] = Z_Realloc(li->stringargs[j], len + 1, PU_LEVEL, NULL); for (k = 0; k < len; k++) li->stringargs[j][k] = P_ReadChar(save_p); li->stringargs[j][len] = '\0'; } } if (diff & LD_SIDE1) UnArchiveSide(save_p, &sides[li->sidenum[0]]); if (diff & LD_SIDE2) UnArchiveSide(save_p, &sides[li->sidenum[1]]); if (diff2 & LD_EXECUTORDELAY) li->executordelay = P_ReadINT32(save_p); if (diff2 & LD_TRANSFPORTAL) li->secportal = P_ReadUINT32(save_p); } } static void P_NetArchiveWorld(save_t *save_p) { // initialize colormap vars because paranoia ClearNetColormaps(); P_WriteUINT32(save_p, ARCHIVEBLOCK_WORLD); ArchiveSectors(save_p); ArchiveLines(save_p); R_ClearTextureNumCache(false); } static void P_NetUnArchiveWorld(save_t *save_p) { UINT16 i; if (P_ReadUINT32(save_p) != ARCHIVEBLOCK_WORLD) I_Error("Bad $$$.sav at archive block World"); // initialize colormap vars because paranoia ClearNetColormaps(); // count the level's ffloors so that colormap loading can have an upper limit for (i = 0; i < numsectors; i++) { ffloor_t *rover; for (rover = sectors[i].ffloors; rover; rover = rover->next) num_ffloors++; } UnArchiveSectors(save_p); UnArchiveLines(save_p); } // // Thinkers // typedef enum { MD_SPAWNPOINT = 1, MD_POS = 1<<1, MD_TYPE = 1<<2, MD_MOM = 1<<3, MD_RADIUS = 1<<4, MD_HEIGHT = 1<<5, MD_FLAGS = 1<<6, MD_HEALTH = 1<<7, MD_RTIME = 1<<8, MD_STATE = 1<<9, MD_TICS = 1<<10, MD_SPRITE = 1<<11, MD_FRAME = 1<<12, MD_EFLAGS = 1<<13, MD_PLAYER = 1<<14, MD_MOVEDIR = 1<<15, MD_MOVECOUNT = 1<<16, MD_THRESHOLD = 1<<17, MD_LASTLOOK = 1<<18, MD_TARGET = 1<<19, MD_TRACER = 1<<20, MD_FRICTION = 1<<21, MD_MOVEFACTOR = 1<<22, MD_FLAGS2 = 1<<23, MD_FUSE = 1<<24, MD_WATERTOP = 1<<25, MD_WATERBOTTOM = 1<<26, MD_SCALE = 1<<27, MD_DSCALE = 1<<28, MD_BLUEFLAG = 1<<29, MD_REDFLAG = 1<<30, MD_MORE = 1<<31 } mobj_diff_t; typedef enum { MD2_CUSVAL = 1, MD2_CVMEM = 1<<1, MD2_SKIN = 1<<2, MD2_COLOR = 1<<3, MD2_SCALESPEED = 1<<4, MD2_EXTVAL1 = 1<<5, MD2_EXTVAL2 = 1<<6, MD2_HNEXT = 1<<7, MD2_HPREV = 1<<8, MD2_FLOORROVER = 1<<9, MD2_CEILINGROVER = 1<<10, MD2_SLOPE = 1<<11, MD2_COLORIZED = 1<<12, MD2_MIRRORED = 1<<13, MD2_SPRITEROLL = 1<<14, MD2_SHADOWSCALE = 1<<15, MD2_RENDERFLAGS = 1<<16, MD2_BLENDMODE = 1<<17, MD2_SPRITEXSCALE = 1<<18, MD2_SPRITEYSCALE = 1<<19, MD2_SPRITEXOFFSET = 1<<20, MD2_SPRITEYOFFSET = 1<<21, MD2_FLOORSPRITESLOPE = 1<<22, MD2_DISPOFFSET = 1<<23, MD2_DRAWONLYFORPLAYER = 1<<24, MD2_DONTDRAWFORVIEWMOBJ = 1<<25, MD2_TRANSLATION = 1<<26, MD2_ALPHA = 1<<27 } mobj_diff2_t; typedef enum { tc_mobj, tc_ceiling, tc_floor, tc_flash, tc_strobe, tc_glow, tc_fireflicker, tc_thwomp, tc_camerascanner, tc_elevator, tc_continuousfalling, tc_bouncecheese, tc_startcrumble, tc_marioblock, tc_marioblockchecker, tc_floatsector, tc_crushceiling, tc_scroll, tc_friction, tc_pusher, tc_laserflash, tc_lightfade, tc_executor, tc_raisesector, tc_noenemies, tc_eachtime, tc_disappear, tc_fade, tc_fadecolormap, tc_planedisplace, tc_dynslopeline, tc_dynslopevert, tc_polyrotate, // haleyjd 03/26/06: polyobjects tc_polymove, tc_polywaypoint, tc_polyslidedoor, tc_polyswingdoor, tc_polyflag, tc_polydisplace, tc_polyrotdisplace, tc_polyfade, tc_end } specials_e; static inline UINT32 SaveMobjnum(const mobj_t *mobj) { if (mobj) return mobj->mobjnum; return 0; } static UINT32 SaveSector(const sector_t *sector) { if (sector) return (UINT32)(sector - sectors); return 0xFFFFFFFF; } static UINT32 SaveLine(const line_t *line) { if (line) return (UINT32)(line - lines); return 0xFFFFFFFF; } static inline UINT32 SavePlayer(const player_t *player) { if (player) return (UINT32)(player - players); return 0xFFFFFFFF; } static UINT32 SaveSlope(const pslope_t *slope) { if (slope) return (UINT32)(slope->id); return 0xFFFFFFFF; } static void SaveMobjThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const mobj_t *mobj = (const mobj_t *)th; UINT32 diff; UINT32 diff2; // Ignore stationary hoops - these will be respawned from mapthings. if (mobj->type == MT_HOOP) return; // These are NEVER saved. if (mobj->type == MT_HOOPCOLLIDE) return; // This hoop has already been collected. if (mobj->type == MT_HOOPCENTER && mobj->threshold == 4242) return; if (mobj->spawnpoint && mobj->info->doomednum != -1) { // spawnpoint is not modified but we must save it since it is an identifier diff = MD_SPAWNPOINT; if ((mobj->x != mobj->spawnpoint->x << FRACBITS) || (mobj->y != mobj->spawnpoint->y << FRACBITS) || (mobj->angle != FixedAngle(mobj->spawnpoint->angle*FRACUNIT)) || (mobj->pitch != FixedAngle(mobj->spawnpoint->pitch*FRACUNIT)) || (mobj->roll != FixedAngle(mobj->spawnpoint->roll*FRACUNIT)) ) diff |= MD_POS; if (mobj->info->doomednum != mobj->spawnpoint->type) diff |= MD_TYPE; } else diff = MD_POS | MD_TYPE; // not a map spawned thing so make it from scratch diff2 = 0; // not the default but the most probable if (mobj->momx != 0 || mobj->momy != 0 || mobj->momz != 0 || mobj->pmomz !=0) diff |= MD_MOM; if (mobj->radius != mobj->info->radius) diff |= MD_RADIUS; if (mobj->height != mobj->info->height) diff |= MD_HEIGHT; if (mobj->flags != mobj->info->flags) diff |= MD_FLAGS; if (mobj->flags2) diff |= MD_FLAGS2; if (mobj->health != mobj->info->spawnhealth) diff |= MD_HEALTH; if (mobj->reactiontime != mobj->info->reactiontime) diff |= MD_RTIME; if ((statenum_t)(mobj->state-states) != mobj->info->spawnstate) diff |= MD_STATE; if (mobj->tics != mobj->state->tics) diff |= MD_TICS; if (mobj->sprite != mobj->state->sprite) diff |= MD_SPRITE; if (mobj->sprite == SPR_PLAY && mobj->sprite2 != P_GetStateSprite2(mobj->state)) diff |= MD_SPRITE; if (mobj->frame != mobj->state->frame) diff |= MD_FRAME; if (mobj->anim_duration != (UINT16)mobj->state->var2) diff |= MD_FRAME; if (mobj->eflags) diff |= MD_EFLAGS; if (mobj->player) diff |= MD_PLAYER; if (mobj->movedir) diff |= MD_MOVEDIR; if (mobj->movecount) diff |= MD_MOVECOUNT; if (mobj->threshold) diff |= MD_THRESHOLD; if (mobj->lastlook != -1) diff |= MD_LASTLOOK; if (mobj->target) diff |= MD_TARGET; if (mobj->tracer) diff |= MD_TRACER; if (mobj->friction != ORIG_FRICTION) diff |= MD_FRICTION; if (mobj->movefactor != FRACUNIT) diff |= MD_MOVEFACTOR; if (mobj->fuse) diff |= MD_FUSE; if (mobj->watertop) diff |= MD_WATERTOP; if (mobj->waterbottom) diff |= MD_WATERBOTTOM; if (mobj->scale != FRACUNIT) diff |= MD_SCALE; if (mobj->destscale != mobj->scale) diff |= MD_DSCALE; if (mobj->scalespeed != FRACUNIT/12) diff2 |= MD2_SCALESPEED; if (mobj == redflag) diff |= MD_REDFLAG; if (mobj == blueflag) diff |= MD_BLUEFLAG; if (mobj->cusval) diff2 |= MD2_CUSVAL; if (mobj->cvmem) diff2 |= MD2_CVMEM; if (mobj->color) diff2 |= MD2_COLOR; if (mobj->translation) diff2 |= MD2_TRANSLATION; if (mobj->skin) diff2 |= MD2_SKIN; if (mobj->extravalue1) diff2 |= MD2_EXTVAL1; if (mobj->extravalue2) diff2 |= MD2_EXTVAL2; if (mobj->hnext) diff2 |= MD2_HNEXT; if (mobj->hprev) diff2 |= MD2_HPREV; if (mobj->floorrover) diff2 |= MD2_FLOORROVER; if (mobj->ceilingrover) diff2 |= MD2_CEILINGROVER; if (mobj->standingslope) diff2 |= MD2_SLOPE; if (mobj->colorized) diff2 |= MD2_COLORIZED; if (mobj->mirrored) diff2 |= MD2_MIRRORED; if (mobj->spriteroll) diff2 |= MD2_SPRITEROLL; if (mobj->shadowscale) diff2 |= MD2_SHADOWSCALE; if (mobj->renderflags) diff2 |= MD2_RENDERFLAGS; if (mobj->blendmode != AST_TRANSLUCENT) diff2 |= MD2_BLENDMODE; if (mobj->spritexscale != FRACUNIT) diff2 |= MD2_SPRITEXSCALE; if (mobj->spriteyscale != FRACUNIT) diff2 |= MD2_SPRITEYSCALE; if (mobj->spritexoffset) diff2 |= MD2_SPRITEXOFFSET; if (mobj->spriteyoffset) diff2 |= MD2_SPRITEYOFFSET; if (mobj->floorspriteslope) { pslope_t *slope = mobj->floorspriteslope; if (slope->zangle || slope->zdelta || slope->xydirection || slope->o.x || slope->o.y || slope->o.z || slope->d.x || slope->d.y || slope->normal.x || slope->normal.y || (slope->normal.z != FRACUNIT)) diff2 |= MD2_FLOORSPRITESLOPE; } if (mobj->drawonlyforplayer) diff2 |= MD2_DRAWONLYFORPLAYER; if (mobj->dontdrawforviewmobj) diff2 |= MD2_DONTDRAWFORVIEWMOBJ; if (mobj->dispoffset != mobj->info->dispoffset) diff2 |= MD2_DISPOFFSET; if (mobj->alpha != FRACUNIT) diff2 |= MD2_ALPHA; if (diff2 != 0) diff |= MD_MORE; // Scrap all of that. If we're a hoop center, this is ALL we're saving. if (mobj->type == MT_HOOPCENTER) diff = MD_SPAWNPOINT; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, diff); if (diff & MD_MORE) P_WriteUINT32(save_p, diff2); // save pointer, at load time we will search this pointer to reinitilize pointers P_WriteUINT32(save_p, (size_t)mobj); P_WriteFixed(save_p, mobj->z); // Force this so 3dfloor problems don't arise. P_WriteFixed(save_p, mobj->floorz); P_WriteFixed(save_p, mobj->ceilingz); if (diff2 & MD2_FLOORROVER) { P_WriteUINT32(save_p, SaveSector(mobj->floorrover->target)); P_WriteUINT16(save_p, P_GetFFloorID(mobj->floorrover)); } if (diff2 & MD2_CEILINGROVER) { P_WriteUINT32(save_p, SaveSector(mobj->ceilingrover->target)); P_WriteUINT16(save_p, P_GetFFloorID(mobj->ceilingrover)); } if (diff & MD_SPAWNPOINT) { size_t z; for (z = 0; z < nummapthings; z++) if (&mapthings[z] == mobj->spawnpoint) P_WriteUINT16(save_p, z); if (mobj->type == MT_HOOPCENTER) return; } if (diff & MD_TYPE) P_WriteUINT32(save_p, mobj->type); if (diff & MD_POS) { P_WriteFixed(save_p, mobj->x); P_WriteFixed(save_p, mobj->y); P_WriteAngle(save_p, mobj->angle); P_WriteAngle(save_p, mobj->pitch); P_WriteAngle(save_p, mobj->roll); } if (diff & MD_MOM) { P_WriteFixed(save_p, mobj->momx); P_WriteFixed(save_p, mobj->momy); P_WriteFixed(save_p, mobj->momz); P_WriteFixed(save_p, mobj->pmomz); } if (diff & MD_RADIUS) P_WriteFixed(save_p, mobj->radius); if (diff & MD_HEIGHT) P_WriteFixed(save_p, mobj->height); if (diff & MD_FLAGS) P_WriteUINT32(save_p, mobj->flags); if (diff & MD_FLAGS2) P_WriteUINT32(save_p, mobj->flags2); if (diff & MD_HEALTH) P_WriteINT32(save_p, mobj->health); if (diff & MD_RTIME) P_WriteINT32(save_p, mobj->reactiontime); if (diff & MD_STATE) P_WriteUINT16(save_p, mobj->state-states); if (diff & MD_TICS) P_WriteINT32(save_p, mobj->tics); if (diff & MD_SPRITE) { P_WriteUINT16(save_p, mobj->sprite); if (mobj->sprite == SPR_PLAY) P_WriteUINT16(save_p, mobj->sprite2); } if (diff & MD_FRAME) { P_WriteUINT32(save_p, mobj->frame); P_WriteUINT16(save_p, mobj->anim_duration); } if (diff & MD_EFLAGS) P_WriteUINT16(save_p, mobj->eflags); if (diff & MD_PLAYER) P_WriteUINT8(save_p, mobj->player-players); if (diff & MD_MOVEDIR) P_WriteAngle(save_p, mobj->movedir); if (diff & MD_MOVECOUNT) P_WriteINT32(save_p, mobj->movecount); if (diff & MD_THRESHOLD) P_WriteINT32(save_p, mobj->threshold); if (diff & MD_LASTLOOK) P_WriteINT32(save_p, mobj->lastlook); if (diff & MD_TARGET) P_WriteUINT32(save_p, mobj->target->mobjnum); if (diff & MD_TRACER) P_WriteUINT32(save_p, mobj->tracer->mobjnum); if (diff & MD_FRICTION) P_WriteFixed(save_p, mobj->friction); if (diff & MD_MOVEFACTOR) P_WriteFixed(save_p, mobj->movefactor); if (diff & MD_FUSE) P_WriteINT32(save_p, mobj->fuse); if (diff & MD_WATERTOP) P_WriteFixed(save_p, mobj->watertop); if (diff & MD_WATERBOTTOM) P_WriteFixed(save_p, mobj->waterbottom); if (diff & MD_SCALE) P_WriteFixed(save_p, mobj->scale); if (diff & MD_DSCALE) P_WriteFixed(save_p, mobj->destscale); if (diff2 & MD2_SCALESPEED) P_WriteFixed(save_p, mobj->scalespeed); if (diff2 & MD2_CUSVAL) P_WriteINT32(save_p, mobj->cusval); if (diff2 & MD2_CVMEM) P_WriteINT32(save_p, mobj->cvmem); if (diff2 & MD2_SKIN) P_WriteUINT8(save_p, (UINT8)(((skin_t *)mobj->skin)->skinnum)); if (diff2 & MD2_COLOR) P_WriteUINT16(save_p, mobj->color); if (diff2 & MD2_EXTVAL1) P_WriteINT32(save_p, mobj->extravalue1); if (diff2 & MD2_EXTVAL2) P_WriteINT32(save_p, mobj->extravalue2); if (diff2 & MD2_HNEXT) P_WriteUINT32(save_p, mobj->hnext->mobjnum); if (diff2 & MD2_HPREV) P_WriteUINT32(save_p, mobj->hprev->mobjnum); if (diff2 & MD2_SLOPE) P_WriteUINT16(save_p, mobj->standingslope->id); if (diff2 & MD2_COLORIZED) P_WriteUINT8(save_p, mobj->colorized); if (diff2 & MD2_MIRRORED) P_WriteUINT8(save_p, mobj->mirrored); if (diff2 & MD2_SPRITEROLL) P_WriteAngle(save_p, mobj->spriteroll); if (diff2 & MD2_SHADOWSCALE) P_WriteFixed(save_p, mobj->shadowscale); if (diff2 & MD2_RENDERFLAGS) P_WriteUINT32(save_p, mobj->renderflags); if (diff2 & MD2_BLENDMODE) P_WriteINT32(save_p, mobj->blendmode); if (diff2 & MD2_SPRITEXSCALE) P_WriteFixed(save_p, mobj->spritexscale); if (diff2 & MD2_SPRITEYSCALE) P_WriteFixed(save_p, mobj->spriteyscale); if (diff2 & MD2_SPRITEXOFFSET) P_WriteFixed(save_p, mobj->spritexoffset); if (diff2 & MD2_SPRITEYOFFSET) P_WriteFixed(save_p, mobj->spriteyoffset); if (diff2 & MD2_FLOORSPRITESLOPE) { pslope_t *slope = mobj->floorspriteslope; P_WriteFixed(save_p, slope->zdelta); P_WriteAngle(save_p, slope->zangle); P_WriteAngle(save_p, slope->xydirection); P_WriteFixed(save_p, slope->o.x); P_WriteFixed(save_p, slope->o.y); P_WriteFixed(save_p, slope->o.z); P_WriteFixed(save_p, slope->d.x); P_WriteFixed(save_p, slope->d.y); P_WriteFixed(save_p, slope->normal.x); P_WriteFixed(save_p, slope->normal.y); P_WriteFixed(save_p, slope->normal.z); } if (diff2 & MD2_DRAWONLYFORPLAYER) P_WriteUINT8(save_p, mobj->drawonlyforplayer-players); if (diff2 & MD2_DONTDRAWFORVIEWMOBJ) P_WriteUINT32(save_p, mobj->dontdrawforviewmobj->mobjnum); if (diff2 & MD2_DISPOFFSET) P_WriteINT32(save_p, mobj->dispoffset); if (diff2 & MD2_TRANSLATION) P_WriteUINT16(save_p, mobj->translation); if (diff2 & MD2_ALPHA) P_WriteFixed(save_p, mobj->alpha); P_WriteUINT32(save_p, mobj->mobjnum); } static void SaveNoEnemiesThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const noenemies_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); } static void SaveBounceCheeseThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const bouncecheese_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteFixed(save_p, ht->speed); P_WriteFixed(save_p, ht->distance); P_WriteFixed(save_p, ht->floorwasheight); P_WriteFixed(save_p, ht->ceilingwasheight); P_WriteChar(save_p, ht->low); } static void SaveContinuousFallThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const continuousfall_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteFixed(save_p, ht->speed); P_WriteINT32(save_p, ht->direction); P_WriteFixed(save_p, ht->floorstartheight); P_WriteFixed(save_p, ht->ceilingstartheight); P_WriteFixed(save_p, ht->destheight); } static void SaveMarioBlockThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const mariothink_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteFixed(save_p, ht->speed); P_WriteINT32(save_p, ht->direction); P_WriteFixed(save_p, ht->floorstartheight); P_WriteFixed(save_p, ht->ceilingstartheight); P_WriteINT16(save_p, ht->tag); } static void SaveMarioCheckThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const mariocheck_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); P_WriteUINT32(save_p, SaveSector(ht->sector)); } static void SaveThwompThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const thwomp_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteFixed(save_p, ht->crushspeed); P_WriteFixed(save_p, ht->retractspeed); P_WriteINT32(save_p, ht->direction); P_WriteFixed(save_p, ht->floorstartheight); P_WriteFixed(save_p, ht->ceilingstartheight); P_WriteINT32(save_p, ht->delay); P_WriteINT16(save_p, ht->tag); P_WriteUINT16(save_p, ht->sound); } static void SaveFloatThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const floatthink_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteINT16(save_p, ht->tag); } static void SaveEachTimeThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const eachtime_t *ht = (const void *)th; size_t i; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); for (i = 0; i < MAXPLAYERS; i++) { P_WriteChar(save_p, ht->playersInArea[i]); } P_WriteChar(save_p, ht->triggerOnExit); } static void SaveRaiseThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const raise_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT16(save_p, ht->tag); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteFixed(save_p, ht->ceilingbottom); P_WriteFixed(save_p, ht->ceilingtop); P_WriteFixed(save_p, ht->basespeed); P_WriteFixed(save_p, ht->extraspeed); P_WriteUINT8(save_p, ht->shaketimer); P_WriteUINT8(save_p, ht->flags); } static void SaveCeilingThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const ceiling_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT8(save_p, ht->type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteFixed(save_p, ht->bottomheight); P_WriteFixed(save_p, ht->topheight); P_WriteFixed(save_p, ht->speed); P_WriteFixed(save_p, ht->delay); P_WriteFixed(save_p, ht->delaytimer); P_WriteUINT8(save_p, ht->crush); P_WriteINT32(save_p, ht->texture); P_WriteINT32(save_p, ht->direction); P_WriteINT16(save_p, ht->tag); P_WriteFixed(save_p, ht->origspeed); P_WriteFixed(save_p, ht->sourceline); } static void SaveFloormoveThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const floormove_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT8(save_p, ht->type); P_WriteUINT8(save_p, ht->crush); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteINT32(save_p, ht->direction); P_WriteINT32(save_p, ht->texture); P_WriteFixed(save_p, ht->floordestheight); P_WriteFixed(save_p, ht->speed); P_WriteFixed(save_p, ht->origspeed); P_WriteFixed(save_p, ht->delay); P_WriteFixed(save_p, ht->delaytimer); P_WriteINT16(save_p, ht->tag); P_WriteFixed(save_p, ht->sourceline); } static void SaveLightflashThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const lightflash_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteINT32(save_p, ht->maxlight); P_WriteINT32(save_p, ht->minlight); } static void SaveStrobeThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const strobe_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteINT32(save_p, ht->count); P_WriteINT16(save_p, ht->minlight); P_WriteINT16(save_p, ht->maxlight); P_WriteINT32(save_p, ht->darktime); P_WriteINT32(save_p, ht->brighttime); } static void SaveGlowThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const glow_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteINT16(save_p, ht->minlight); P_WriteINT16(save_p, ht->maxlight); P_WriteINT16(save_p, ht->direction); P_WriteINT16(save_p, ht->speed); } static inline void SaveFireflickerThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const fireflicker_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteINT32(save_p, ht->count); P_WriteINT32(save_p, ht->resetcount); P_WriteINT16(save_p, ht->maxlight); P_WriteINT16(save_p, ht->minlight); } static void SaveElevatorThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const elevator_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT8(save_p, ht->type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteUINT32(save_p, SaveSector(ht->actionsector)); P_WriteINT32(save_p, ht->direction); P_WriteFixed(save_p, ht->floordestheight); P_WriteFixed(save_p, ht->ceilingdestheight); P_WriteFixed(save_p, ht->speed); P_WriteFixed(save_p, ht->origspeed); P_WriteFixed(save_p, ht->low); P_WriteFixed(save_p, ht->high); P_WriteFixed(save_p, ht->distance); P_WriteFixed(save_p, ht->delay); P_WriteFixed(save_p, ht->delaytimer); P_WriteFixed(save_p, ht->floorwasheight); P_WriteFixed(save_p, ht->ceilingwasheight); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); } static void SaveCrumbleThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const crumble_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteUINT32(save_p, SaveSector(ht->actionsector)); P_WriteUINT32(save_p, SavePlayer(ht->player)); // was dummy P_WriteINT32(save_p, ht->direction); P_WriteINT32(save_p, ht->origalpha); P_WriteINT32(save_p, ht->timer); P_WriteFixed(save_p, ht->speed); P_WriteFixed(save_p, ht->floorwasheight); P_WriteFixed(save_p, ht->ceilingwasheight); P_WriteUINT8(save_p, ht->flags); } static inline void SaveScrollThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const scroll_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteFixed(save_p, ht->dx); P_WriteFixed(save_p, ht->dy); P_WriteINT32(save_p, ht->affectee); P_WriteINT32(save_p, ht->control); P_WriteFixed(save_p, ht->last_height); P_WriteFixed(save_p, ht->vdx); P_WriteFixed(save_p, ht->vdy); P_WriteINT32(save_p, ht->accel); P_WriteINT32(save_p, ht->exclusive); P_WriteUINT8(save_p, ht->type); } static inline void SaveFrictionThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const friction_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->friction); P_WriteINT32(save_p, ht->movefactor); P_WriteINT32(save_p, ht->affectee); P_WriteINT32(save_p, ht->referrer); P_WriteUINT8(save_p, ht->roverfriction); } static inline void SavePusherThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const pusher_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT8(save_p, ht->type); P_WriteFixed(save_p, ht->x_mag); P_WriteFixed(save_p, ht->y_mag); P_WriteFixed(save_p, ht->z_mag); P_WriteINT32(save_p, ht->affectee); P_WriteUINT8(save_p, ht->roverpusher); P_WriteINT32(save_p, ht->referrer); P_WriteINT32(save_p, ht->exclusive); P_WriteINT32(save_p, ht->slider); } static void SaveLaserThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const laserthink_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT16(save_p, ht->tag); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); P_WriteUINT8(save_p, ht->nobosses); } static void SaveLightlevelThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const lightlevel_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteINT16(save_p, ht->sourcelevel); P_WriteINT16(save_p, ht->destlevel); P_WriteFixed(save_p, ht->fixedcurlevel); P_WriteFixed(save_p, ht->fixedpertic); P_WriteINT32(save_p, ht->timer); } static void SaveExecutorThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const executor_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveLine(ht->line)); P_WriteUINT32(save_p, SaveMobjnum(ht->caller)); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteINT32(save_p, ht->timer); } static void SaveDisappearThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const disappear_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, ht->appeartime); P_WriteUINT32(save_p, ht->disappeartime); P_WriteUINT32(save_p, ht->offset); P_WriteUINT32(save_p, ht->timer); P_WriteINT32(save_p, ht->affectee); P_WriteINT32(save_p, ht->sourceline); P_WriteINT32(save_p, ht->exists); } static void SaveFadeThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const fade_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, CheckAddNetColormapToList(ht->dest_exc)); P_WriteUINT32(save_p, ht->sectornum); P_WriteUINT32(save_p, ht->ffloornum); P_WriteINT32(save_p, ht->alpha); P_WriteINT16(save_p, ht->sourcevalue); P_WriteINT16(save_p, ht->destvalue); P_WriteINT16(save_p, ht->destlightlevel); P_WriteINT16(save_p, ht->speed); P_WriteUINT8(save_p, (UINT8)ht->ticbased); P_WriteINT32(save_p, ht->timer); P_WriteUINT8(save_p, ht->doexists); P_WriteUINT8(save_p, ht->dotranslucent); P_WriteUINT8(save_p, ht->dolighting); P_WriteUINT8(save_p, ht->docolormap); P_WriteUINT8(save_p, ht->docollision); P_WriteUINT8(save_p, ht->doghostfade); P_WriteUINT8(save_p, ht->exactalpha); } static void SaveFadeColormapThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const fadecolormap_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSector(ht->sector)); P_WriteUINT32(save_p, CheckAddNetColormapToList(ht->source_exc)); P_WriteUINT32(save_p, CheckAddNetColormapToList(ht->dest_exc)); P_WriteUINT8(save_p, (UINT8)ht->ticbased); P_WriteINT32(save_p, ht->duration); P_WriteINT32(save_p, ht->timer); } static void SavePlaneDisplaceThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const planedisplace_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->affectee); P_WriteINT32(save_p, ht->control); P_WriteFixed(save_p, ht->last_height); P_WriteFixed(save_p, ht->speed); P_WriteUINT8(save_p, ht->type); } static inline void SaveDynamicLineSlopeThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const dynlineplanethink_t* ht = (const void*)th; P_WriteUINT8(save_p, type); P_WriteUINT8(save_p, ht->type); P_WriteUINT32(save_p, SaveSlope(ht->slope)); P_WriteUINT32(save_p, SaveLine(ht->sourceline)); P_WriteFixed(save_p, ht->extent); } static inline void SaveDynamicVertexSlopeThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { size_t i; const dynvertexplanethink_t* ht = (const void*)th; P_WriteUINT8(save_p, type); P_WriteUINT32(save_p, SaveSlope(ht->slope)); for (i = 0; i < 3; i++) P_WriteUINT32(save_p, SaveSector(ht->secs[i])); P_WriteMem(save_p, ht->vex, sizeof(ht->vex)); P_WriteMem(save_p, ht->origsecheights, sizeof(ht->origsecheights)); P_WriteMem(save_p, ht->origvecheights, sizeof(ht->origvecheights)); P_WriteUINT8(save_p, ht->relative); } static inline void SavePolyrotatetThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const polyrotate_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->polyObjNum); P_WriteINT32(save_p, ht->speed); P_WriteINT32(save_p, ht->distance); P_WriteUINT8(save_p, ht->turnobjs); } static void SavePolymoveThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const polymove_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->polyObjNum); P_WriteINT32(save_p, ht->speed); P_WriteFixed(save_p, ht->momx); P_WriteFixed(save_p, ht->momy); P_WriteINT32(save_p, ht->distance); P_WriteAngle(save_p, ht->angle); } static void SavePolywaypointThinker(save_t *save_p, const thinker_t *th, UINT8 type) { const polywaypoint_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->polyObjNum); P_WriteINT32(save_p, ht->speed); P_WriteINT32(save_p, ht->sequence); P_WriteINT32(save_p, ht->pointnum); P_WriteINT32(save_p, ht->direction); P_WriteUINT8(save_p, ht->returnbehavior); P_WriteUINT8(save_p, ht->continuous); P_WriteUINT8(save_p, ht->stophere); } static void SavePolyslidedoorThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const polyslidedoor_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->polyObjNum); P_WriteINT32(save_p, ht->delay); P_WriteINT32(save_p, ht->delayCount); P_WriteINT32(save_p, ht->initSpeed); P_WriteINT32(save_p, ht->speed); P_WriteINT32(save_p, ht->initDistance); P_WriteINT32(save_p, ht->distance); P_WriteUINT32(save_p, ht->initAngle); P_WriteUINT32(save_p, ht->angle); P_WriteUINT32(save_p, ht->revAngle); P_WriteFixed(save_p, ht->momx); P_WriteFixed(save_p, ht->momy); P_WriteUINT8(save_p, ht->closing); } static void SavePolyswingdoorThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const polyswingdoor_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->polyObjNum); P_WriteINT32(save_p, ht->delay); P_WriteINT32(save_p, ht->delayCount); P_WriteINT32(save_p, ht->initSpeed); P_WriteINT32(save_p, ht->speed); P_WriteINT32(save_p, ht->initDistance); P_WriteINT32(save_p, ht->distance); P_WriteUINT8(save_p, ht->closing); } static void SavePolydisplaceThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const polydisplace_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->polyObjNum); P_WriteUINT32(save_p, SaveSector(ht->controlSector)); P_WriteFixed(save_p, ht->dx); P_WriteFixed(save_p, ht->dy); P_WriteFixed(save_p, ht->oldHeights); } static void SavePolyrotdisplaceThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const polyrotdisplace_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->polyObjNum); P_WriteUINT32(save_p, SaveSector(ht->controlSector)); P_WriteFixed(save_p, ht->rotscale); P_WriteUINT8(save_p, ht->turnobjs); P_WriteFixed(save_p, ht->oldHeights); } static void SavePolyfadeThinker(save_t *save_p, const thinker_t *th, const UINT8 type) { const polyfade_t *ht = (const void *)th; P_WriteUINT8(save_p, type); P_WriteINT32(save_p, ht->polyObjNum); P_WriteINT32(save_p, ht->sourcevalue); P_WriteINT32(save_p, ht->destvalue); P_WriteUINT8(save_p, (UINT8)ht->docollision); P_WriteUINT8(save_p, (UINT8)ht->doghostfade); P_WriteUINT8(save_p, (UINT8)ht->ticbased); P_WriteINT32(save_p, ht->duration); P_WriteINT32(save_p, ht->timer); } static void P_NetArchiveThinkers(save_t *save_p) { const thinker_t *th; UINT32 i; P_WriteUINT32(save_p, ARCHIVEBLOCK_THINKERS); for (i = 0; i < NUM_THINKERLISTS; i++) { UINT32 numsaved = 0; // save off the current thinkers for (th = thlist[i].next; th != &thlist[i]; th = th->next) { if (!(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed || th->function.acp1 == (actionf_p1)P_NullPrecipThinker)) numsaved++; if (th->function.acp1 == (actionf_p1)P_MobjThinker) { SaveMobjThinker(save_p, th, tc_mobj); continue; } #ifdef PARANOIA else if (th->function.acp1 == (actionf_p1)P_NullPrecipThinker); #endif else if (th->function.acp1 == (actionf_p1)T_MoveCeiling) { SaveCeilingThinker(save_p, th, tc_ceiling); continue; } else if (th->function.acp1 == (actionf_p1)T_CrushCeiling) { SaveCeilingThinker(save_p, th, tc_crushceiling); continue; } else if (th->function.acp1 == (actionf_p1)T_MoveFloor) { SaveFloormoveThinker(save_p, th, tc_floor); continue; } else if (th->function.acp1 == (actionf_p1)T_LightningFlash) { SaveLightflashThinker(save_p, th, tc_flash); continue; } else if (th->function.acp1 == (actionf_p1)T_StrobeFlash) { SaveStrobeThinker(save_p, th, tc_strobe); continue; } else if (th->function.acp1 == (actionf_p1)T_Glow) { SaveGlowThinker(save_p, th, tc_glow); continue; } else if (th->function.acp1 == (actionf_p1)T_FireFlicker) { SaveFireflickerThinker(save_p, th, tc_fireflicker); continue; } else if (th->function.acp1 == (actionf_p1)T_MoveElevator) { SaveElevatorThinker(save_p, th, tc_elevator); continue; } else if (th->function.acp1 == (actionf_p1)T_ContinuousFalling) { SaveContinuousFallThinker(save_p, th, tc_continuousfalling); continue; } else if (th->function.acp1 == (actionf_p1)T_ThwompSector) { SaveThwompThinker(save_p, th, tc_thwomp); continue; } else if (th->function.acp1 == (actionf_p1)T_NoEnemiesSector) { SaveNoEnemiesThinker(save_p, th, tc_noenemies); continue; } else if (th->function.acp1 == (actionf_p1)T_EachTimeThinker) { SaveEachTimeThinker(save_p, th, tc_eachtime); continue; } else if (th->function.acp1 == (actionf_p1)T_RaiseSector) { SaveRaiseThinker(save_p, th, tc_raisesector); continue; } else if (th->function.acp1 == (actionf_p1)T_CameraScanner) { SaveElevatorThinker(save_p, th, tc_camerascanner); continue; } else if (th->function.acp1 == (actionf_p1)T_Scroll) { SaveScrollThinker(save_p, th, tc_scroll); continue; } else if (th->function.acp1 == (actionf_p1)T_Friction) { SaveFrictionThinker(save_p, th, tc_friction); continue; } else if (th->function.acp1 == (actionf_p1)T_Pusher) { SavePusherThinker(save_p, th, tc_pusher); continue; } else if (th->function.acp1 == (actionf_p1)T_BounceCheese) { SaveBounceCheeseThinker(save_p, th, tc_bouncecheese); continue; } else if (th->function.acp1 == (actionf_p1)T_StartCrumble) { SaveCrumbleThinker(save_p, th, tc_startcrumble); continue; } else if (th->function.acp1 == (actionf_p1)T_MarioBlock) { SaveMarioBlockThinker(save_p, th, tc_marioblock); continue; } else if (th->function.acp1 == (actionf_p1)T_MarioBlockChecker) { SaveMarioCheckThinker(save_p, th, tc_marioblockchecker); continue; } else if (th->function.acp1 == (actionf_p1)T_FloatSector) { SaveFloatThinker(save_p, th, tc_floatsector); continue; } else if (th->function.acp1 == (actionf_p1)T_LaserFlash) { SaveLaserThinker(save_p, th, tc_laserflash); continue; } else if (th->function.acp1 == (actionf_p1)T_LightFade) { SaveLightlevelThinker(save_p, th, tc_lightfade); continue; } else if (th->function.acp1 == (actionf_p1)T_ExecutorDelay) { SaveExecutorThinker(save_p, th, tc_executor); continue; } else if (th->function.acp1 == (actionf_p1)T_Disappear) { SaveDisappearThinker(save_p, th, tc_disappear); continue; } else if (th->function.acp1 == (actionf_p1)T_Fade) { SaveFadeThinker(save_p, th, tc_fade); continue; } else if (th->function.acp1 == (actionf_p1)T_FadeColormap) { SaveFadeColormapThinker(save_p, th, tc_fadecolormap); continue; } else if (th->function.acp1 == (actionf_p1)T_PlaneDisplace) { SavePlaneDisplaceThinker(save_p, th, tc_planedisplace); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjRotate) { SavePolyrotatetThinker(save_p, th, tc_polyrotate); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjMove) { SavePolymoveThinker(save_p, th, tc_polymove); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjWaypoint) { SavePolywaypointThinker(save_p, th, tc_polywaypoint); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyDoorSlide) { SavePolyslidedoorThinker(save_p, th, tc_polyslidedoor); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyDoorSwing) { SavePolyswingdoorThinker(save_p, th, tc_polyswingdoor); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjFlag) { SavePolymoveThinker(save_p, th, tc_polyflag); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjDisplace) { SavePolydisplaceThinker(save_p, th, tc_polydisplace); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjRotDisplace) { SavePolyrotdisplaceThinker(save_p, th, tc_polyrotdisplace); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjFade) { SavePolyfadeThinker(save_p, th, tc_polyfade); continue; } else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeLine) { SaveDynamicLineSlopeThinker(save_p, th, tc_dynslopeline); continue; } else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeVert) { SaveDynamicVertexSlopeThinker(save_p, th, tc_dynslopevert); continue; } #ifdef PARANOIA else I_Assert(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed); // wait garbage collection #endif } CONS_Debug(DBG_NETPLAY, "%u thinkers saved in list %d\n", numsaved, i); P_WriteUINT8(save_p, tc_end); } } // Now save the pointers, tracer and target, but at load time we must // relink to this; the savegame contains the old position in the pointer // field copyed in the info field temporarily, but finally we just search // for the old position and relink to it. mobj_t *P_FindNewPosition(UINT32 oldposition) { thinker_t *th; mobj_t *mobj; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; mobj = (mobj_t *)th; if (mobj->mobjnum != oldposition) continue; return mobj; } CONS_Debug(DBG_GAMELOGIC, "mobj not found\n"); return NULL; } static inline mobj_t *LoadMobj(UINT32 mobjnum) { if (mobjnum == 0) return NULL; return (mobj_t *)(size_t)mobjnum; } static sector_t *LoadSector(UINT32 sector) { if (sector >= numsectors) return NULL; return §ors[sector]; } static line_t *LoadLine(UINT32 line) { if (line >= numlines) return NULL; return &lines[line]; } static inline player_t *LoadPlayer(UINT32 player) { if (player >= MAXPLAYERS) return NULL; return &players[player]; } static inline pslope_t *LoadSlope(UINT32 slopeid) { pslope_t *p = slopelist; if (slopeid > slopecount) return NULL; do { if (p->id == slopeid) return p; } while ((p = p->next)); return NULL; } static thinker_t* LoadMobjThinker(save_t *save_p, actionf_p1 thinker) { thinker_t *next; mobj_t *mobj; UINT32 diff; UINT32 diff2; INT32 i; fixed_t z, floorz, ceilingz; ffloor_t *floorrover = NULL, *ceilingrover = NULL; diff = P_ReadUINT32(save_p); if (diff & MD_MORE) diff2 = P_ReadUINT32(save_p); else diff2 = 0; next = (void *)(size_t)P_ReadUINT32(save_p); z = P_ReadFixed(save_p); // Force this so 3dfloor problems don't arise. floorz = P_ReadFixed(save_p); ceilingz = P_ReadFixed(save_p); if (diff2 & MD2_FLOORROVER) { sector_t *sec = LoadSector(P_ReadUINT32(save_p)); UINT16 id = P_ReadUINT16(save_p); floorrover = P_GetFFloorByID(sec, id); } if (diff2 & MD2_CEILINGROVER) { sector_t *sec = LoadSector(P_ReadUINT32(save_p)); UINT16 id = P_ReadUINT16(save_p); ceilingrover = P_GetFFloorByID(sec, id); } if (diff & MD_SPAWNPOINT) { UINT16 spawnpointnum = P_ReadUINT16(save_p); if (mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case { P_SpawnHoop(&mapthings[spawnpointnum]); return NULL; } mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); mobj->spawnpoint = &mapthings[spawnpointnum]; mapthings[spawnpointnum].mobj = mobj; } else mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); // declare this as a valid mobj as soon as possible. mobj->thinker.function.acp1 = thinker; mobj->z = z; mobj->floorz = floorz; mobj->ceilingz = ceilingz; mobj->floorrover = floorrover; mobj->ceilingrover = ceilingrover; if (diff & MD_TYPE) mobj->type = P_ReadUINT32(save_p); else { for (i = 0; i < NUMMOBJTYPES; i++) if (mobj->spawnpoint && mobj->spawnpoint->type == mobjinfo[i].doomednum) break; if (i == NUMMOBJTYPES) { if (mobj->spawnpoint) CONS_Alert(CONS_ERROR, "Found mobj with unknown map thing type %d\n", mobj->spawnpoint->type); else CONS_Alert(CONS_ERROR, "Found mobj with unknown map thing type NULL\n"); I_Error("Savegame corrupted"); } mobj->type = i; } mobj->info = &mobjinfo[mobj->type]; if (diff & MD_POS) { mobj->x = P_ReadFixed(save_p); mobj->y = P_ReadFixed(save_p); mobj->angle = P_ReadAngle(save_p); mobj->pitch = P_ReadAngle(save_p); mobj->roll = P_ReadAngle(save_p); } else { mobj->x = mobj->spawnpoint->x << FRACBITS; mobj->y = mobj->spawnpoint->y << FRACBITS; mobj->angle = FixedAngle(mobj->spawnpoint->angle*FRACUNIT); mobj->pitch = FixedAngle(mobj->spawnpoint->pitch*FRACUNIT); mobj->roll = FixedAngle(mobj->spawnpoint->roll*FRACUNIT); } if (diff & MD_MOM) { mobj->momx = P_ReadFixed(save_p); mobj->momy = P_ReadFixed(save_p); mobj->momz = P_ReadFixed(save_p); mobj->pmomz = P_ReadFixed(save_p); } // otherwise they're zero, and the memset took care of it if (diff & MD_RADIUS) mobj->radius = P_ReadFixed(save_p); else mobj->radius = mobj->info->radius; if (diff & MD_HEIGHT) mobj->height = P_ReadFixed(save_p); else mobj->height = mobj->info->height; if (diff & MD_FLAGS) mobj->flags = P_ReadUINT32(save_p); else mobj->flags = mobj->info->flags; if (diff & MD_FLAGS2) mobj->flags2 = P_ReadUINT32(save_p); if (diff & MD_HEALTH) mobj->health = P_ReadINT32(save_p); else mobj->health = mobj->info->spawnhealth; if (diff & MD_RTIME) mobj->reactiontime = P_ReadINT32(save_p); else mobj->reactiontime = mobj->info->reactiontime; if (diff & MD_STATE) mobj->state = &states[P_ReadUINT16(save_p)]; else mobj->state = &states[mobj->info->spawnstate]; if (diff & MD_TICS) mobj->tics = P_ReadINT32(save_p); else mobj->tics = mobj->state->tics; if (diff & MD_SPRITE) { mobj->sprite = P_ReadUINT16(save_p); if (mobj->sprite == SPR_PLAY) mobj->sprite2 = P_ReadUINT16(save_p); } else { mobj->sprite = mobj->state->sprite; if (mobj->sprite == SPR_PLAY) mobj->sprite2 = P_GetStateSprite2(mobj->state); } if (diff & MD_FRAME) { mobj->frame = P_ReadUINT32(save_p); mobj->anim_duration = P_ReadUINT16(save_p); } else { mobj->frame = mobj->state->frame; mobj->anim_duration = (UINT16)mobj->state->var2; } if (diff & MD_EFLAGS) mobj->eflags = P_ReadUINT16(save_p); if (diff & MD_PLAYER) { i = P_ReadUINT8(save_p); mobj->player = &players[i]; mobj->player->mo = mobj; } if (diff & MD_MOVEDIR) mobj->movedir = P_ReadAngle(save_p); if (diff & MD_MOVECOUNT) mobj->movecount = P_ReadINT32(save_p); if (diff & MD_THRESHOLD) mobj->threshold = P_ReadINT32(save_p); if (diff & MD_LASTLOOK) mobj->lastlook = P_ReadINT32(save_p); else mobj->lastlook = -1; if (diff & MD_TARGET) mobj->target = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (diff & MD_TRACER) mobj->tracer = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (diff & MD_FRICTION) mobj->friction = P_ReadFixed(save_p); else mobj->friction = ORIG_FRICTION; if (diff & MD_MOVEFACTOR) mobj->movefactor = P_ReadFixed(save_p); else mobj->movefactor = FRACUNIT; if (diff & MD_FUSE) mobj->fuse = P_ReadINT32(save_p); if (diff & MD_WATERTOP) mobj->watertop = P_ReadFixed(save_p); if (diff & MD_WATERBOTTOM) mobj->waterbottom = P_ReadFixed(save_p); if (diff & MD_SCALE) mobj->scale = P_ReadFixed(save_p); else mobj->scale = FRACUNIT; if (diff & MD_DSCALE) mobj->destscale = P_ReadFixed(save_p); else mobj->destscale = mobj->scale; if (diff2 & MD2_SCALESPEED) mobj->scalespeed = P_ReadFixed(save_p); else mobj->scalespeed = FRACUNIT/12; if (diff2 & MD2_CUSVAL) mobj->cusval = P_ReadINT32(save_p); if (diff2 & MD2_CVMEM) mobj->cvmem = P_ReadINT32(save_p); if (diff2 & MD2_SKIN) mobj->skin = skins[P_ReadUINT8(save_p)]; if (diff2 & MD2_COLOR) mobj->color = P_ReadUINT16(save_p); if (diff2 & MD2_EXTVAL1) mobj->extravalue1 = P_ReadINT32(save_p); if (diff2 & MD2_EXTVAL2) mobj->extravalue2 = P_ReadINT32(save_p); if (diff2 & MD2_HNEXT) mobj->hnext = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (diff2 & MD2_HPREV) mobj->hprev = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (diff2 & MD2_SLOPE) mobj->standingslope = P_SlopeById(P_ReadUINT16(save_p)); if (diff2 & MD2_COLORIZED) mobj->colorized = P_ReadUINT8(save_p); if (diff2 & MD2_MIRRORED) mobj->mirrored = P_ReadUINT8(save_p); if (diff2 & MD2_SPRITEROLL) mobj->spriteroll = P_ReadAngle(save_p); if (diff2 & MD2_SHADOWSCALE) mobj->shadowscale = P_ReadFixed(save_p); if (diff2 & MD2_RENDERFLAGS) mobj->renderflags = P_ReadUINT32(save_p); if (diff2 & MD2_BLENDMODE) mobj->blendmode = P_ReadINT32(save_p); else mobj->blendmode = AST_TRANSLUCENT; if (diff2 & MD2_SPRITEXSCALE) mobj->spritexscale = P_ReadFixed(save_p); else mobj->spritexscale = FRACUNIT; if (diff2 & MD2_SPRITEYSCALE) mobj->spriteyscale = P_ReadFixed(save_p); else mobj->spriteyscale = FRACUNIT; if (diff2 & MD2_SPRITEXOFFSET) mobj->spritexoffset = P_ReadFixed(save_p); if (diff2 & MD2_SPRITEYOFFSET) mobj->spriteyoffset = P_ReadFixed(save_p); if (diff2 & MD2_FLOORSPRITESLOPE) { pslope_t *slope = (pslope_t *)P_CreateFloorSpriteSlope(mobj); slope->zdelta = P_ReadFixed(save_p); slope->zangle = P_ReadAngle(save_p); slope->xydirection = P_ReadAngle(save_p); slope->o.x = P_ReadFixed(save_p); slope->o.y = P_ReadFixed(save_p); slope->o.z = P_ReadFixed(save_p); slope->d.x = P_ReadFixed(save_p); slope->d.y = P_ReadFixed(save_p); slope->normal.x = P_ReadFixed(save_p); slope->normal.y = P_ReadFixed(save_p); slope->normal.z = P_ReadFixed(save_p); slope->moved = true; } if (diff2 & MD2_DRAWONLYFORPLAYER) mobj->drawonlyforplayer = &players[P_ReadUINT8(save_p)]; if (diff2 & MD2_DONTDRAWFORVIEWMOBJ) mobj->dontdrawforviewmobj = (mobj_t *)(size_t)P_ReadUINT32(save_p); if (diff2 & MD2_DISPOFFSET) mobj->dispoffset = P_ReadINT32(save_p); else mobj->dispoffset = mobj->info->dispoffset; if (diff2 & MD2_TRANSLATION) mobj->translation = P_ReadUINT16(save_p); if (diff2 & MD2_ALPHA) mobj->alpha = P_ReadFixed(save_p); else mobj->alpha = FRACUNIT; if (diff & MD_REDFLAG) { redflag = mobj; rflagpoint = mobj->spawnpoint; } if (diff & MD_BLUEFLAG) { blueflag = mobj; bflagpoint = mobj->spawnpoint; } // set sprev, snext, bprev, bnext, subsector P_SetThingPosition(mobj); mobj->mobjnum = P_ReadUINT32(save_p); if (mobj->player) { if (mobj->eflags & MFE_VERTICALFLIP) mobj->player->viewz = mobj->z + mobj->height - mobj->player->viewheight; else mobj->player->viewz = mobj->player->mo->z + mobj->player->viewheight; } if (mobj->type == MT_SKYBOX && mobj->spawnpoint) { mtag_t tag = Tag_FGet(&mobj->spawnpoint->tags); if (tag >= 0 && tag <= 15) { if (mobj->spawnpoint->args[0]) skyboxcenterpnts[tag] = mobj; else skyboxviewpnts[tag] = mobj; } } mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function R_AddMobjInterpolator(mobj); return &mobj->thinker; } static thinker_t* LoadNoEnemiesThinker(save_t *save_p, actionf_p1 thinker) { noenemies_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(P_ReadUINT32(save_p)); return &ht->thinker; } static thinker_t* LoadBounceCheeseThinker(save_t *save_p, actionf_p1 thinker) { bouncecheese_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(P_ReadUINT32(save_p)); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->speed = P_ReadFixed(save_p); ht->distance = P_ReadFixed(save_p); ht->floorwasheight = P_ReadFixed(save_p); ht->ceilingwasheight = P_ReadFixed(save_p); ht->low = P_ReadChar(save_p); if (ht->sector) ht->sector->ceilingdata = ht; return &ht->thinker; } static thinker_t* LoadContinuousFallThinker(save_t *save_p, actionf_p1 thinker) { continuousfall_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->speed = P_ReadFixed(save_p); ht->direction = P_ReadINT32(save_p); ht->floorstartheight = P_ReadFixed(save_p); ht->ceilingstartheight = P_ReadFixed(save_p); ht->destheight = P_ReadFixed(save_p); if (ht->sector) { ht->sector->ceilingdata = ht; ht->sector->floordata = ht; } return &ht->thinker; } static thinker_t* LoadMarioBlockThinker(save_t *save_p, actionf_p1 thinker) { mariothink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->speed = P_ReadFixed(save_p); ht->direction = P_ReadINT32(save_p); ht->floorstartheight = P_ReadFixed(save_p); ht->ceilingstartheight = P_ReadFixed(save_p); ht->tag = P_ReadINT16(save_p); if (ht->sector) { ht->sector->ceilingdata = ht; ht->sector->floordata = ht; } return &ht->thinker; } static thinker_t* LoadMarioCheckThinker(save_t *save_p, actionf_p1 thinker) { mariocheck_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(P_ReadUINT32(save_p)); ht->sector = LoadSector(P_ReadUINT32(save_p)); return &ht->thinker; } static thinker_t* LoadThwompThinker(save_t *save_p, actionf_p1 thinker) { thwomp_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(P_ReadUINT32(save_p)); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->crushspeed = P_ReadFixed(save_p); ht->retractspeed = P_ReadFixed(save_p); ht->direction = P_ReadINT32(save_p); ht->floorstartheight = P_ReadFixed(save_p); ht->ceilingstartheight = P_ReadFixed(save_p); ht->delay = P_ReadINT32(save_p); ht->tag = P_ReadINT16(save_p); ht->sound = P_ReadUINT16(save_p); if (ht->sector) { ht->sector->ceilingdata = ht; ht->sector->floordata = ht; } return &ht->thinker; } static thinker_t* LoadFloatThinker(save_t *save_p, actionf_p1 thinker) { floatthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(P_ReadUINT32(save_p)); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->tag = P_ReadINT16(save_p); return &ht->thinker; } static thinker_t* LoadEachTimeThinker(save_t *save_p, actionf_p1 thinker) { size_t i; eachtime_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(P_ReadUINT32(save_p)); for (i = 0; i < MAXPLAYERS; i++) { ht->playersInArea[i] = P_ReadChar(save_p); } ht->triggerOnExit = P_ReadChar(save_p); return &ht->thinker; } static thinker_t* LoadRaiseThinker(save_t *save_p, actionf_p1 thinker) { raise_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->tag = P_ReadINT16(save_p); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->ceilingbottom = P_ReadFixed(save_p); ht->ceilingtop = P_ReadFixed(save_p); ht->basespeed = P_ReadFixed(save_p); ht->extraspeed = P_ReadFixed(save_p); ht->shaketimer = P_ReadUINT8(save_p); ht->flags = P_ReadUINT8(save_p); return &ht->thinker; } static thinker_t* LoadCeilingThinker(save_t *save_p, actionf_p1 thinker) { ceiling_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->type = P_ReadUINT8(save_p); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->bottomheight = P_ReadFixed(save_p); ht->topheight = P_ReadFixed(save_p); ht->speed = P_ReadFixed(save_p); ht->delay = P_ReadFixed(save_p); ht->delaytimer = P_ReadFixed(save_p); ht->crush = P_ReadUINT8(save_p); ht->texture = P_ReadINT32(save_p); ht->direction = P_ReadINT32(save_p); ht->tag = P_ReadINT16(save_p); ht->origspeed = P_ReadFixed(save_p); ht->sourceline = P_ReadFixed(save_p); if (ht->sector) ht->sector->ceilingdata = ht; return &ht->thinker; } static thinker_t* LoadFloormoveThinker(save_t *save_p, actionf_p1 thinker) { floormove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->type = P_ReadUINT8(save_p); ht->crush = P_ReadUINT8(save_p); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->direction = P_ReadINT32(save_p); ht->texture = P_ReadINT32(save_p); ht->floordestheight = P_ReadFixed(save_p); ht->speed = P_ReadFixed(save_p); ht->origspeed = P_ReadFixed(save_p); ht->delay = P_ReadFixed(save_p); ht->delaytimer = P_ReadFixed(save_p); ht->tag = P_ReadINT16(save_p); ht->sourceline = P_ReadFixed(save_p); if (ht->sector) ht->sector->floordata = ht; return &ht->thinker; } static thinker_t* LoadLightflashThinker(save_t *save_p, actionf_p1 thinker) { lightflash_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->maxlight = P_ReadINT32(save_p); ht->minlight = P_ReadINT32(save_p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } static thinker_t* LoadStrobeThinker(save_t *save_p, actionf_p1 thinker) { strobe_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->count = P_ReadINT32(save_p); ht->minlight = P_ReadINT16(save_p); ht->maxlight = P_ReadINT16(save_p); ht->darktime = P_ReadINT32(save_p); ht->brighttime = P_ReadINT32(save_p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } static thinker_t* LoadGlowThinker(save_t *save_p, actionf_p1 thinker) { glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->minlight = P_ReadINT16(save_p); ht->maxlight = P_ReadINT16(save_p); ht->direction = P_ReadINT16(save_p); ht->speed = P_ReadINT16(save_p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } static thinker_t* LoadFireflickerThinker(save_t *save_p, actionf_p1 thinker) { fireflicker_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->count = P_ReadINT32(save_p); ht->resetcount = P_ReadINT32(save_p); ht->maxlight = P_ReadINT16(save_p); ht->minlight = P_ReadINT16(save_p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } static thinker_t* LoadElevatorThinker(save_t *save_p, actionf_p1 thinker, boolean setplanedata) { elevator_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->type = P_ReadUINT8(save_p); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->actionsector = LoadSector(P_ReadUINT32(save_p)); ht->direction = P_ReadINT32(save_p); ht->floordestheight = P_ReadFixed(save_p); ht->ceilingdestheight = P_ReadFixed(save_p); ht->speed = P_ReadFixed(save_p); ht->origspeed = P_ReadFixed(save_p); ht->low = P_ReadFixed(save_p); ht->high = P_ReadFixed(save_p); ht->distance = P_ReadFixed(save_p); ht->delay = P_ReadFixed(save_p); ht->delaytimer = P_ReadFixed(save_p); ht->floorwasheight = P_ReadFixed(save_p); ht->ceilingwasheight = P_ReadFixed(save_p); ht->sourceline = LoadLine(P_ReadUINT32(save_p)); if (ht->sector && setplanedata) { ht->sector->ceilingdata = ht; ht->sector->floordata = ht; } return &ht->thinker; } static thinker_t* LoadCrumbleThinker(save_t *save_p, actionf_p1 thinker) { crumble_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(P_ReadUINT32(save_p)); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->actionsector = LoadSector(P_ReadUINT32(save_p)); ht->player = LoadPlayer(P_ReadUINT32(save_p)); ht->direction = P_ReadINT32(save_p); ht->origalpha = P_ReadINT32(save_p); ht->timer = P_ReadINT32(save_p); ht->speed = P_ReadFixed(save_p); ht->floorwasheight = P_ReadFixed(save_p); ht->ceilingwasheight = P_ReadFixed(save_p); ht->flags = P_ReadUINT8(save_p); if (ht->sector) ht->sector->floordata = ht; return &ht->thinker; } static thinker_t* LoadScrollThinker(save_t *save_p, actionf_p1 thinker) { scroll_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->dx = P_ReadFixed(save_p); ht->dy = P_ReadFixed(save_p); ht->affectee = P_ReadINT32(save_p); ht->control = P_ReadINT32(save_p); ht->last_height = P_ReadFixed(save_p); ht->vdx = P_ReadFixed(save_p); ht->vdy = P_ReadFixed(save_p); ht->accel = P_ReadINT32(save_p); ht->exclusive = P_ReadINT32(save_p); ht->type = P_ReadUINT8(save_p); return &ht->thinker; } static inline thinker_t* LoadFrictionThinker(save_t *save_p, actionf_p1 thinker) { friction_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->friction = P_ReadINT32(save_p); ht->movefactor = P_ReadINT32(save_p); ht->affectee = P_ReadINT32(save_p); ht->referrer = P_ReadINT32(save_p); ht->roverfriction = P_ReadUINT8(save_p); return &ht->thinker; } static thinker_t* LoadPusherThinker(save_t *save_p, actionf_p1 thinker) { pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->type = P_ReadUINT8(save_p); ht->x_mag = P_ReadFixed(save_p); ht->y_mag = P_ReadFixed(save_p); ht->z_mag = P_ReadFixed(save_p); ht->affectee = P_ReadINT32(save_p); ht->roverpusher = P_ReadUINT8(save_p); ht->referrer = P_ReadINT32(save_p); ht->exclusive = P_ReadINT32(save_p); ht->slider = P_ReadINT32(save_p); return &ht->thinker; } static inline thinker_t* LoadLaserThinker(save_t *save_p, actionf_p1 thinker) { laserthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->tag = P_ReadINT16(save_p); ht->sourceline = LoadLine(P_ReadUINT32(save_p)); ht->nobosses = P_ReadUINT8(save_p); return &ht->thinker; } static inline thinker_t* LoadLightlevelThinker(save_t *save_p, actionf_p1 thinker) { lightlevel_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->sourcelevel = P_ReadINT16(save_p); ht->destlevel = P_ReadINT16(save_p); ht->fixedcurlevel = P_ReadFixed(save_p); ht->fixedpertic = P_ReadFixed(save_p); ht->timer = P_ReadINT32(save_p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } static inline thinker_t* LoadExecutorThinker(save_t *save_p, actionf_p1 thinker) { executor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->line = LoadLine(P_ReadUINT32(save_p)); ht->caller = LoadMobj(P_ReadUINT32(save_p)); ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->timer = P_ReadINT32(save_p); return &ht->thinker; } static inline thinker_t* LoadDisappearThinker(save_t *save_p, actionf_p1 thinker) { disappear_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->appeartime = P_ReadUINT32(save_p); ht->disappeartime = P_ReadUINT32(save_p); ht->offset = P_ReadUINT32(save_p); ht->timer = P_ReadUINT32(save_p); ht->affectee = P_ReadINT32(save_p); ht->sourceline = P_ReadINT32(save_p); ht->exists = P_ReadINT32(save_p); return &ht->thinker; } static inline thinker_t* LoadFadeThinker(save_t *save_p, actionf_p1 thinker) { sector_t *ss; fade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->dest_exc = GetNetColormapFromList(P_ReadUINT32(save_p)); ht->sectornum = P_ReadUINT32(save_p); ht->ffloornum = P_ReadUINT32(save_p); ht->alpha = P_ReadINT32(save_p); ht->sourcevalue = P_ReadINT16(save_p); ht->destvalue = P_ReadINT16(save_p); ht->destlightlevel = P_ReadINT16(save_p); ht->speed = P_ReadINT16(save_p); ht->ticbased = (boolean)P_ReadUINT8(save_p); ht->timer = P_ReadINT32(save_p); ht->doexists = P_ReadUINT8(save_p); ht->dotranslucent = P_ReadUINT8(save_p); ht->dolighting = P_ReadUINT8(save_p); ht->docolormap = P_ReadUINT8(save_p); ht->docollision = P_ReadUINT8(save_p); ht->doghostfade = P_ReadUINT8(save_p); ht->exactalpha = P_ReadUINT8(save_p); ss = LoadSector(ht->sectornum); if (ss) { size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc ffloor_t *rover; for (rover = ss->ffloors; rover; rover = rover->next) { if (j == ht->ffloornum) { ht->rover = rover; rover->fadingdata = ht; break; } j++; } } return &ht->thinker; } static inline thinker_t* LoadFadeColormapThinker(save_t *save_p, actionf_p1 thinker) { fadecolormap_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(P_ReadUINT32(save_p)); ht->source_exc = GetNetColormapFromList(P_ReadUINT32(save_p)); ht->dest_exc = GetNetColormapFromList(P_ReadUINT32(save_p)); ht->ticbased = (boolean)P_ReadUINT8(save_p); ht->duration = P_ReadINT32(save_p); ht->timer = P_ReadINT32(save_p); if (ht->sector) ht->sector->fadecolormapdata = ht; return &ht->thinker; } static inline thinker_t* LoadPlaneDisplaceThinker(save_t *save_p, actionf_p1 thinker) { planedisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->affectee = P_ReadINT32(save_p); ht->control = P_ReadINT32(save_p); ht->last_height = P_ReadFixed(save_p); ht->speed = P_ReadFixed(save_p); ht->type = P_ReadUINT8(save_p); return &ht->thinker; } static inline thinker_t* LoadDynamicLineSlopeThinker(save_t *save_p, actionf_p1 thinker) { dynlineplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->type = P_ReadUINT8(save_p); ht->slope = LoadSlope(P_ReadUINT32(save_p)); ht->sourceline = LoadLine(P_ReadUINT32(save_p)); ht->extent = P_ReadFixed(save_p); return &ht->thinker; } static inline thinker_t* LoadDynamicVertexSlopeThinker(save_t *save_p, actionf_p1 thinker) { size_t i; dynvertexplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->slope = LoadSlope(P_ReadUINT32(save_p)); for (i = 0; i < 3; i++) ht->secs[i] = LoadSector(P_ReadUINT32(save_p)); P_ReadMem(save_p, ht->vex, sizeof(ht->vex)); P_ReadMem(save_p, ht->origsecheights, sizeof(ht->origsecheights)); P_ReadMem(save_p, ht->origvecheights, sizeof(ht->origvecheights)); ht->relative = P_ReadUINT8(save_p); return &ht->thinker; } static inline thinker_t* LoadPolyrotatetThinker(save_t *save_p, actionf_p1 thinker) { polyrotate_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->polyObjNum = P_ReadINT32(save_p); ht->speed = P_ReadINT32(save_p); ht->distance = P_ReadINT32(save_p); ht->turnobjs = P_ReadUINT8(save_p); return &ht->thinker; } static thinker_t* LoadPolymoveThinker(save_t *save_p, actionf_p1 thinker) { polymove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->polyObjNum = P_ReadINT32(save_p); ht->speed = P_ReadINT32(save_p); ht->momx = P_ReadFixed(save_p); ht->momy = P_ReadFixed(save_p); ht->distance = P_ReadINT32(save_p); ht->angle = P_ReadAngle(save_p); return &ht->thinker; } static inline thinker_t* LoadPolywaypointThinker(save_t *save_p, actionf_p1 thinker) { polywaypoint_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->polyObjNum = P_ReadINT32(save_p); ht->speed = P_ReadINT32(save_p); ht->sequence = P_ReadINT32(save_p); ht->pointnum = P_ReadINT32(save_p); ht->direction = P_ReadINT32(save_p); ht->returnbehavior = P_ReadUINT8(save_p); ht->continuous = P_ReadUINT8(save_p); ht->stophere = P_ReadUINT8(save_p); return &ht->thinker; } static inline thinker_t* LoadPolyslidedoorThinker(save_t *save_p, actionf_p1 thinker) { polyslidedoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->polyObjNum = P_ReadINT32(save_p); ht->delay = P_ReadINT32(save_p); ht->delayCount = P_ReadINT32(save_p); ht->initSpeed = P_ReadINT32(save_p); ht->speed = P_ReadINT32(save_p); ht->initDistance = P_ReadINT32(save_p); ht->distance = P_ReadINT32(save_p); ht->initAngle = P_ReadUINT32(save_p); ht->angle = P_ReadUINT32(save_p); ht->revAngle = P_ReadUINT32(save_p); ht->momx = P_ReadFixed(save_p); ht->momy = P_ReadFixed(save_p); ht->closing = P_ReadUINT8(save_p); return &ht->thinker; } static inline thinker_t* LoadPolyswingdoorThinker(save_t *save_p, actionf_p1 thinker) { polyswingdoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->polyObjNum = P_ReadINT32(save_p); ht->delay = P_ReadINT32(save_p); ht->delayCount = P_ReadINT32(save_p); ht->initSpeed = P_ReadINT32(save_p); ht->speed = P_ReadINT32(save_p); ht->initDistance = P_ReadINT32(save_p); ht->distance = P_ReadINT32(save_p); ht->closing = P_ReadUINT8(save_p); return &ht->thinker; } static inline thinker_t* LoadPolydisplaceThinker(save_t *save_p, actionf_p1 thinker) { polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->polyObjNum = P_ReadINT32(save_p); ht->controlSector = LoadSector(P_ReadUINT32(save_p)); ht->dx = P_ReadFixed(save_p); ht->dy = P_ReadFixed(save_p); ht->oldHeights = P_ReadFixed(save_p); return &ht->thinker; } static inline thinker_t* LoadPolyrotdisplaceThinker(save_t *save_p, actionf_p1 thinker) { polyrotdisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->polyObjNum = P_ReadINT32(save_p); ht->controlSector = LoadSector(P_ReadUINT32(save_p)); ht->rotscale = P_ReadFixed(save_p); ht->turnobjs = P_ReadUINT8(save_p); ht->oldHeights = P_ReadFixed(save_p); return &ht->thinker; } static thinker_t* LoadPolyfadeThinker(save_t *save_p, actionf_p1 thinker) { polyfade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; ht->polyObjNum = P_ReadINT32(save_p); ht->sourcevalue = P_ReadINT32(save_p); ht->destvalue = P_ReadINT32(save_p); ht->docollision = (boolean)P_ReadUINT8(save_p); ht->doghostfade = (boolean)P_ReadUINT8(save_p); ht->ticbased = (boolean)P_ReadUINT8(save_p); ht->duration = P_ReadINT32(save_p); ht->timer = P_ReadINT32(save_p); return &ht->thinker; } static void P_NetUnArchiveThinkers(save_t *save_p) { thinker_t *currentthinker; thinker_t *next; UINT8 tclass; UINT8 restoreNum = false; UINT32 i; UINT32 numloaded = 0; if (P_ReadUINT32(save_p) != ARCHIVEBLOCK_THINKERS) I_Error("Bad $$$.sav at archive block Thinkers"); // remove all the current thinkers for (i = 0; i < NUM_THINKERLISTS; i++) { currentthinker = thlist[i].next; for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = next) { next = currentthinker->next; if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker || currentthinker->function.acp1 == (actionf_p1)P_NullPrecipThinker) P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it else { (next->prev = currentthinker->prev)->next = next; R_DestroyLevelInterpolators(currentthinker); Z_Free(currentthinker); } } } // we don't want the removed mobjs to come back iquetail = iquehead = 0; P_InitThinkers(); // clear sector thinker pointers so they don't point to non-existant thinkers for all of eternity for (i = 0; i < numsectors; i++) { sectors[i].floordata = sectors[i].ceilingdata = sectors[i].lightingdata = sectors[i].fadecolormapdata = NULL; } // read in saved thinkers for (i = 0; i < NUM_THINKERLISTS; i++) { for (;;) { thinker_t* th = NULL; tclass = P_ReadUINT8(save_p); if (tclass == tc_end) break; // leave the saved thinker reading loop numloaded++; switch (tclass) { case tc_mobj: th = LoadMobjThinker(save_p, (actionf_p1)P_MobjThinker); break; case tc_ceiling: th = LoadCeilingThinker(save_p, (actionf_p1)T_MoveCeiling); break; case tc_crushceiling: th = LoadCeilingThinker(save_p, (actionf_p1)T_CrushCeiling); break; case tc_floor: th = LoadFloormoveThinker(save_p, (actionf_p1)T_MoveFloor); break; case tc_flash: th = LoadLightflashThinker(save_p, (actionf_p1)T_LightningFlash); break; case tc_strobe: th = LoadStrobeThinker(save_p, (actionf_p1)T_StrobeFlash); break; case tc_glow: th = LoadGlowThinker(save_p, (actionf_p1)T_Glow); break; case tc_fireflicker: th = LoadFireflickerThinker(save_p, (actionf_p1)T_FireFlicker); break; case tc_elevator: th = LoadElevatorThinker(save_p, (actionf_p1)T_MoveElevator, true); break; case tc_continuousfalling: th = LoadContinuousFallThinker(save_p, (actionf_p1)T_ContinuousFalling); break; case tc_thwomp: th = LoadThwompThinker(save_p, (actionf_p1)T_ThwompSector); break; case tc_noenemies: th = LoadNoEnemiesThinker(save_p, (actionf_p1)T_NoEnemiesSector); break; case tc_eachtime: th = LoadEachTimeThinker(save_p, (actionf_p1)T_EachTimeThinker); break; case tc_raisesector: th = LoadRaiseThinker(save_p, (actionf_p1)T_RaiseSector); break; case tc_camerascanner: th = LoadElevatorThinker(save_p, (actionf_p1)T_CameraScanner, false); break; case tc_bouncecheese: th = LoadBounceCheeseThinker(save_p, (actionf_p1)T_BounceCheese); break; case tc_startcrumble: th = LoadCrumbleThinker(save_p, (actionf_p1)T_StartCrumble); break; case tc_marioblock: th = LoadMarioBlockThinker(save_p, (actionf_p1)T_MarioBlock); break; case tc_marioblockchecker: th = LoadMarioCheckThinker(save_p, (actionf_p1)T_MarioBlockChecker); break; case tc_floatsector: th = LoadFloatThinker(save_p, (actionf_p1)T_FloatSector); break; case tc_laserflash: th = LoadLaserThinker(save_p, (actionf_p1)T_LaserFlash); break; case tc_lightfade: th = LoadLightlevelThinker(save_p, (actionf_p1)T_LightFade); break; case tc_executor: th = LoadExecutorThinker(save_p, (actionf_p1)T_ExecutorDelay); restoreNum = true; break; case tc_disappear: th = LoadDisappearThinker(save_p, (actionf_p1)T_Disappear); break; case tc_fade: th = LoadFadeThinker(save_p, (actionf_p1)T_Fade); break; case tc_fadecolormap: th = LoadFadeColormapThinker(save_p, (actionf_p1)T_FadeColormap); break; case tc_planedisplace: th = LoadPlaneDisplaceThinker(save_p, (actionf_p1)T_PlaneDisplace); break; case tc_polyrotate: th = LoadPolyrotatetThinker(save_p, (actionf_p1)T_PolyObjRotate); break; case tc_polymove: th = LoadPolymoveThinker(save_p, (actionf_p1)T_PolyObjMove); break; case tc_polywaypoint: th = LoadPolywaypointThinker(save_p, (actionf_p1)T_PolyObjWaypoint); break; case tc_polyslidedoor: th = LoadPolyslidedoorThinker(save_p, (actionf_p1)T_PolyDoorSlide); break; case tc_polyswingdoor: th = LoadPolyswingdoorThinker(save_p, (actionf_p1)T_PolyDoorSwing); break; case tc_polyflag: th = LoadPolymoveThinker(save_p, (actionf_p1)T_PolyObjFlag); break; case tc_polydisplace: th = LoadPolydisplaceThinker(save_p, (actionf_p1)T_PolyObjDisplace); break; case tc_polyrotdisplace: th = LoadPolyrotdisplaceThinker(save_p, (actionf_p1)T_PolyObjRotDisplace); break; case tc_polyfade: th = LoadPolyfadeThinker(save_p, (actionf_p1)T_PolyObjFade); break; case tc_dynslopeline: th = LoadDynamicLineSlopeThinker(save_p, (actionf_p1)T_DynamicSlopeLine); break; case tc_dynslopevert: th = LoadDynamicVertexSlopeThinker(save_p, (actionf_p1)T_DynamicSlopeVert); break; case tc_scroll: th = LoadScrollThinker(save_p, (actionf_p1)T_Scroll); break; case tc_friction: th = LoadFrictionThinker(save_p, (actionf_p1)T_Friction); break; case tc_pusher: th = LoadPusherThinker(save_p, (actionf_p1)T_Pusher); break; default: I_Error("P_UnarchiveSpecials: Unknown tclass %d in savegame", tclass); } if (th) P_AddThinker(i, th); } CONS_Debug(DBG_NETPLAY, "%u thinkers loaded in list %d\n", numloaded, i); } // Set each skyboxmo to the first skybox (or NULL) skyboxmo[0] = skyboxviewpnts[0]; skyboxmo[1] = skyboxcenterpnts[0]; if (restoreNum) { executor_t *delay = NULL; UINT32 mobjnum; for (currentthinker = thlist[THINK_MAIN].next; currentthinker != &thlist[THINK_MAIN]; currentthinker = currentthinker->next) { if (currentthinker->function.acp1 != (actionf_p1)T_ExecutorDelay) continue; delay = (void *)currentthinker; if (!(mobjnum = (UINT32)(size_t)delay->caller)) continue; delay->caller = P_FindNewPosition(mobjnum); } } } /////////////////////////////////////////////////////////////////////////////// // // haleyjd 03/26/06: PolyObject saving code // #define PD_FLAGS 0x01 #define PD_TRANS 0x02 static inline void P_ArchivePolyObj(save_t *save_p, polyobj_t *po) { UINT8 diff = 0; P_WriteINT32(save_p, po->id); P_WriteAngle(save_p, po->angle); P_WriteFixed(save_p, po->spawnSpot.x); P_WriteFixed(save_p, po->spawnSpot.y); if (po->flags != po->spawnflags) diff |= PD_FLAGS; if (po->translucency != po->spawntrans) diff |= PD_TRANS; P_WriteUINT8(save_p, diff); if (diff & PD_FLAGS) P_WriteINT32(save_p, po->flags); if (diff & PD_TRANS) P_WriteINT32(save_p, po->translucency); } static inline void P_UnArchivePolyObj(save_t *save_p, polyobj_t *po) { INT32 id; UINT32 angle; fixed_t x, y; UINT8 diff; // nullify all polyobject thinker pointers; // the thinkers themselves will fight over who gets the field // when they first start to run. po->thinker = NULL; id = P_ReadINT32(save_p); angle = P_ReadAngle(save_p); x = P_ReadFixed(save_p); y = P_ReadFixed(save_p); diff = P_ReadUINT8(save_p); if (diff & PD_FLAGS) po->flags = P_ReadINT32(save_p); if (diff & PD_TRANS) po->translucency = P_ReadINT32(save_p); // if the object is bad or isn't in the id hash, we can do nothing more // with it, so return now if (po->isBad || po != Polyobj_GetForNum(id)) return; // rotate and translate polyobject Polyobj_MoveOnLoad(po, angle, x, y); } static inline void P_ArchivePolyObjects(save_t *save_p) { INT32 i; P_WriteUINT32(save_p, ARCHIVEBLOCK_POBJS); // save number of polyobjects P_WriteINT32(save_p, numPolyObjects); for (i = 0; i < numPolyObjects; ++i) P_ArchivePolyObj(save_p, &PolyObjects[i]); } static inline void P_UnArchivePolyObjects(save_t *save_p) { INT32 i, numSavedPolys; if (P_ReadUINT32(save_p) != ARCHIVEBLOCK_POBJS) I_Error("Bad $$$.sav at archive block Pobjs"); numSavedPolys = P_ReadINT32(save_p); if (numSavedPolys != numPolyObjects) I_Error("P_UnArchivePolyObjects: polyobj count inconsistency\n"); for (i = 0; i < numSavedPolys; ++i) P_UnArchivePolyObj(save_p, &PolyObjects[i]); } static inline void P_FinishMobjs(void) { thinker_t *currentthinker; mobj_t *mobj; // put info field there real value for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; currentthinker = currentthinker->next) { if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; mobj = (mobj_t *)currentthinker; mobj->info = &mobjinfo[mobj->type]; } } static void P_RelinkPointers(void) { thinker_t *currentthinker; mobj_t *mobj; UINT32 temp; // use info field (value = oldposition) to relink mobjs for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; currentthinker = currentthinker->next) { if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; mobj = (mobj_t *)currentthinker; if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER) continue; if (mobj->dontdrawforviewmobj) { temp = (UINT32)(size_t)mobj->dontdrawforviewmobj; mobj->dontdrawforviewmobj = NULL; if (!P_SetTarget(&mobj->dontdrawforviewmobj, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "dontdrawforviewmobj not found on %d\n", mobj->type); } if (mobj->tracer) { temp = (UINT32)(size_t)mobj->tracer; mobj->tracer = NULL; if (!P_SetTarget(&mobj->tracer, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "tracer not found on %d\n", mobj->type); } if (mobj->target) { temp = (UINT32)(size_t)mobj->target; mobj->target = NULL; if (!P_SetTarget(&mobj->target, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "target not found on %d\n", mobj->type); } if (mobj->hnext) { temp = (UINT32)(size_t)mobj->hnext; mobj->hnext = NULL; if (!(mobj->hnext = P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "hnext not found on %d\n", mobj->type); } if (mobj->hprev) { temp = (UINT32)(size_t)mobj->hprev; mobj->hprev = NULL; if (!(mobj->hprev = P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type); } if (mobj->player && mobj->player->capsule) { temp = (UINT32)(size_t)mobj->player->capsule; mobj->player->capsule = NULL; if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type); } if (mobj->player && mobj->player->axis1) { temp = (UINT32)(size_t)mobj->player->axis1; mobj->player->axis1 = NULL; if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type); } if (mobj->player && mobj->player->axis2) { temp = (UINT32)(size_t)mobj->player->axis2; mobj->player->axis2 = NULL; if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type); } if (mobj->player && mobj->player->awayviewmobj) { temp = (UINT32)(size_t)mobj->player->awayviewmobj; mobj->player->awayviewmobj = NULL; if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); } if (mobj->player && mobj->player->followmobj) { temp = (UINT32)(size_t)mobj->player->followmobj; mobj->player->followmobj = NULL; if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type); } if (mobj->player && mobj->player->drone) { temp = (UINT32)(size_t)mobj->player->drone; mobj->player->drone = NULL; if (!P_SetTarget(&mobj->player->drone, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "drone not found on %d\n", mobj->type); } } } static inline void P_NetArchiveSpecials(save_t *save_p) { size_t i, z; P_WriteUINT32(save_p, ARCHIVEBLOCK_SPECIALS); // itemrespawn queue for deathmatch i = iquetail; while (iquehead != i) { for (z = 0; z < nummapthings; z++) { if (&mapthings[z] == itemrespawnque[i]) { P_WriteUINT32(save_p, z); break; } } P_WriteUINT32(save_p, itemrespawntime[i]); i = (i + 1) & (ITEMQUESIZE-1); } // end delimiter P_WriteUINT32(save_p, 0xffffffff); // Sky number P_WriteINT32(save_p, globallevelskynum); // Current global weather type P_WriteUINT8(save_p, globalweather); if (metalplayback) // Is metal sonic running? { UINT8 *p = &save_p->buf[save_p->pos+1]; P_WriteUINT8(save_p, 0x01); G_SaveMetal(&p); } else P_WriteUINT8(save_p, 0x00); } static void P_NetUnArchiveSpecials(save_t *save_p) { size_t i; INT32 j; if (P_ReadUINT32(save_p) != ARCHIVEBLOCK_SPECIALS) I_Error("Bad $$$.sav at archive block Specials"); // BP: added save itemrespawn queue for deathmatch iquetail = iquehead = 0; while ((i = P_ReadUINT32(save_p)) != 0xffffffff) { itemrespawnque[iquehead] = &mapthings[i]; itemrespawntime[iquehead++] = P_ReadINT32(save_p); } j = P_ReadINT32(save_p); if (j != globallevelskynum) P_SetupLevelSky(j, true); globalweather = P_ReadUINT8(save_p); if (globalweather) { if (curWeather == globalweather) curWeather = PRECIP_NONE; P_SwitchWeather(globalweather); } else // PRECIP_NONE { if (curWeather != PRECIP_NONE) P_SwitchWeather(globalweather); } if (P_ReadUINT8(save_p) == 0x01) // metal sonic { UINT8 *p = &save_p->buf[save_p->pos]; G_LoadMetal(&p); } } // ======================================================================= // Misc // ======================================================================= static inline void P_ArchiveMisc(save_t *save_p, INT16 mapnum) { //lastmapsaved = mapnum; lastmaploaded = mapnum; if (gamecomplete) mapnum |= 8192; P_WriteINT16(save_p, mapnum); P_WriteUINT16(save_p, emeralds+357); P_WriteStringN(save_p, timeattackfolder, sizeof(timeattackfolder)); } static inline void P_UnArchiveSPGame(save_t *save_p, INT16 mapoverride) { char testname[sizeof(timeattackfolder)]; gamemap = P_ReadINT16(save_p); if (mapoverride != 0) { gamemap = mapoverride; gamecomplete = 1; } else gamecomplete = 0; // gamemap changed; we assume that its map header is always valid, // so make it so if(!mapheaderinfo[gamemap-1]) P_AllocMapHeader(gamemap-1); //lastmapsaved = gamemap; lastmaploaded = gamemap; tokenlist = 0; token = 0; savedata.emeralds = P_ReadUINT16(save_p)-357; P_ReadStringN(save_p, testname, sizeof(testname)); if (strcmp(testname, timeattackfolder)) { if (modifiedgame) I_Error("Save game not for this modification."); else I_Error("This save file is for a particular mod, it cannot be used with the regular game."); } memset(playeringame, 0, sizeof(*playeringame)); playeringame[consoleplayer] = true; } static void P_NetArchiveMisc(save_t *save_p, boolean resending) { INT32 i; P_WriteUINT32(save_p, ARCHIVEBLOCK_MISC); if (resending) P_WriteUINT32(save_p, gametic); P_WriteINT16(save_p, gamemap); if (gamestate != GS_LEVEL) P_WriteINT16(save_p, GS_WAITINGPLAYERS); // nice hack to put people back into waitingplayers else P_WriteINT16(save_p, gamestate); P_WriteINT16(save_p, gametype); { UINT32 pig = 0; for (i = 0; i < MAXPLAYERS; i++) pig |= (playeringame[i] != 0)<totalplaytime); // TODO put another cipher on these things? meh, I don't care... for (i = 0; i < NUMMAPS; i++) P_WriteUINT8(save_p, (data->mapvisited[i] & MV_MAX)); // To save space, use one bit per collected/achieved/unlocked flag for (i = 0; i < MAXEMBLEMS;) { btemp = 0; for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) btemp |= (data->collected[j+i] << j); P_WriteUINT8(save_p, btemp); i += j; } for (i = 0; i < MAXEXTRAEMBLEMS;) { btemp = 0; for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) btemp |= (data->extraCollected[j+i] << j); P_WriteUINT8(save_p, btemp); i += j; } for (i = 0; i < MAXUNLOCKABLES;) { btemp = 0; for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) btemp |= (data->unlocked[j+i] << j); P_WriteUINT8(save_p, btemp); i += j; } for (i = 0; i < MAXCONDITIONSETS;) { btemp = 0; for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) btemp |= (data->achieved[j+i] << j); P_WriteUINT8(save_p, btemp); i += j; } P_WriteUINT32(save_p, data->timesBeaten); P_WriteUINT32(save_p, data->timesBeatenWithEmeralds); P_WriteUINT32(save_p, data->timesBeatenUltimate); // Main records for (i = 0; i < NUMMAPS; i++) { if (data->mainrecords[i]) { P_WriteUINT32(save_p, data->mainrecords[i]->score); P_WriteUINT32(save_p, data->mainrecords[i]->time); P_WriteUINT16(save_p, data->mainrecords[i]->rings); } else { P_WriteUINT32(save_p, 0); P_WriteUINT32(save_p, 0); P_WriteUINT16(save_p, 0); } } // NiGHTS records for (i = 0; i < NUMMAPS; i++) { if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares) { P_WriteUINT8(save_p, 0); continue; } P_WriteUINT8(save_p, data->nightsrecords[i]->nummares); for (curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare) { P_WriteUINT32(save_p, data->nightsrecords[i]->score[curmare]); P_WriteUINT8(save_p, data->nightsrecords[i]->grade[curmare]); P_WriteUINT32(save_p, data->nightsrecords[i]->time[curmare]); } } // Mid-map stuff P_WriteUINT32(save_p, unlocktriggers); for (i = 0; i < MAXPLAYERS; i++) { if (!ntemprecords[i].nummares) { P_WriteUINT8(save_p, 0); continue; } P_WriteUINT8(save_p, ntemprecords[i].nummares); for (curmare = 0; curmare < (ntemprecords[i].nummares + 1); ++curmare) { P_WriteUINT32(save_p, ntemprecords[i].score[curmare]); P_WriteUINT8(save_p, ntemprecords[i].grade[curmare]); P_WriteUINT32(save_p, ntemprecords[i].time[curmare]); } } } static inline void P_NetUnArchiveEmblems(save_t *save_p) { gamedata_t *data = serverGamedata; INT32 i, j; UINT8 rtemp; UINT32 recscore; tic_t rectime; UINT16 recrings; UINT8 recmares; INT32 curmare; if (P_ReadUINT32(save_p) != ARCHIVEBLOCK_EMBLEMS) I_Error("Bad $$$.sav at archive block Emblems"); savemoddata = (boolean)P_ReadUINT8(save_p); // this one is actually necessary because savemoddata stays false otherwise for some reason. if (numemblems != P_ReadINT32(save_p)) I_Error("Bad $$$.sav dearchiving Emblems (numemblems mismatch)"); if (numextraemblems != P_ReadINT32(save_p)) I_Error("Bad $$$.sav dearchiving Emblems (numextraemblems mismatch)"); // This shouldn't happen, but if something really fucked up happens and you transfer // the SERVER player's gamedata over your own CLIENT gamedata, // then this prevents it from being saved over yours. data->loaded = false; M_ClearSecrets(data); G_ClearRecords(data); // The rest of this is lifted straight from G_LoadGameData in g_game.c // TODO: Optimize this to only read information about emblems, unlocks, etc. which actually exist // There is no need to go all the way up to MAXEMBLEMS when wads are guaranteed to be the same. data->totalplaytime = P_ReadUINT32(save_p); // TODO put another cipher on these things? meh, I don't care... for (i = 0; i < NUMMAPS; i++) if ((data->mapvisited[i] = P_ReadUINT8(save_p)) > MV_MAX) I_Error("Bad $$$.sav dearchiving Emblems (invalid visit flags)"); // To save space, use one bit per collected/achieved/unlocked flag for (i = 0; i < MAXEMBLEMS;) { rtemp = P_ReadUINT8(save_p); for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) data->collected[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXEXTRAEMBLEMS;) { rtemp = P_ReadUINT8(save_p); for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) data->extraCollected[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXUNLOCKABLES;) { rtemp = P_ReadUINT8(save_p); for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) data->unlocked[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXCONDITIONSETS;) { rtemp = P_ReadUINT8(save_p); for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) data->achieved[j+i] = ((rtemp >> j) & 1); i += j; } data->timesBeaten = P_ReadUINT32(save_p); data->timesBeatenWithEmeralds = P_ReadUINT32(save_p); data->timesBeatenUltimate = P_ReadUINT32(save_p); // Main records for (i = 0; i < NUMMAPS; ++i) { recscore = P_ReadUINT32(save_p); rectime = (tic_t)P_ReadUINT32(save_p); recrings = P_ReadUINT16(save_p); if (recrings > 10000 || recscore > MAXSCORE) I_Error("Bad $$$.sav dearchiving Emblems (invalid score)"); if (recscore || rectime || recrings) { G_AllocMainRecordData((INT16)i, data); data->mainrecords[i]->score = recscore; data->mainrecords[i]->time = rectime; data->mainrecords[i]->rings = recrings; } } // Nights records for (i = 0; i < NUMMAPS; ++i) { if ((recmares = P_ReadUINT8(save_p)) == 0) continue; G_AllocNightsRecordData((INT16)i, data); for (curmare = 0; curmare < (recmares+1); ++curmare) { data->nightsrecords[i]->score[curmare] = P_ReadUINT32(save_p); data->nightsrecords[i]->grade[curmare] = P_ReadUINT8(save_p); data->nightsrecords[i]->time[curmare] = (tic_t)P_ReadUINT32(save_p); if (data->nightsrecords[i]->grade[curmare] > GRADE_S) { I_Error("Bad $$$.sav dearchiving Emblems (invalid grade)"); } } data->nightsrecords[i]->nummares = recmares; } // Mid-map stuff unlocktriggers = P_ReadUINT32(save_p); for (i = 0; i < MAXPLAYERS; ++i) { if ((recmares = P_ReadUINT8(save_p)) == 0) continue; for (curmare = 0; curmare < (recmares+1); ++curmare) { ntemprecords[i].score[curmare] = P_ReadUINT32(save_p); ntemprecords[i].grade[curmare] = P_ReadUINT8(save_p); ntemprecords[i].time[curmare] = (tic_t)P_ReadUINT32(save_p); if (ntemprecords[i].grade[curmare] > GRADE_S) { I_Error("Bad $$$.sav dearchiving Emblems (invalid temp grade)"); } } ntemprecords[i].nummares = recmares; } } static void P_NetArchiveSectorPortals(save_t *save_p) { P_WriteUINT32(save_p, ARCHIVEBLOCK_SECPORTALS); P_WriteUINT32(save_p, secportalcount); for (size_t i = 0; i < secportalcount; i++) { UINT8 type = secportals[i].type; P_WriteUINT8(save_p, type); P_WriteFixed(save_p, secportals[i].origin.x); P_WriteFixed(save_p, secportals[i].origin.y); switch (type) { case SECPORTAL_LINE: P_WriteUINT32(save_p, SaveLine(secportals[i].line.start)); P_WriteUINT32(save_p, SaveLine(secportals[i].line.dest)); break; case SECPORTAL_PLANE: case SECPORTAL_HORIZON: case SECPORTAL_FLOOR: case SECPORTAL_CEILING: P_WriteUINT32(save_p, SaveSector(secportals[i].sector)); break; case SECPORTAL_OBJECT: if (secportals[i].mobj && !P_MobjWasRemoved(secportals[i].mobj)) SaveMobjnum(secportals[i].mobj); else P_WriteUINT32(save_p, 0); break; default: break; } } } static void P_NetUnArchiveSectorPortals(save_t *save_p) { if (P_ReadUINT32(save_p) != ARCHIVEBLOCK_SECPORTALS) I_Error("Bad $$$.sav at archive block Secportals"); Z_Free(secportals); P_InitSectorPortals(); UINT32 count = P_ReadUINT32(save_p); for (UINT32 i = 0; i < count; i++) { UINT32 id = P_NewSectorPortal(); sectorportal_t *secportal = &secportals[id]; secportal->type = P_ReadUINT8(save_p); secportal->origin.x = P_ReadFixed(save_p); secportal->origin.y = P_ReadFixed(save_p); switch (secportal->type) { case SECPORTAL_LINE: secportal->line.start = LoadLine(P_ReadUINT32(save_p)); secportal->line.dest = LoadLine(P_ReadUINT32(save_p)); break; case SECPORTAL_PLANE: case SECPORTAL_HORIZON: case SECPORTAL_FLOOR: case SECPORTAL_CEILING: secportal->sector = LoadSector(P_ReadUINT32(save_p)); break; case SECPORTAL_OBJECT: id = P_ReadUINT32(save_p); secportal->mobj = (id == 0) ? NULL : P_FindNewPosition(id); break; default: break; } } } static inline void P_ArchiveLuabanksAndConsistency(save_t *save_p) { UINT8 i, banksinuse = NUM_LUABANKS; while (banksinuse && !luabanks[banksinuse-1]) banksinuse--; // get the last used bank if (banksinuse) { P_WriteUINT8(save_p, 0xb7); // luabanks marker P_WriteUINT8(save_p, banksinuse); for (i = 0; i < banksinuse; i++) P_WriteINT32(save_p, luabanks[i]); } P_WriteUINT8(save_p, 0x1d); // consistency marker } static inline boolean P_UnArchiveLuabanksAndConsistency(save_t *save_p) { switch (P_ReadUINT8(save_p)) { case 0xb7: // luabanks marker { UINT8 i, banksinuse = P_ReadUINT8(save_p); if (banksinuse > NUM_LUABANKS) { CONS_Alert(CONS_ERROR, M_GetText("Corrupt Luabanks! (Too many banks in use)\n")); return false; } for (i = 0; i < banksinuse; i++) luabanks[i] = P_ReadINT32(save_p); if (P_ReadUINT8(save_p) != 0x1d) // consistency marker { CONS_Alert(CONS_ERROR, M_GetText("Corrupt Luabanks! (Failed consistency check)\n")); return false; } } case 0x1d: // consistency marker break; default: // anything else is nonsense CONS_Alert(CONS_ERROR, M_GetText("Failed consistency check (???)\n")); return false; } return true; } void P_SaveGame(save_t *save_p, INT16 mapnum) { P_ArchiveMisc(save_p, mapnum); P_ArchivePlayer(save_p); P_ArchiveLuabanksAndConsistency(save_p); } void P_SaveNetGame(save_t *save_p, boolean resending) { thinker_t *th; mobj_t *mobj; INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise CV_SaveNetVars(save_p); P_NetArchiveMisc(save_p, resending); P_NetArchiveEmblems(save_p); // Assign the mobjnumber for pointer tracking for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; mobj = (mobj_t *)th; if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER) continue; mobj->mobjnum = i++; } P_NetArchivePlayers(save_p); if (gamestate == GS_LEVEL) { P_NetArchiveWorld(save_p); P_ArchivePolyObjects(save_p); P_NetArchiveThinkers(save_p); P_NetArchiveSpecials(save_p); P_NetArchiveColormaps(save_p); P_NetArchiveWaypoints(save_p); P_NetArchiveSectorPortals(save_p); } LUA_Archive(save_p); P_ArchiveLuabanksAndConsistency(save_p); } boolean P_LoadGame(save_t *save_p, INT16 mapoverride) { if (gamestate == GS_INTERMISSION) Y_EndIntermission(); G_SetGamestate(GS_NULL); // should be changed in P_UnArchiveMisc P_UnArchiveSPGame(save_p, mapoverride); P_UnArchivePlayer(save_p); if (!P_UnArchiveLuabanksAndConsistency(save_p)) return false; // Only do this after confirming savegame is ok G_DeferedInitNew(false, G_BuildMapName(gamemap), savedata.skin, false, true); COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this return true; } boolean P_LoadNetGame(save_t *save_p, boolean reloading) { CV_LoadNetVars(save_p); if (!P_NetUnArchiveMisc(save_p, reloading)) return false; P_NetUnArchiveEmblems(save_p); P_NetUnArchivePlayers(save_p); if (gamestate == GS_LEVEL) { P_NetUnArchiveWorld(save_p); P_UnArchivePolyObjects(save_p); P_NetUnArchiveThinkers(save_p); P_NetUnArchiveSpecials(save_p); P_NetUnArchiveColormaps(save_p); P_NetUnArchiveWaypoints(save_p); P_NetUnArchiveSectorPortals(save_p); P_RelinkPointers(); P_FinishMobjs(); } LUA_UnArchive(save_p); // This is stupid and hacky, but maybe it'll work! P_SetRandSeed(P_GetInitSeed()); // The precipitation would normally be spawned in P_SetupLevel, which is called by // P_NetUnArchiveMisc above. However, that would place it up before P_NetUnArchiveThinkers, // so the thinkers would be deleted later. Therefore, P_SetupLevel will *not* spawn // precipitation when loading a netgame save. Instead, precip has to be spawned here. // This is done in P_NetUnArchiveSpecials now. return P_UnArchiveLuabanksAndConsistency(save_p); }