From a011e4eafe6aac85c9d556fa4e5e41accce2d6d2 Mon Sep 17 00:00:00 2001 From: danij Date: Fri, 28 Feb 2014 07:58:05 +0000 Subject: [PATCH] Refactor|libcommon|SaveInfo: Extracted SessionMetadata from SaveInfo --- doomsday/plugins/common/include/saveinfo.h | 100 ++- doomsday/plugins/common/src/g_game.cpp | 16 +- .../plugins/common/src/gamestatereader.cpp | 32 +- .../plugins/common/src/gamestatewriter.cpp | 4 +- doomsday/plugins/common/src/p_saveg.cpp | 22 +- doomsday/plugins/common/src/saveinfo.cpp | 575 ++++++++---------- doomsday/plugins/common/src/saveslots.cpp | 4 +- .../doom/src/doomv9gamestatereader.cpp | 8 +- .../heretic/src/hereticv13gamestatereader.cpp | 8 +- 9 files changed, 348 insertions(+), 421 deletions(-) diff --git a/doomsday/plugins/common/include/saveinfo.h b/doomsday/plugins/common/include/saveinfo.h index 7e5d315827..2660b001f6 100644 --- a/doomsday/plugins/common/include/saveinfo.h +++ b/doomsday/plugins/common/include/saveinfo.h @@ -25,6 +25,37 @@ #include #include +/** + * Game session metadata (serialized to savegames). + * @ingroup libcommon + */ +struct SessionMetadata +{ +#if !__JHEXEN__ + // Info data about players present (or not) in the game session. + typedef byte Players[MAXPLAYERS]; +#endif + + de::String userDescription; + uint sessionId; + int magic; + int version; + de::String gameIdentityKey; + Uri *mapUri; + GameRuleset gameRules; +#if !__JHEXEN__ + int mapTime; + Players players; +#endif + + SessionMetadata(); + SessionMetadata(SessionMetadata const &other); + ~SessionMetadata(); + + void write(Writer *writer) const; + void read(Reader *reader); +}; + /** * Represents a saved game session state. * @@ -46,17 +77,12 @@ class SaveInfo Unused }; -#if !__JHEXEN__ - // Info data about players present (or not) in the game session. - typedef byte Players[MAXPLAYERS]; -#endif - public: SaveInfo(de::String const &fileName = ""); SaveInfo(SaveInfo const &other); - static SaveInfo *newWithCurrentSessionMetadata(de::String const &fileName = "", - de::String const &userDescription = ""); + static SaveInfo *newWithCurrentSessionMeta(de::String const &fileName = "", + de::String const &userDescription = ""); SaveInfo &operator = (SaveInfo const &other); @@ -129,73 +155,31 @@ class SaveInfo * Update the metadata associated with the save using values derived from the current game * session. Note that this does @em not affect the copy of this save on disk. */ - void applyCurrentSessionMetadata(); + void applyCurrentSessionMeta(); /** - * Returns the unique "identity key" of the game session. + * Provides read-only access to a copy of the deserialized game session metadata. */ - de::String const &gameIdentityKey() const; - void setGameIdentityKey(de::String newGameIdentityKey); + SessionMetadata const &meta() const; /** - * Returns the logical version of the serialized game session state. + * Deserializes the game session metadata using @a reader. */ - int version() const; - void setVersion(int newVersion); + void readMeta(Reader *reader); - /** - * Returns the textual description of the saved game session provided by the user. The - * UserDescriptionChange audience is notified whenever the description changes. - */ - de::String const &userDescription() const; + // Metadata manipulation: + void setGameIdentityKey(de::String newGameIdentityKey); + void setVersion(int newVersion); void setUserDescription(de::String newUserDescription); - - /** - * @see G_GenerateSessionId() - */ - uint sessionId() const; void setSessionId(uint newSessionId); - - /** - * Returns the URI of the @em current map of the game session. - */ - Uri const *mapUri() const; void setMapUri(Uri const *newMapUri); - #if !__JHEXEN__ - - /** - * Returns the expired time in tics since the @em current map of the game session began. - */ - int mapTime() const; void setMapTime(int newMapTime); - - /** - * Returns the player info data for the game session. - */ - Players const &players() const; - void setPlayers(Players const &newPlayers); - + void setPlayers(SessionMetadata::Players const &newPlayers); #endif // !__JHEXEN__ - - /** - * Returns the game ruleset for the game session. - */ - GameRuleset const &gameRules() const; void setGameRules(GameRuleset const &newRules); - /** - * Serializes the game session header using @a writer. - */ - void write(Writer *writer) const; - - /** - * Deserializes the game session header using @a reader. - */ - void read(Reader *reader); - public: /// @todo refactor away: - int magic() const; void setMagic(int newMagic); static SaveInfo *fromReader(Reader *reader); diff --git a/doomsday/plugins/common/src/g_game.cpp b/doomsday/plugins/common/src/g_game.cpp index 30f75d17c3..2db2205654 100644 --- a/doomsday/plugins/common/src/g_game.cpp +++ b/doomsday/plugins/common/src/g_game.cpp @@ -1408,7 +1408,7 @@ int G_DoLoadMap(loadmap_params_t *p) throw de::Error("G_DoLoadMap", "Failed opening \"" + de::NativePath(path).pretty() + "\" for read"); } - MapStateReader(saveInfo.version()).read(reader); + MapStateReader(saveInfo.meta().version).read(reader); SV_HxReleaseSaveBuffer(); } @@ -2243,7 +2243,7 @@ void G_DoReborn(int plrNum) { // Compose the confirmation message. SaveInfo &saveInfo = G_SaveSlots()[chosenSlot].saveInfo(); - AutoStr *msg = Str_Appendf(AutoStr_NewStd(), REBORNLOAD_CONFIRM, saveInfo.userDescription().toUtf8().constData()); + AutoStr *msg = Str_Appendf(AutoStr_NewStd(), REBORNLOAD_CONFIRM, saveInfo.meta().userDescription.toUtf8().constData()); S_LocalSound(SFX_REBORNLOAD_CONFIRM, NULL); Hu_MsgStart(MSG_YESNO, Str_Text(msg), rebornLoadConfirmResponse, 0, new de::String(chosenSlot)); } @@ -2906,10 +2906,10 @@ static int saveGameStateWorker(void *context) if(userDescription.isEmpty()) { SaveInfo &saveInfo = G_SaveSlots()[p.slotId].saveInfo(); - if(!saveInfo.userDescription().isEmpty()) + if(!saveInfo.meta().userDescription.isEmpty()) { // Slot already in use; reuse the existing description. - userDescription = saveInfo.userDescription(); + userDescription = saveInfo.meta().userDescription; } else if(gaSaveSessionGenerateDescription) { @@ -2917,7 +2917,7 @@ static int saveGameStateWorker(void *context) } } - SaveInfo *saveInfo = SaveInfo::newWithCurrentSessionMetadata( + SaveInfo *saveInfo = SaveInfo::newWithCurrentSessionMeta( G_SaveSlots()[logicalSlot].fileName(), userDescription); try @@ -4179,7 +4179,7 @@ D_CMD(LoadSession) S_LocalSound(SFX_QUICKLOAD_PROMPT, NULL); // Compose the confirmation message. - AutoStr *msg = Str_Appendf(AutoStr_NewStd(), QLPROMPT, sslot.saveInfo().userDescription().toUtf8().constData()); + AutoStr *msg = Str_Appendf(AutoStr_NewStd(), QLPROMPT, sslot.saveInfo().meta().userDescription.toUtf8().constData()); Hu_MsgStart(MSG_YESNO, Str_Text(msg), loadSessionConfirmed, 0, new de::String(slotId)); return true; } @@ -4295,7 +4295,7 @@ D_CMD(SaveSession) } // Compose the confirmation message. - AutoStr *msg = Str_Appendf(AutoStr_NewStd(), QSPROMPT, sslot.saveInfo().userDescription().toUtf8().constData()); + AutoStr *msg = Str_Appendf(AutoStr_NewStd(), QSPROMPT, sslot.saveInfo().meta().userDescription.toUtf8().constData()); savesessionconfirmed_params_t *parm = new savesessionconfirmed_params_t; parm->slotId = slotId; @@ -4391,7 +4391,7 @@ D_CMD(DeleteSavedSession) else { // Compose the confirmation message. - AutoStr *msg = Str_Appendf(AutoStr_NewStd(), DELETESAVEGAME_CONFIRM, sslot.saveInfo().userDescription().toUtf8().constData()); + AutoStr *msg = Str_Appendf(AutoStr_NewStd(), DELETESAVEGAME_CONFIRM, sslot.saveInfo().meta().userDescription.toUtf8().constData()); S_LocalSound(SFX_DELETESAVEGAME_CONFIRM, NULL); Hu_MsgStart(MSG_YESNO, Str_Text(msg), deleteSavedSessionConfirmed, 0, new de::String(slotId)); } diff --git a/doomsday/plugins/common/src/gamestatereader.cpp b/doomsday/plugins/common/src/gamestatereader.cpp index 39e9df2477..1546e764b9 100644 --- a/doomsday/plugins/common/src/gamestatereader.cpp +++ b/doomsday/plugins/common/src/gamestatereader.cpp @@ -99,11 +99,11 @@ DENG2_PIMPL(GameStateReader) void readWorldACScriptData() { #if __JHEXEN__ - if(saveInfo->version() >= 7) + if(saveInfo->meta().version >= 7) { beginSegment(ASEG_WORLDSCRIPTDATA); } - Game_ACScriptInterpreter().readWorldScriptData(reader, saveInfo->version()); + Game_ACScriptInterpreter().readWorldScriptData(reader, saveInfo->meta().version); #endif } @@ -121,7 +121,7 @@ DENG2_PIMPL(GameStateReader) } #endif - MapStateReader(saveInfo->version(), thingArchiveSize).read(reader); + MapStateReader(saveInfo->meta().version, thingArchiveSize).read(reader); readConsistencyBytes(); #if __JHEXEN__ @@ -141,15 +141,15 @@ DENG2_PIMPL(GameStateReader) void readPlayers() { #if __JHEXEN__ - if(saveInfo->version() >= 4) + if(saveInfo->meta().version >= 4) #else - if(saveInfo->version() >= 5) + if(saveInfo->meta().version >= 5) #endif { beginSegment(ASEG_PLAYER_HEADER); } playerheader_t plrHdr; - plrHdr.read(reader, saveInfo->version()); + plrHdr.read(reader, saveInfo->meta().version); // Setup the dummy. ddplayer_t dummyDDPlayer; @@ -160,7 +160,7 @@ DENG2_PIMPL(GameStateReader) { loaded[i] = 0; #if !__JHEXEN__ - infile[i] = saveInfo->players()[i]; + infile[i] = saveInfo->meta().players[i]; #endif } @@ -285,7 +285,7 @@ bool GameStateReader::recognize(SaveInfo &info) // static if(!SV_OpenFile(path, false/*for reading*/)) return false; Reader *reader = SV_NewReader(); - info.read(reader); + info.readMeta(reader); Reader_Delete(reader); #if __JHEXEN__ @@ -295,7 +295,7 @@ bool GameStateReader::recognize(SaveInfo &info) // static #endif // Magic must match. - if(info.magic() != MY_SAVE_MAGIC && info.magic() != MY_CLIENT_SAVE_MAGIC) + if(info.meta().magic != MY_SAVE_MAGIC && info.meta().magic != MY_CLIENT_SAVE_MAGIC) { return false; } @@ -303,7 +303,7 @@ bool GameStateReader::recognize(SaveInfo &info) // static /* * Check for unsupported versions. */ - if(info.version() > MY_SAVE_VERSION) // Future version? + if(info.meta().version > MY_SAVE_VERSION) // Future version? { return false; } @@ -311,7 +311,7 @@ bool GameStateReader::recognize(SaveInfo &info) // static #if __JHEXEN__ // We are incompatible with v3 saves due to an invalid test used to determine // present sides (ver3 format's sides contain chunks of junk data). - if(info.version() == 3) + if(info.meta().version == 3) { return false; } @@ -346,16 +346,16 @@ void GameStateReader::read(SaveInfo &info) * Load the map and configure some game settings. */ briefDisabled = true; - G_NewSession(d->saveInfo->mapUri(), 0/*not saved??*/, &d->saveInfo->gameRules()); + G_NewSession(d->saveInfo->meta().mapUri, 0/*not saved??*/, &d->saveInfo->meta().gameRules); G_SetGameAction(GA_NONE); /// @todo Necessary? #if !__JHEXEN__ - mapTime = d->saveInfo->mapTime(); + mapTime = d->saveInfo->meta().mapTime; #endif int thingArchiveSize = 0; #if !__JHEXEN__ - thingArchiveSize = (d->saveInfo->version() >= 5? Reader_ReadInt32(d->reader) : 1024); + thingArchiveSize = (d->saveInfo->meta().version >= 5? Reader_ReadInt32(d->reader) : 1024); #endif d->readPlayers(); @@ -366,13 +366,13 @@ void GameStateReader::read(SaveInfo &info) #if !__JHEXEN__ // In netgames, the server tells the clients about this. - NetSv_LoadGame(d->saveInfo->sessionId()); + NetSv_LoadGame(d->saveInfo->meta().sessionId); #endif Reader_Delete(d->reader); d->reader = 0; // Material scrollers must be spawned for older savegame versions. - if(d->saveInfo->version() <= 10) + if(d->saveInfo->meta().version <= 10) { P_SpawnAllMaterialOriginScrollers(); } diff --git a/doomsday/plugins/common/src/gamestatewriter.cpp b/doomsday/plugins/common/src/gamestatewriter.cpp index 69579240ad..b7974cf6b5 100644 --- a/doomsday/plugins/common/src/gamestatewriter.cpp +++ b/doomsday/plugins/common/src/gamestatewriter.cpp @@ -71,7 +71,7 @@ DENG2_PIMPL(GameStateWriter) void writeSessionHeader() { - saveInfo->write(writer); + saveInfo->meta().write(writer); } void writeWorldACScriptData() @@ -138,7 +138,7 @@ void GameStateWriter::write(SaveInfo &info) // In networked games the server tells the clients to save their games. #if !__JHEXEN__ - NetSv_SaveGame(d->saveInfo->sessionId()); + NetSv_SaveGame(d->saveInfo->meta().sessionId); #endif if(!SV_OpenFile(path, true/*for writing*/)) diff --git a/doomsday/plugins/common/src/p_saveg.cpp b/doomsday/plugins/common/src/p_saveg.cpp index 82be6e73cb..e304003b38 100644 --- a/doomsday/plugins/common/src/p_saveg.cpp +++ b/doomsday/plugins/common/src/p_saveg.cpp @@ -795,7 +795,7 @@ void SV_SaveGameClient(uint sessionId) } // Prepare new save info. - SaveInfo *info = SaveInfo::newWithCurrentSessionMetadata(saveNameForClientSessionId(sessionId)); + SaveInfo *info = SaveInfo::newWithCurrentSessionMeta(saveNameForClientSessionId(sessionId)); info->setSessionId(sessionId); de::Path path = SV_ClientSavePath() / info->fileName(); @@ -810,7 +810,7 @@ void SV_SaveGameClient(uint sessionId) } Writer *writer = SV_NewWriter(); - info->write(writer); + info->meta().write(writer); // Some important information. // Our position and look angles. @@ -864,7 +864,7 @@ void SV_LoadGameClient(uint sessionId) return; } - if(info->magic() != MY_CLIENT_SAVE_MAGIC) + if(info->meta().magic != MY_CLIENT_SAVE_MAGIC) { SV_CloseFile(); Reader_Delete(reader); @@ -874,16 +874,16 @@ void SV_LoadGameClient(uint sessionId) } // Do we need to change the map? - if(!Uri_Equality(gameMapUri, info->mapUri())) + if(!Uri_Equality(gameMapUri, info->meta().mapUri)) { - G_NewSession(info->mapUri(), 0/*default*/, &info->gameRules()); + G_NewSession(info->meta().mapUri, 0/*default*/, &info->meta().gameRules); G_SetGameAction(GA_NONE); /// @todo Necessary? } else { - G_Rules() = info->gameRules(); + G_Rules() = info->meta().gameRules; } - mapTime = info->mapTime(); + mapTime = info->meta().mapTime; P_MobjUnlink(mo); mo->origin[VX] = FIX2FLT(Reader_ReadInt32(reader)); @@ -897,19 +897,19 @@ void SV_LoadGameClient(uint sessionId) cpl->plr->lookDir = Reader_ReadFloat(reader); /* $unifiedangles */ #if __JHEXEN__ - if(info->version() >= 4) + if(info->meta().version >= 4) #else - if(info->version() >= 5) + if(info->meta().version >= 5) #endif { SV_AssertSegment(ASEG_PLAYER_HEADER); } playerheader_t plrHdr; - plrHdr.read(reader, info->version()); + plrHdr.read(reader, info->meta().version); cpl->read(reader, plrHdr); - MapStateReader(info->version()).read(reader); + MapStateReader(info->meta().version).read(reader); SV_CloseFile(); Reader_Delete(reader); diff --git a/doomsday/plugins/common/src/saveinfo.cpp b/doomsday/plugins/common/src/saveinfo.cpp index 16b192d463..a88105c72d 100644 --- a/doomsday/plugins/common/src/saveinfo.cpp +++ b/doomsday/plugins/common/src/saveinfo.cpp @@ -29,6 +29,223 @@ #include #include +SessionMetadata::SessionMetadata() + : sessionId(0) + , magic (0) + , version (0) + , mapUri (Uri_New()) +#if !__JHEXEN__ + , mapTime (0) +#endif +{ +#if !__JHEXEN__ + de::zap(players); +#endif +} + +SessionMetadata::SessionMetadata(SessionMetadata const &other) + : userDescription(other.userDescription) + , sessionId (other.sessionId) + , magic (other.magic) + , version (other.version) + , gameIdentityKey(other.gameIdentityKey) + , mapUri (Uri_Dup(other.mapUri)) + , gameRules (other.gameRules) +#if !__JHEXEN__ + , mapTime (other.mapTime) +#endif +{ +#if !__JHEXEN__ + std::memcpy(&players, &other.players, sizeof(players)); +#endif +} + +SessionMetadata::~SessionMetadata() +{ + Uri_Delete(mapUri); +} + +void SessionMetadata::write(Writer *writer) const +{ + Writer_WriteInt32(writer, magic); + Writer_WriteInt32(writer, version); + + AutoStr *gameIdentityKeyStr = AutoStr_FromTextStd(gameIdentityKey.toUtf8().constData()); + Str_Write(gameIdentityKeyStr, writer); + + AutoStr *descriptionStr = AutoStr_FromTextStd(userDescription.toUtf8().constData()); + Str_Write(descriptionStr, writer); + + Uri_Write(mapUri, writer); +#if !__JHEXEN__ + Writer_WriteInt32(writer, mapTime); +#endif + gameRules.write(writer); + +#if !__JHEXEN__ + for(int i = 0; i < MAXPLAYERS; ++i) + { + Writer_WriteByte(writer, players[i]); + } +#endif + + Writer_WriteInt32(writer, sessionId); +} + +void SessionMetadata::read(Reader *reader) +{ +#if __JHEXEN__ + // Read the magic byte to determine the high-level format. + int magic = Reader_ReadInt32(reader); + SV_HxSavePtr()->b -= 4; // Rewind the stream. + + if((!IS_NETWORK_CLIENT && magic != MY_SAVE_MAGIC) || + ( IS_NETWORK_CLIENT && magic != MY_CLIENT_SAVE_MAGIC)) + { + // Assume the old v9 format. + char descBuf[24]; + Reader_Read(reader, descBuf, 24); + userDescription = de::String(descBuf, 24); + + magic = MY_SAVE_MAGIC; // Lets pretend... + + char verText[16 + 1]; // "HXS Ver " + Reader_Read(reader, &verText, 16); descBuf[16] = 0; + version = de::String(&verText[8]).toInt(); + + /// @note Kludge: Assume the current game. + gameIdentityKey = G_IdentityKey(); + /// Kludge end. + + /*Skip junk*/ SV_Seek(4); + + uint map = Reader_ReadByte(reader) - 1; + Uri_Copy(mapUri, G_ComposeMapUri(0, map)); + + gameRules.skill = (skillmode_t) (Reader_ReadByte(reader) & 0x7f); + // Interpret skill modes outside the normal range as "spawn no things". + if(gameRules.skill < SM_BABY || gameRules.skill >= NUM_SKILL_MODES) + { + gameRules.skill = SM_NOTHINGS; + } + + gameRules.deathmatch = Reader_ReadByte(reader); + gameRules.noMonsters = Reader_ReadByte(reader); + gameRules.randomClasses = Reader_ReadByte(reader); + + sessionId = 0; // None. + return; + } +#endif + + magic = Reader_ReadInt32(reader); + version = Reader_ReadInt32(reader); + if(version >= 14) + { + AutoStr *tmp = AutoStr_NewStd(); + Str_Read(tmp, reader); + gameIdentityKey = Str_Text(tmp); + } + else + { + // Translate gamemode identifiers from older save versions. + int oldGamemode = Reader_ReadInt32(reader); + gameIdentityKey = G_IdentityKeyForLegacyGamemode(oldGamemode, version); + } + + if(version >= 10) + { + AutoStr *tmp = AutoStr_NewStd(); + Str_Read(tmp, reader); + userDescription = Str_Text(tmp); + } + else + { + // Description is a fixed 24 characters in length. + char descBuf[24]; + Reader_Read(reader, descBuf, 24); + userDescription = de::String(descBuf, 24); + } + + if(version >= 14) + { + Uri_Read(mapUri, reader); +#if !__JHEXEN__ + mapTime = Reader_ReadInt32(reader); +#endif + + gameRules.read(reader); + } + else + { +#if !__JHEXEN__ + if(version < 13) + { + // In DOOM the high bit of the skill mode byte is also used for the + // "fast" game rule dd_bool. There is more confusion in that SM_NOTHINGS + // will result in 0xff and thus always set the fast bit. + // + // Here we decipher this assuming that if the skill mode is invalid then + // by default this means "spawn no things" and if so then the "fast" game + // rule is meaningless so it is forced off. + byte skillModePlusFastBit = Reader_ReadByte(reader); + gameRules.skill = (skillmode_t) (skillModePlusFastBit & 0x7f); + if(gameRules.skill < SM_BABY || gameRules.skill >= NUM_SKILL_MODES) + { + gameRules.skill = SM_NOTHINGS; + gameRules.fast = 0; + } + else + { + gameRules.fast = (skillModePlusFastBit & 0x80) != 0; + } + } + else +#endif + { + gameRules.skill = skillmode_t( Reader_ReadByte(reader) & 0x7f ); + // Interpret skill levels outside the normal range as "spawn no things". + if(gameRules.skill < SM_BABY || gameRules.skill >= NUM_SKILL_MODES) + { + gameRules.skill = SM_NOTHINGS; + } + } + + uint episode = Reader_ReadByte(reader) - 1; + uint map = Reader_ReadByte(reader) - 1; + Uri_Copy(mapUri, G_ComposeMapUri(episode, map)); + + gameRules.deathmatch = Reader_ReadByte(reader); +#if !__JHEXEN__ + if(version >= 13) + { + gameRules.fast = Reader_ReadByte(reader); + } +#endif + gameRules.noMonsters = Reader_ReadByte(reader); +#if __JHEXEN__ + gameRules.randomClasses = Reader_ReadByte(reader); +#endif +#if !__JHEXEN__ + gameRules.respawnMonsters = Reader_ReadByte(reader); +#endif +#if !__JHEXEN__ + /*skip old junk*/ if(version < 10) SV_Seek(2); + + mapTime = Reader_ReadInt32(reader); +#endif + } + +#if !__JHEXEN__ + for(int i = 0; i < MAXPLAYERS; ++i) + { + players[i] = Reader_ReadByte(reader); + } +#endif + + sessionId = Reader_ReadInt32(reader); +} + namespace internal { static bool usingSeparateMapSessionFiles() @@ -50,62 +267,21 @@ DENG2_PIMPL(SaveInfo) bool needUpdateStatus; String fileName; ///< Name of the game session file. - - // Metadata (the session header): - String userDescription; - uint sessionId; - int magic; - int version; - String gameIdentityKey; - Uri *mapUri; - GameRuleset gameRules; -#if !__JHEXEN__ - int mapTime; - Players players; -#endif + SessionMetadata meta; Instance(Public *i) : Base(i) , status (Unused) , needUpdateStatus(true) - , sessionId (0) - , magic (0) - , version (0) - , mapUri (Uri_New()) -#if !__JHEXEN__ - , mapTime (0) -#endif - { -#if !__JHEXEN__ - de::zap(players); -#endif - } + {} Instance(Public *i, Instance const &other) : Base(i) , status (other.status) , needUpdateStatus(other.needUpdateStatus) , fileName (other.fileName) - , userDescription (other.userDescription) - , sessionId (other.sessionId) - , magic (other.magic) - , version (other.version) - , gameIdentityKey (other.gameIdentityKey) - , mapUri (Uri_Dup(other.mapUri)) - , gameRules (other.gameRules) -#if !__JHEXEN__ - , mapTime (other.mapTime) -#endif - { -#if !__JHEXEN__ - std::memcpy(&players, &other.players, sizeof(players)); -#endif - } - - ~Instance() - { - Uri_Delete(mapUri); - } + , meta (other.meta) + {} void updateStatusIfNeeded() { @@ -121,7 +297,7 @@ DENG2_PIMPL(SaveInfo) { status = Incompatible; // Game identity key missmatch? - if(!gameIdentityKey.compareWithoutCase(G_IdentityKey())) + if(!meta.gameIdentityKey.compareWithoutCase(G_IdentityKey())) { /// @todo Validate loaded add-ons and checksum the definition database. status = Loadable; // It's good! @@ -136,46 +312,6 @@ DENG2_PIMPL(SaveInfo) } } } - -#if __JHEXEN__ - /** - * Deserialize the legacy Hexen-specific v.9 info. - */ - void read_Hx_v9(reader_s *reader) - { - char descBuf[24]; - Reader_Read(reader, descBuf, 24); - userDescription = String(descBuf, 24); - - magic = MY_SAVE_MAGIC; // Lets pretend... - - char verText[16 + 1]; // "HXS Ver " - Reader_Read(reader, &verText, 16); descBuf[16] = 0; - version = String(&verText[8]).toInt(); - - /// @note Kludge: Assume the current game. - gameIdentityKey = G_IdentityKey(); - /// Kludge end. - - /*Skip junk*/ SV_Seek(4); - - uint map = Reader_ReadByte(reader) - 1; - Uri_Copy(mapUri, G_ComposeMapUri(0, map)); - - gameRules.skill = (skillmode_t) (Reader_ReadByte(reader) & 0x7f); - // Interpret skill modes outside the normal range as "spawn no things". - if(gameRules.skill < SM_BABY || gameRules.skill >= NUM_SKILL_MODES) - { - gameRules.skill = SM_NOTHINGS; - } - - gameRules.deathmatch = Reader_ReadByte(reader); - gameRules.noMonsters = Reader_ReadByte(reader); - gameRules.randomClasses = Reader_ReadByte(reader); - - sessionId = 0; // None. - } -#endif }; SaveInfo::SaveInfo(String const &fileName) : d(new Instance(this)) @@ -186,13 +322,13 @@ SaveInfo::SaveInfo(String const &fileName) : d(new Instance(this)) SaveInfo::SaveInfo(SaveInfo const &other) : d(new Instance(this, *other.d)) {} -SaveInfo *SaveInfo::newWithCurrentSessionMetadata(String const &fileName, +SaveInfo *SaveInfo::newWithCurrentSessionMeta(String const &fileName, String const &userDescription) // static { LOG_AS("SaveInfo"); SaveInfo *info = new SaveInfo(fileName); info->setUserDescription(userDescription); - info->applyCurrentSessionMetadata(); + info->applyCurrentSessionMeta(); info->setSessionId(G_GenerateSessionId()); return info; } @@ -232,44 +368,29 @@ String SaveInfo::fileNameForMap(Uri const *mapUri) const .arg(int(map + 1), 2, 10, QChar('0')); } -String const &SaveInfo::gameIdentityKey() const -{ - return d->gameIdentityKey; -} - void SaveInfo::setGameIdentityKey(String newGameIdentityKey) { - if(d->gameIdentityKey != newGameIdentityKey) + if(d->meta.gameIdentityKey != newGameIdentityKey) { - d->gameIdentityKey = newGameIdentityKey; + d->meta.gameIdentityKey = newGameIdentityKey; d->needUpdateStatus = true; } } -int SaveInfo::version() const -{ - return d->version; -} - void SaveInfo::setVersion(int newVersion) { - if(d->version != newVersion) + if(d->meta.version != newVersion) { - d->version = newVersion; + d->meta.version = newVersion; d->needUpdateStatus = true; } } -String const &SaveInfo::userDescription() const -{ - return d->userDescription; -} - void SaveInfo::setUserDescription(String newUserDescription) { - if(d->userDescription != newUserDescription) + if(d->meta.userDescription != newUserDescription) { - d->userDescription = newUserDescription; + d->meta.userDescription = newUserDescription; DENG2_FOR_AUDIENCE(UserDescriptionChange, i) { i->saveInfoUserDescriptionChanged(*this); @@ -277,82 +398,56 @@ void SaveInfo::setUserDescription(String newUserDescription) } } -uint SaveInfo::sessionId() const -{ - return d->sessionId; -} - void SaveInfo::setSessionId(uint newSessionId) { - if(d->sessionId != newSessionId) + if(d->meta.sessionId != newSessionId) { - d->sessionId = newSessionId; + d->meta.sessionId = newSessionId; d->needUpdateStatus = true; } } -Uri const *SaveInfo::mapUri() const -{ - return d->mapUri; -} - void SaveInfo::setMapUri(Uri const *newMapUri) { DENG2_ASSERT(newMapUri != 0); - Uri_Copy(d->mapUri, newMapUri); + Uri_Copy(d->meta.mapUri, newMapUri); } #if !__JHEXEN__ -int SaveInfo::mapTime() const -{ - return d->mapTime; -} - void SaveInfo::setMapTime(int newMapTime) { - d->mapTime = newMapTime; + d->meta.mapTime = newMapTime; } -SaveInfo::Players const &SaveInfo::players() const +void SaveInfo::setPlayers(SessionMetadata::Players const &newPlayers) { - return d->players; + std::memcpy(d->meta.players, newPlayers, sizeof(d->meta.players)); } - -void SaveInfo::setPlayers(Players const &newPlayers) -{ - std::memcpy(d->players, newPlayers, sizeof(d->players)); -} - #endif // !__JHEXEN__ -GameRuleset const &SaveInfo::gameRules() const -{ - return d->gameRules; -} - void SaveInfo::setGameRules(GameRuleset const &newRules) { LOG_AS("SaveInfo"); - d->gameRules = newRules; // Make a copy + d->meta.gameRules = newRules; // Make a copy d->needUpdateStatus = true; } -void SaveInfo::applyCurrentSessionMetadata() +void SaveInfo::applyCurrentSessionMeta() { LOG_AS("SaveInfo"); - d->magic = IS_NETWORK_CLIENT? MY_CLIENT_SAVE_MAGIC : MY_SAVE_MAGIC; - d->version = MY_SAVE_VERSION; - d->gameIdentityKey = G_IdentityKey(); - Uri_Copy(d->mapUri, gameMapUri); + d->meta.magic = IS_NETWORK_CLIENT? MY_CLIENT_SAVE_MAGIC : MY_SAVE_MAGIC; + d->meta.version = MY_SAVE_VERSION; + d->meta.gameIdentityKey = G_IdentityKey(); + Uri_Copy(d->meta.mapUri, gameMapUri); #if !__JHEXEN__ - d->mapTime = ::mapTime; + d->meta.mapTime = ::mapTime; #endif - d->gameRules = G_Rules(); // Make a copy. + d->meta.gameRules = G_Rules(); // Make a copy. #if !__JHEXEN__ for(int i = 0; i < MAXPLAYERS; i++) { - d->players[i] = (::players[i]).plr->inGame; + d->meta.players[i] = (::players[i]).plr->inGame; } #endif d->needUpdateStatus = true; @@ -383,7 +478,7 @@ void SaveInfo::updateFromFile() if(G_GameStateReaderFactory().recognize(*this)) { // Ensure we have a valid description. - if(d->userDescription.isEmpty()) + if(d->meta.userDescription.isEmpty()) { setUserDescription("UNNAMED"); } @@ -399,162 +494,15 @@ void SaveInfo::updateFromFile() d->updateStatusIfNeeded(); } -void SaveInfo::write(writer_s *writer) const +SessionMetadata const &SaveInfo::meta() const { - LOG_AS("SaveInfo"); - - Writer_WriteInt32(writer, d->magic); - Writer_WriteInt32(writer, d->version); - - AutoStr *gameIdentityKeyStr = AutoStr_FromTextStd(d->gameIdentityKey.toUtf8().constData()); - Str_Write(gameIdentityKeyStr, writer); - - AutoStr *descriptionStr = AutoStr_FromTextStd(d->userDescription.toUtf8().constData()); - Str_Write(descriptionStr, writer); - - Uri_Write(d->mapUri, writer); -#if !__JHEXEN__ - Writer_WriteInt32(writer, d->mapTime); -#endif - d->gameRules.write(writer); - -#if !__JHEXEN__ - for(int i = 0; i < MAXPLAYERS; ++i) - { - Writer_WriteByte(writer, d->players[i]); - } -#endif - - Writer_WriteInt32(writer, d->sessionId); + return d->meta; } -void SaveInfo::read(reader_s *reader) +void SaveInfo::readMeta(reader_s *reader) { LOG_AS("SaveInfo"); - -#if __JHEXEN__ - // Read the magic byte to determine the high-level format. - int magic = Reader_ReadInt32(reader); - SV_HxSavePtr()->b -= 4; // Rewind the stream. - - if((!IS_NETWORK_CLIENT && magic != MY_SAVE_MAGIC) || - ( IS_NETWORK_CLIENT && magic != MY_CLIENT_SAVE_MAGIC)) - { - // Perhaps the old v9 format? - d->read_Hx_v9(reader); - - d->needUpdateStatus = true; - return; - } -#endif - - d->magic = Reader_ReadInt32(reader); - d->version = Reader_ReadInt32(reader); - if(d->version >= 14) - { - AutoStr *tmp = AutoStr_NewStd(); - Str_Read(tmp, reader); - d->gameIdentityKey = Str_Text(tmp); - } - else - { - // Translate gamemode identifiers from older save versions. - int oldGamemode = Reader_ReadInt32(reader); - d->gameIdentityKey = G_IdentityKeyForLegacyGamemode(oldGamemode, d->version); - } - - if(d->version >= 10) - { - AutoStr *tmp = AutoStr_NewStd(); - Str_Read(tmp, reader); - d->userDescription = Str_Text(tmp); - } - else - { - // Description is a fixed 24 characters in length. - char descBuf[24]; - Reader_Read(reader, descBuf, 24); - d->userDescription = String(descBuf, 24); - } - - if(d->version >= 14) - { - Uri_Read(d->mapUri, reader); -#if !__JHEXEN__ - d->mapTime = Reader_ReadInt32(reader); -#endif - - d->gameRules.read(reader); - } - else - { -#if !__JHEXEN__ - if(d->version < 13) - { - // In DOOM the high bit of the skill mode byte is also used for the - // "fast" game rule dd_bool. There is more confusion in that SM_NOTHINGS - // will result in 0xff and thus always set the fast bit. - // - // Here we decipher this assuming that if the skill mode is invalid then - // by default this means "spawn no things" and if so then the "fast" game - // rule is meaningless so it is forced off. - byte skillModePlusFastBit = Reader_ReadByte(reader); - d->gameRules.skill = (skillmode_t) (skillModePlusFastBit & 0x7f); - if(d->gameRules.skill < SM_BABY || d->gameRules.skill >= NUM_SKILL_MODES) - { - d->gameRules.skill = SM_NOTHINGS; - d->gameRules.fast = 0; - } - else - { - d->gameRules.fast = (skillModePlusFastBit & 0x80) != 0; - } - } - else -#endif - { - d->gameRules.skill = skillmode_t( Reader_ReadByte(reader) & 0x7f ); - // Interpret skill levels outside the normal range as "spawn no things". - if(d->gameRules.skill < SM_BABY || d->gameRules.skill >= NUM_SKILL_MODES) - { - d->gameRules.skill = SM_NOTHINGS; - } - } - - uint episode = Reader_ReadByte(reader) - 1; - uint map = Reader_ReadByte(reader) - 1; - Uri_Copy(d->mapUri, G_ComposeMapUri(episode, map)); - - d->gameRules.deathmatch = Reader_ReadByte(reader); -#if !__JHEXEN__ - if(d->version >= 13) - { - d->gameRules.fast = Reader_ReadByte(reader); - } -#endif - d->gameRules.noMonsters = Reader_ReadByte(reader); -#if __JHEXEN__ - d->gameRules.randomClasses = Reader_ReadByte(reader); -#endif -#if !__JHEXEN__ - d->gameRules.respawnMonsters = Reader_ReadByte(reader); -#endif -#if !__JHEXEN__ - /*skip old junk*/ if(d->version < 10) SV_Seek(2); - - d->mapTime = Reader_ReadInt32(reader); -#endif - } - -#if !__JHEXEN__ - for(int i = 0; i < MAXPLAYERS; ++i) - { - d->players[i] = Reader_ReadByte(reader); - } -#endif - - d->sessionId = Reader_ReadInt32(reader); - + d->meta.read(reader); d->needUpdateStatus = true; } @@ -570,7 +518,7 @@ String SaveInfo::statusAsText() const String SaveInfo::description() const { - AutoStr *currentMapUriAsText = Uri_ToString(mapUri()); + AutoStr *currentMapUriAsText = Uri_ToString(meta().mapUri); return String(_E(b) "%1\n" _E(.) _E(l) "IdentityKey: " _E(.)_E(i) "%2 " _E(.) @@ -580,26 +528,21 @@ String SaveInfo::description() const _E(l) "Session id: " _E(.)_E(i) "%6\n" _E(.) _E(D) "Game rules:\n" _E(.) " %7\n" _E(D) "Status: " _E(.) "%8") - .arg(userDescription()) - .arg(gameIdentityKey()) + .arg(meta().userDescription) + .arg(meta().gameIdentityKey) .arg(Str_Text(currentMapUriAsText)) .arg(NativePath(SV_SavePath() / fileName()).pretty()) - .arg(version()) - .arg(sessionId()) - .arg(gameRules().asText()) + .arg(meta().version) + .arg(meta().sessionId) + .arg(meta().gameRules.asText()) .arg(statusAsText()); } -int SaveInfo::magic() const -{ - return d->magic; -} - void SaveInfo::setMagic(int newMagic) { - if(d->magic != newMagic) + if(d->meta.magic != newMagic) { - d->magic = newMagic; + d->meta.magic = newMagic; d->needUpdateStatus = true; } } @@ -607,6 +550,6 @@ void SaveInfo::setMagic(int newMagic) SaveInfo *SaveInfo::fromReader(reader_s *reader) // static { SaveInfo *info = new SaveInfo; - info->read(reader); + info->readMeta(reader); return info; } diff --git a/doomsday/plugins/common/src/saveslots.cpp b/doomsday/plugins/common/src/saveslots.cpp index 2db124add5..f48760c0c4 100644 --- a/doomsday/plugins/common/src/saveslots.cpp +++ b/doomsday/plugins/common/src/saveslots.cpp @@ -73,7 +73,7 @@ DENG2_PIMPL_NOREF(SaveSlots::Slot) MNObject_SetFlags(ob, FO_SET, MNF_DISABLED); if(info->gameSessionIsLoadable()) { - MNEdit_SetText(ob, MNEDIT_STF_NO_ACTION, info->userDescription().toUtf8().constData()); + MNEdit_SetText(ob, MNEDIT_STF_NO_ACTION, info->meta().userDescription.toUtf8().constData()); MNObject_SetFlags(ob, FO_CLEAR, MNF_DISABLED); } else @@ -306,7 +306,7 @@ SaveSlots::Slot *SaveSlots::slotByUserDescription(String description) const DENG2_FOR_EACH_CONST(Instance::Slots, i, d->sslots) { SaveSlot &sslot = *i->second; - if(!sslot.saveInfo().userDescription().compareWithoutCase(description)) + if(!sslot.saveInfo().meta().userDescription.compareWithoutCase(description)) { return &sslot; } diff --git a/doomsday/plugins/doom/src/doomv9gamestatereader.cpp b/doomsday/plugins/doom/src/doomv9gamestatereader.cpp index b227807a3d..962aeb72c9 100644 --- a/doomsday/plugins/doom/src/doomv9gamestatereader.cpp +++ b/doomsday/plugins/doom/src/doomv9gamestatereader.cpp @@ -643,7 +643,7 @@ static void SaveInfo_Read_Dm_v19(SaveInfo *info, Reader *reader) uint map = Reader_ReadByte(reader) - 1; info->setMapUri(G_ComposeMapUri(episode, map)); - SaveInfo::Players players; de::zap(players); + SessionMetadata::Players players; de::zap(players); for(int i = 0; i < 4; ++i) { players[i] = Reader_ReadByte(reader); @@ -953,7 +953,7 @@ bool DoomV9GameStateReader::recognize(SaveInfo &info) // static if(strncmp(vcheck, "version ", 8))*/ { SaveInfo_Read_Dm_v19(&info, reader); - result = (info.version() <= 500); + result = (info.meta().version <= 500); } Reader_Delete(reader); reader = 0; @@ -984,11 +984,11 @@ void DoomV9GameStateReader::read(SaveInfo &info) * Load the map and configure some game settings. */ briefDisabled = true; - G_NewSession(info.mapUri(), 0/*not saved??*/, &info.gameRules()); + G_NewSession(info.meta().mapUri, 0/*not saved??*/, &info.meta().gameRules); G_SetGameAction(GA_NONE); /// @todo Necessary? // Recreate map state. - mapTime = info.mapTime(); + mapTime = info.meta().mapTime; d->readPlayers(); d->readMap(); diff --git a/doomsday/plugins/heretic/src/hereticv13gamestatereader.cpp b/doomsday/plugins/heretic/src/hereticv13gamestatereader.cpp index b8b7b546ec..5d5722b1c5 100644 --- a/doomsday/plugins/heretic/src/hereticv13gamestatereader.cpp +++ b/doomsday/plugins/heretic/src/hereticv13gamestatereader.cpp @@ -665,7 +665,7 @@ static void SaveInfo_Read_Hr_v13(SaveInfo *info, Reader *reader) uint map = Reader_ReadByte(reader) - 1; info->setMapUri(G_ComposeMapUri(episode, map)); - SaveInfo::Players players; de::zap(players); + SessionMetadata::Players players; de::zap(players); for(int i = 0; i < 4; ++i) { players[i] = Reader_ReadByte(reader); @@ -967,7 +967,7 @@ bool HereticV13GameStateReader::recognize(SaveInfo &info) // static if(strncmp(vcheck, "version ", 8))*/ { SaveInfo_Read_Hr_v13(&info, reader); - result = (info.version() == 130); + result = (info.meta().version == 130); } Reader_Delete(reader); reader = 0; @@ -998,11 +998,11 @@ void HereticV13GameStateReader::read(SaveInfo &info) briefDisabled = true; // Load a base map. - G_NewSession(info.mapUri(), 0/*not saved??*/, &info.gameRules()); + G_NewSession(info.meta().mapUri, 0/*not saved??*/, &info.meta().gameRules); G_SetGameAction(GA_NONE); /// @todo Necessary? // Recreate map state. - mapTime = info.mapTime(); + mapTime = info.meta().mapTime; d->readPlayers(); d->readMap();