From d7502c99231dceb69bec755754067d6b918d734a Mon Sep 17 00:00:00 2001 From: danij Date: Mon, 10 Feb 2014 21:29:37 +0000 Subject: [PATCH] Refactor|libcommon: Extracted high level savegame management into new C++ class 'SaveSlots' At present the associated SaveInfos will continue to be owned by this class. In the future these should be referenced from an index provided by libdeng2's file system, which has none of the restrictions implicit in the original games' slot-based mechanism. Ultimately, SaveSlots will amount to little more than a mapping from file paths into a finite set of 'slots'. Todo: Cleanup --- doomsday/plugins/common/common.pri | 2 + doomsday/plugins/common/include/p_saveg.h | 67 +--- doomsday/plugins/common/include/saveslots.h | 136 +++++++ doomsday/plugins/common/src/p_saveg.cpp | 423 +++----------------- doomsday/plugins/common/src/saveslots.cpp | 378 +++++++++++++++++ 5 files changed, 581 insertions(+), 425 deletions(-) create mode 100644 doomsday/plugins/common/include/saveslots.h create mode 100644 doomsday/plugins/common/src/saveslots.cpp diff --git a/doomsday/plugins/common/common.pri b/doomsday/plugins/common/common.pri index 7947bcb6da..0b6c169133 100644 --- a/doomsday/plugins/common/common.pri +++ b/doomsday/plugins/common/common.pri @@ -68,6 +68,7 @@ HEADERS += \ $$common_inc/polyobjs.h \ $$common_inc/r_common.h \ $$common_inc/saveinfo.h \ + $$common_inc/saveslots.h \ $$common_inc/thinkerinfo.h \ $$common_inc/x_hair.h \ $$common_inc/xgclass.h @@ -131,5 +132,6 @@ SOURCES += \ $$common_src/polyobjs.cpp \ $$common_src/r_common.c \ $$common_src/saveinfo.cpp \ + $$common_src/saveslots.cpp \ $$common_src/thinkerinfo.cpp \ $$common_src/x_hair.c diff --git a/doomsday/plugins/common/include/p_saveg.h b/doomsday/plugins/common/include/p_saveg.h index d1a49f9ded..864dd045aa 100644 --- a/doomsday/plugins/common/include/p_saveg.h +++ b/doomsday/plugins/common/include/p_saveg.h @@ -43,58 +43,13 @@ void SV_Initialize(void); /// Shutdown this module. void SV_Shutdown(void); -/** - * Force an update of the cached game-save info. To be called (sparingly) at - * strategic points when an update is necessary (e.g., the game-save paths - * have changed). - * - * @note It is not necessary to call this after a game-save is made, this - * module will do so automatically. - */ -void SV_UpdateAllSaveInfo(void); +dd_bool SV_RecogniseGameState(Str const *path, SaveInfo *info); -/** - * Lookup a save slot by searching for a match on game-save name. Search is in - * ascending logical slot order 0...N (where N is the number of available save - * slots in the current game). - * - * @param name Name of the game-save to look for. Case insensitive. - * - * @return Logical slot number of the found game-save else @c -1 - */ +void SV_UpdateAllSaveInfo(void); int SV_SlotForSaveName(char const *name); - -/** - * Parse @a str and determine whether it references a logical game-save slot. - * - * @param str String to be parsed. Parse is divided into three passes. - * Pass 1: Check for a known game-save name which matches this. - * Search is in ascending logical slot order 0..N (where N - * is the number of available save slots). - * Pass 2: Check for keyword identifiers. - * = The "auto save" slot. - * = The last used slot. - * = The currently nominated "quick save" slot. - * Pass 3: Check for a logical save slot number. - * - * @return Save slot identifier of the slot else @c -1 - */ int SV_ParseSlotIdentifier(char const *str); - -/** - * Returns @c true iff @a slot is a valid logical save slot. - */ dd_bool SV_IsValidSlot(int slot); - -/** - * Returns @c true iff @a slot is user-writable save slot (i.e., its not one - * of the special slots such as @em auto). - */ dd_bool SV_IsUserWritableSlot(int slot); - -/** - * Returns @c true iff a game-save is present for logical save @a slot. - */ dd_bool SV_IsSlotUsed(int slot); #if __JHEXEN__ @@ -105,27 +60,9 @@ dd_bool SV_IsSlotUsed(int slot); dd_bool SV_HxHaveMapStateForSlot(int slot, uint map); #endif -/** - * Returns the save info for save @a slot. Always returns SaveInfo even if - * supplied with an invalid or unused slot identifer (a null object). - */ SaveInfo *SV_SaveInfoForSlot(int slot); - -/** - * Compose the textual identifier/name for save @a slot. - * - * @return Name/identifier associated with slot @a slot. - */ AutoStr *SV_ComposeSlotIdentifier(int slot); - -/** - * Deletes all save game files associated with a slot number. - */ void SV_ClearSlot(int slot); - -/** - * Copies all the save game files from one slot to another. - */ void SV_CopySlot(int sourceSlot, int destSlot); /** diff --git a/doomsday/plugins/common/include/saveslots.h b/doomsday/plugins/common/include/saveslots.h new file mode 100644 index 0000000000..8096cd880c --- /dev/null +++ b/doomsday/plugins/common/include/saveslots.h @@ -0,0 +1,136 @@ +/** @file saveslots.h Map of logical game save slots. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2005-2013 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef LIBCOMMON_SAVESLOTS_H +#define LIBCOMMON_SAVESLOTS_H + +#include "common.h" +#include "saveinfo.h" +#include "p_savedef.h" /// @todo remove me + +/** + * Maps saved games into a finite set of "save slots". + * + * @todo At present the associated SaveInfos are actually owned by this class. In the future + * these should be referenced from another container which has none of the restrictions of + * the slot-based mechanism. + * + * @ingroup libcommon + */ +class SaveSlots +{ +public: + SaveSlots(); + + void clearSaveInfo(); + + /// Re-build game-save info by re-scanning the save paths and populating the list. + void buildSaveInfo(); + + /** + * Force an update of the cached game-save info. To be called (sparingly) at strategic + * points when an update is necessary (e.g., the game-save paths have changed). + * + * @note It is not necessary to call this after a game-save is made, this module will do + * so automatically. + */ + void updateAllSaveInfo(); + + /** + * Composes the textual identifier/name for save @a slot. + */ + AutoStr *composeSlotIdentifier(int slot); + + /** + * Parse @a str and determine whether it references a logical game-save slot. + * + * @param str String to be parsed. Parse is divided into three passes. + * Pass 1: Check for a known game-save name which matches this. + * Search is in ascending logical slot order 0..N (where N is the number + * of available save slots). + * Pass 2: Check for keyword identifiers. + * = The "auto save" slot. + * = The last used slot. + * = The currently nominated "quick save" slot. + * Pass 3: Check for a logical save slot number. + * + * @return Save slot identifier of the slot else @c -1 + */ + int parseSlotIdentifier(char const *str); + + /** + * Lookup a save slot by searching for a match on game-save name. Search is in ascending + * logical slot order 0...N (where N is the number of available save slots). + * + * @param name Name of the game-save to look for. Case insensitive. + * + * @return Logical slot number of the found game-save else @c -1 + */ + int slotForSaveName(char const *description); + + /** + * Returns @c true iff a game-save is present for logical save @a slot. + */ + bool slotInUse(int slot); + + /** + * Returns @c true iff @a slot is a valid logical save slot. + */ + bool isValidSlot(int slot); + + /** + * Returns @c true iff @a slot is user-writable save slot (i.e., its not one of the special + * slots such as @em auto). + */ + bool isUserWritableSlot(int slot); + + /** + * Returns the save info for save @a slot. Always returns SaveInfo even if supplied with an + * invalid or unused slot identifer (a null object). + */ + SaveInfo *findSaveInfoForSlot(int slot); + + void replaceSaveInfo(int slot, SaveInfo *newInfo); + + /** + * Deletes all save game files associated with a slot number. + */ + void clearSlot(int slot); + + /** + * Copies all the save game files from one slot to another. + */ + void copySlot(int sourceSlot, int destSlot); + + /** + * Compose the (possibly relative) path to save state associated with the logical save @a slot. + * + * @param slot Logical save slot identifier. + * @param map If @c >= 0 include this logical map index in the composed path. + * + * @return The composed path if reachable (else a zero-length string). + */ + AutoStr *composeGameSavePathForSlot(int slot, int map = -1); + +private: + DENG2_PRIVATE(d) +}; + +#endif // LIBCOMMON_MAPSTATEREADER_H diff --git a/doomsday/plugins/common/src/p_saveg.cpp b/doomsday/plugins/common/src/p_saveg.cpp index 9e86e10698..724324a45c 100644 --- a/doomsday/plugins/common/src/p_saveg.cpp +++ b/doomsday/plugins/common/src/p_saveg.cpp @@ -37,6 +37,7 @@ #include "polyobjs.h" #include "mapstatereader.h" #include "mapstatewriter.h" +#include "saveslots.h" #include #include #include @@ -44,16 +45,6 @@ using namespace dmu_lib; -#define MAX_HUB_MAPS 99 - -#define FF_FULLBRIGHT 0x8000 ///< Used to be a flag in thing->frame. -#define FF_FRAMEMASK 0x7fff - -#if __JHEXEN__ -/// Symbolic identifier used to mark references to players in map states. -static ThingSerialId const TargetPlayerId = -2; -#endif - struct playerheader_t { int numPowers; @@ -70,42 +61,22 @@ struct playerheader_t #endif }; -enum sectorclass_t -{ - sc_normal, - sc_ploff, ///< plane offset -#if !__JHEXEN__ - sc_xg1, -#endif - NUM_SECTORCLASSES -}; - -enum lineclass_t -{ - lc_normal, -#if !__JHEXEN__ - lc_xg1, -#endif - NUM_LINECLASSES -}; - static bool inited = false; -static int cvarLastSlot; // -1 = Not yet loaded/saved in this game session. -static int cvarQuickSlot; // -1 = Not yet chosen/determined. +static int cvarLastSlot; ///< @c -1= Not yet loaded/saved in this game session. +static int cvarQuickSlot; ///< @c -1= Not yet chosen/determined. -static SaveInfo **saveInfo; -static SaveInfo *autoSaveInfo; -#if __JHEXEN__ -static SaveInfo *baseSaveInfo; -#endif -static SaveInfo *nullSaveInfo; - -static SaveInfo const *curInfo; +static SaveSlots saveSlots; +SaveInfo const *curInfo; static playerheader_t playerHeader; static dd_bool playerHeaderOK; +#if __JHEXEN__ +/// Symbolic identifier used to mark references to players in map states. +static ThingSerialId const TargetPlayerId = -2; +#endif + static mobj_t **thingArchive; int thingArchiveVersion; uint thingArchiveSize; @@ -114,41 +85,11 @@ static bool thingArchiveExcludePlayers; static int saveToRealPlayerNum[MAXPLAYERS]; #if __JHEXEN__ static targetplraddress_t *targetPlayerAddrs; -static byte *saveBuffer; #endif -/** - * Compose the (possibly relative) path to the game-save associated - * with the logical save @a slot. - * - * @param slot Logical save slot identifier. - * @param map If @c >= 0 include this logical map index in the composed path. - * @return The composed path if reachable (else a zero-length string). - */ -static AutoStr *composeGameSavePathForSlot(int slot, int map = -1) -{ - DENG_ASSERT(inited); - - AutoStr *path = AutoStr_NewStd(); - - // A valid slot? - if(!SV_IsValidSlot(slot)) return path; - - // Do we have a valid path? - if(!F_MakePath(SV_SavePath())) return path; - - // Compose the full game-save path and filename. - if(map >= 0) - { - Str_Appendf(path, "%s" SAVEGAMENAME "%i%02i." SAVEGAMEEXTENSION, SV_SavePath(), slot, map); - } - else - { - Str_Appendf(path, "%s" SAVEGAMENAME "%i." SAVEGAMEEXTENSION, SV_SavePath(), slot); - } - F_TranslatePath(path, path); - return path; -} +#if __JHEXEN__ +static byte *saveBuffer; +#endif #if !__JHEXEN__ /** @@ -171,33 +112,6 @@ static AutoStr *composeGameSavePathForClientGameId(uint gameId) } #endif -static void clearSaveInfo() -{ - if(saveInfo) - { - for(int i = 0; i < NUMSAVESLOTS; ++i) - { - delete saveInfo[i]; - } - M_Free(saveInfo); saveInfo = 0; - } - - if(autoSaveInfo) - { - delete autoSaveInfo; autoSaveInfo = 0; - } -#if __JHEXEN__ - if(baseSaveInfo) - { - delete baseSaveInfo; baseSaveInfo = 0; - } -#endif - if(nullSaveInfo) - { - delete nullSaveInfo; nullSaveInfo = 0; - } -} - static void SV_SaveInfo_Read(SaveInfo *info, Reader *reader) { #if __JHEXEN__ @@ -273,7 +187,7 @@ static bool recogniseNativeState(Str const *path, SaveInfo *info) return true; } -static bool recogniseGameState(Str const *path, SaveInfo *info) +dd_bool SV_RecogniseGameState(Str const *path, SaveInfo *info) { if(path && info) { @@ -293,268 +207,64 @@ static bool recogniseGameState(Str const *path, SaveInfo *info) return false; } -static void updateSaveInfo(Str const *path, SaveInfo *info) -{ - if(!info) return; - - if(!path || Str_IsEmpty(path)) - { - // The save path cannot be accessed for some reason. Perhaps its a - // network path? Clear the info for this slot. - info->setDescription(0); - info->setGameId(0); - return; - } - - // Is this a recognisable save state? - if(!recogniseGameState(path, info)) - { - // Clear the info for this slot. - info->setDescription(0); - info->setGameId(0); - return; - } - - // Ensure we have a valid name. - if(Str_IsEmpty(info->description())) - { - info->setDescription(AutoStr_FromText("UNNAMED")); - } -} - -/// Re-build game-save info by re-scanning the save paths and populating the list. -static void buildSaveInfo() -{ - DENG_ASSERT(inited); - - if(!saveInfo) - { - // Not yet been here. We need to allocate and initialize the game-save info list. - saveInfo = reinterpret_cast(M_Malloc(NUMSAVESLOTS * sizeof(*saveInfo))); - - // Initialize. - for(int i = 0; i < NUMSAVESLOTS; ++i) - { - saveInfo[i] = new SaveInfo; - } - autoSaveInfo = new SaveInfo; -#if __JHEXEN__ - baseSaveInfo = new SaveInfo; -#endif - nullSaveInfo = new SaveInfo; - } - - /// Scan the save paths and populate the list. - /// @todo We should look at all files on the save path and not just those - /// which match the default game-save file naming convention. - for(int i = 0; i < NUMSAVESLOTS; ++i) - { - SaveInfo *info = saveInfo[i]; - updateSaveInfo(composeGameSavePathForSlot(i), info); - } - updateSaveInfo(composeGameSavePathForSlot(AUTO_SLOT), autoSaveInfo); -#if __JHEXEN__ - updateSaveInfo(composeGameSavePathForSlot(BASE_SLOT), baseSaveInfo); -#endif -} - -/// Given a logical save slot identifier retrieve the assciated game-save info. -static SaveInfo *findSaveInfoForSlot(int slot) -{ - DENG_ASSERT(inited); - - if(!SV_IsValidSlot(slot)) return nullSaveInfo; - - // On first call - automatically build and populate game-save info. - if(!saveInfo) - { - buildSaveInfo(); - } - - // Retrieve the info for this slot. - if(slot == AUTO_SLOT) return autoSaveInfo; -#if __JHEXEN__ - if(slot == BASE_SLOT) return baseSaveInfo; -#endif - return saveInfo[slot]; -} - -static void replaceSaveInfo(int slot, SaveInfo *newInfo) -{ - DENG_ASSERT(SV_IsValidSlot(slot)); - - SaveInfo **destAdr; - if(slot == AUTO_SLOT) - { - destAdr = &autoSaveInfo; - } -#if __JHEXEN__ - else if(slot == BASE_SLOT) - { - destAdr = &baseSaveInfo; - } -#endif - else - { - destAdr = &saveInfo[slot]; - } - - if(*destAdr) delete (*destAdr); - *destAdr = newInfo; -} - AutoStr *SV_ComposeSlotIdentifier(int slot) { - AutoStr *str = AutoStr_NewStd(); - if(slot < 0) return Str_Set(str, "(invalid slot)"); - if(slot == AUTO_SLOT) return Str_Set(str, ""); -#if __JHEXEN__ - if(slot == BASE_SLOT) return Str_Set(str, ""); -#endif - return Str_Appendf(str, "%i", slot); -} - -/** - * Determines whether to announce when the specified @a slot is cleared. - */ -static bool announceOnClearingSlot(int slot) -{ -#if _DEBUG - return true; // Always. -#endif -#if __JHEXEN__ - return (slot != AUTO_SLOT && slot != BASE_SLOT); -#else - return (slot != AUTO_SLOT); -#endif + return saveSlots.composeSlotIdentifier(slot); } void SV_ClearSlot(int slot) { DENG_ASSERT(inited); - - if(!SV_IsValidSlot(slot)) return; - - if(announceOnClearingSlot(slot)) - { - AutoStr *ident = SV_ComposeSlotIdentifier(slot); - App_Log(DE2_RES_MSG, "Clearing save slot %s", Str_Text(ident)); - } - - for(int i = 0; i < MAX_HUB_MAPS; ++i) - { - AutoStr *path = composeGameSavePathForSlot(slot, i); - SV_RemoveFile(path); - } - - AutoStr *path = composeGameSavePathForSlot(slot); - SV_RemoveFile(path); - - // Update save info for this slot. - updateSaveInfo(path, findSaveInfoForSlot(slot)); + saveSlots.clearSlot(slot); } dd_bool SV_IsValidSlot(int slot) { - if(slot == AUTO_SLOT) return true; -#if __JHEXEN__ - if(slot == BASE_SLOT) return true; -#endif - return (slot >= 0 && slot < NUMSAVESLOTS); + DENG_ASSERT(inited); + return saveSlots.isValidSlot(slot); } dd_bool SV_IsUserWritableSlot(int slot) { - if(slot == AUTO_SLOT) return false; -#if __JHEXEN__ - if(slot == BASE_SLOT) return false; -#endif - return SV_IsValidSlot(slot); + DENG_ASSERT(inited); + return saveSlots.isUserWritableSlot(slot); } SaveInfo *SV_SaveInfoForSlot(int slot) { DENG_ASSERT(inited); - return findSaveInfoForSlot(slot); + return saveSlots.findSaveInfoForSlot(slot); } void SV_UpdateAllSaveInfo() { DENG_ASSERT(inited); - buildSaveInfo(); + saveSlots.buildSaveInfo(); } int SV_ParseSlotIdentifier(char const *str) { - // Try game-save name match. - int slot = SV_SlotForSaveName(str); - if(slot >= 0) return slot; - - // Try keyword identifiers. - if(!stricmp(str, "last") || !stricmp(str, "")) - { - return Con_GetInteger("game-save-last-slot"); - } - if(!stricmp(str, "quick") || !stricmp(str, "")) - { - return Con_GetInteger("game-save-quick-slot"); - } - if(!stricmp(str, "auto") || !stricmp(str, "")) - { - return AUTO_SLOT; - } - - // Try logical slot identifier. - if(M_IsStringValidInt(str)) - { - return atoi(str); - } - - // Unknown/not found. - return -1; + DENG_ASSERT(inited); + return saveSlots.parseSlotIdentifier(str); } int SV_SlotForSaveName(char const *name) { DENG_ASSERT(inited); - - int saveSlot = -1; - if(name && name[0]) - { - // On first call - automatically build and populate game-save info. - if(!saveInfo) - { - buildSaveInfo(); - } - - int i = 0; - do - { - SaveInfo *info = saveInfo[i]; - if(!Str_CompareIgnoreCase(info->description(), name)) - { - // This is the one! - saveSlot = i; - } - } while(-1 == saveSlot && ++i < NUMSAVESLOTS); - } - return saveSlot; + return saveSlots.slotForSaveName(name); } dd_bool SV_IsSlotUsed(int slot) { DENG_ASSERT(inited); - if(SV_ExistingFile(composeGameSavePathForSlot(slot))) - { - return SV_SaveInfoForSlot(slot)->isLoadable(); - } - return false; + return saveSlots.slotInUse(slot); } #if __JHEXEN__ dd_bool SV_HxHaveMapStateForSlot(int slot, uint map) { - AutoStr *path = composeGameSavePathForSlot(slot, (int)map+1); + DENG_ASSERT(inited); + AutoStr *path = saveSlots.composeGameSavePathForSlot(slot, (int)map+1); if(!path || Str_IsEmpty(path)) return false; return SV_ExistingFile(path); } @@ -563,44 +273,13 @@ dd_bool SV_HxHaveMapStateForSlot(int slot, uint map) void SV_CopySlot(int sourceSlot, int destSlot) { DENG_ASSERT(inited); - - if(!SV_IsValidSlot(sourceSlot)) - { - DENG_ASSERT(!"SV_CopySlot: Source slot invalid"); - return; - } - - if(!SV_IsValidSlot(destSlot)) - { - DENG_ASSERT(!"SV_CopySlot: Dest slot invalid"); - return; - } - - // Clear all save files at destination slot. - SV_ClearSlot(destSlot); - - AutoStr *src, *dst; - for(int i = 0; i < MAX_HUB_MAPS; ++i) - { - src = composeGameSavePathForSlot(sourceSlot, i); - dst = composeGameSavePathForSlot(destSlot, i); - SV_CopyFile(src, dst); - } - - src = composeGameSavePathForSlot(sourceSlot); - dst = composeGameSavePathForSlot(destSlot); - SV_CopyFile(src, dst); - - // Copy saveinfo too. - SaveInfo *info = findSaveInfoForSlot(sourceSlot); - DENG_ASSERT(info != 0); - replaceSaveInfo(destSlot, new SaveInfo(*info)); + saveSlots.copySlot(sourceSlot, destSlot); } #if __JHEXEN__ void SV_HxInitBaseSlot() { - SV_ClearSlot(BASE_SLOT); + saveSlots.clearSlot(BASE_SLOT); } #endif @@ -1556,6 +1235,9 @@ static void RestoreMobj(mobj_t *mo, int ver) */ int SV_ReadMobj(thinker_t *th, MapStateReader *msr) { +#define FF_FULLBRIGHT 0x8000 ///< Used to be a flag in thing->frame. +#define FF_FRAMEMASK 0x7fff + Reader *reader = msr->reader(); mobj_t *mo = (mobj_t *) th; @@ -1812,6 +1494,9 @@ int SV_ReadMobj(thinker_t *th, MapStateReader *msr) RestoreMobj(mo, ver); return false; + +#undef FF_FRAMEMASK +#undef FF_FULLBRIGHT } /** @@ -2016,6 +1701,16 @@ static void readPlayers(SaveInfo &info, dd_bool *infile, dd_bool *loaded, #endif } +enum sectorclass_t +{ + sc_normal, + sc_ploff, ///< plane offset +#if !__JHEXEN__ + sc_xg1, +#endif + NUM_SECTORCLASSES +}; + void SV_WriteSector(Sector *sec, MapStateWriter *msw) { Writer *writer = msw->writer(); @@ -2256,6 +1951,15 @@ void SV_ReadSector(Sector *sec, MapStateReader *msr) xsec->soundTarget = 0; } +enum lineclass_t +{ + lc_normal, +#if !__JHEXEN__ + lc_xg1, +#endif + NUM_LINECLASSES +}; + void SV_WriteLine(Line *li, MapStateWriter *msw) { Writer *writer = msw->writer(); @@ -2648,7 +2352,6 @@ void SV_Initialize() static bool firstInit = true; SV_InitIO(); - saveInfo = 0; inited = true; if(firstInit) @@ -2675,7 +2378,7 @@ void SV_Shutdown() if(!inited) return; SV_ShutdownIO(); - clearSaveInfo(); + saveSlots.clearSaveInfo(); cvarLastSlot = -1; cvarQuickSlot = -1; @@ -2795,7 +2498,7 @@ static int SV_LoadState(Str const *path, SaveInfo *info) // Load the current map state. #if __JHEXEN__ - readMapState(reader, info->version(), composeGameSavePathForSlot(BASE_SLOT, gameMap+1)); + readMapState(reader, info->version(), saveSlots.composeGameSavePathForSlot(BASE_SLOT, gameMap+1)); #else readMapState(reader, info->version()); #endif @@ -2942,7 +2645,7 @@ dd_bool SV_LoadGame(int slot) if(!SV_IsValidSlot(slot)) return false; - AutoStr *path = composeGameSavePathForSlot(slot); + AutoStr *path = saveSlots.composeGameSavePathForSlot(slot); if(Str_IsEmpty(path)) { App_Log(DE2_RES_ERROR, "Game not loaded: path \"%s\" is unreachable", SV_SavePath()); @@ -3147,7 +2850,7 @@ static int saveStateWorker(Str const *path, SaveInfo *saveInfo) */ #if __JHEXEN__ // ...map state is actually written to a separate file. - SV_OpenFile(composeGameSavePathForSlot(BASE_SLOT, gameMap+1), "wp"); + SV_OpenFile(saveSlots.composeGameSavePathForSlot(BASE_SLOT, gameMap+1), "wp"); #endif MapStateWriter(thingArchiveExcludePlayers).write(writer); @@ -3199,7 +2902,7 @@ dd_bool SV_SaveGame(int slot, char const *name) return false; } - AutoStr *path = composeGameSavePathForSlot(logicalSlot); + AutoStr *path = saveSlots.composeGameSavePathForSlot(logicalSlot); if(Str_IsEmpty(path)) { App_Log(DE2_RES_WARNING, "Cannot save game: path \"%s\" is unreachable", SV_SavePath()); @@ -3212,7 +2915,7 @@ dd_bool SV_SaveGame(int slot, char const *name) if(!saveError) { // Swap the save info. - replaceSaveInfo(logicalSlot, info); + saveSlots.replaceSaveInfo(logicalSlot, info); #if __JHEXEN__ // Copy base slot to destination slot. @@ -3241,7 +2944,7 @@ void SV_HxSaveHubMap() { playerHeaderOK = false; // Uninitialized. - SV_OpenFile(composeGameSavePathForSlot(BASE_SLOT, gameMap+1), "wp"); + SV_OpenFile(saveSlots.composeGameSavePathForSlot(BASE_SLOT, gameMap+1), "wp"); // Set the mobj archive numbers initThingArchiveForSave(true /*exclude players*/); @@ -3271,7 +2974,7 @@ void SV_HxLoadHubMap() Reader *reader = SV_NewReader(); // Been here before, load the previous map state. - readMapState(reader, info->version(), composeGameSavePathForSlot(BASE_SLOT, gameMap+1)); + readMapState(reader, info->version(), saveSlots.composeGameSavePathForSlot(BASE_SLOT, gameMap+1)); Reader_Delete(reader); } diff --git a/doomsday/plugins/common/src/saveslots.cpp b/doomsday/plugins/common/src/saveslots.cpp new file mode 100644 index 0000000000..72eb315241 --- /dev/null +++ b/doomsday/plugins/common/src/saveslots.cpp @@ -0,0 +1,378 @@ +/** @file saveslots.cpp Map of logical game save slots. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2005-2013 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "common.h" +#include "saveslots.h" + +#include "p_saveg.h" /// @todo remove me +#include + +#define MAX_HUB_MAPS 99 + +DENG2_PIMPL(SaveSlots) +{ + SaveInfo **saveInfo; + SaveInfo *autoSaveInfo; +#if __JHEXEN__ + SaveInfo *baseSaveInfo; +#endif + SaveInfo *nullSaveInfo; + + Instance(Public *i) + : Base(i) + , saveInfo(0) + , autoSaveInfo(0) +#if __JHEXEN__ + , baseSaveInfo(0) +#endif + , nullSaveInfo(0) + {} + + /** + * Determines whether to announce when the specified @a slot is cleared. + */ + bool announceOnClearingSlot(int slot) + { +#if _DEBUG + return true; // Always. +#endif +#if __JHEXEN__ + return (slot != AUTO_SLOT && slot != BASE_SLOT); +#else + return (slot != AUTO_SLOT); +#endif + } + + void updateSaveInfo(Str const *path, SaveInfo *info) + { + if(!info) return; + + if(!path || Str_IsEmpty(path)) + { + // The save path cannot be accessed for some reason. Perhaps its a + // network path? Clear the info for this slot. + info->setDescription(0); + info->setGameId(0); + return; + } + + // Is this a recognisable save state? + if(!SV_RecogniseGameState(path, info)) + { + // Clear the info for this slot. + info->setDescription(0); + info->setGameId(0); + return; + } + + // Ensure we have a valid name. + if(Str_IsEmpty(info->description())) + { + info->setDescription(AutoStr_FromText("UNNAMED")); + } + } +}; + +SaveSlots::SaveSlots() : d(new Instance(this)) +{} + +void SaveSlots::clearSaveInfo() +{ + if(d->saveInfo) + { + for(int i = 0; i < NUMSAVESLOTS; ++i) + { + delete d->saveInfo[i]; + } + M_Free(d->saveInfo); d->saveInfo = 0; + } + + if(d->autoSaveInfo) + { + delete d->autoSaveInfo; d->autoSaveInfo = 0; + } +#if __JHEXEN__ + if(d->baseSaveInfo) + { + delete d->baseSaveInfo; d->baseSaveInfo = 0; + } +#endif + if(d->nullSaveInfo) + { + delete d->nullSaveInfo; d->nullSaveInfo = 0; + } +} + +void SaveSlots::buildSaveInfo() +{ + if(!d->saveInfo) + { + // Not yet been here. We need to allocate and initialize the game-save info list. + d->saveInfo = (SaveInfo **)M_Malloc(NUMSAVESLOTS * sizeof(*d->saveInfo)); + + // Initialize. + for(int i = 0; i < NUMSAVESLOTS; ++i) + { + d->saveInfo[i] = new SaveInfo; + } + d->autoSaveInfo = new SaveInfo; +#if __JHEXEN__ + d->baseSaveInfo = new SaveInfo; +#endif + d->nullSaveInfo = new SaveInfo; + } + + /// Scan the save paths and populate the list. + /// @todo We should look at all files on the save path and not just those + /// which match the default game-save file naming convention. + for(int i = 0; i < NUMSAVESLOTS; ++i) + { + SaveInfo *info = d->saveInfo[i]; + d->updateSaveInfo(composeGameSavePathForSlot(i), info); + } + d->updateSaveInfo(composeGameSavePathForSlot(AUTO_SLOT), d->autoSaveInfo); +#if __JHEXEN__ + d->updateSaveInfo(composeGameSavePathForSlot(BASE_SLOT), d->baseSaveInfo); +#endif +} + +void SaveSlots::updateAllSaveInfo() +{ + buildSaveInfo(); +} + +AutoStr *SaveSlots::composeSlotIdentifier(int slot) +{ + AutoStr *str = AutoStr_NewStd(); + if(slot < 0) return Str_Set(str, "(invalid slot)"); + if(slot == AUTO_SLOT) return Str_Set(str, ""); +#if __JHEXEN__ + if(slot == BASE_SLOT) return Str_Set(str, ""); +#endif + return Str_Appendf(str, "%i", slot); +} + +int SaveSlots::parseSlotIdentifier(char const *str) +{ + // Try game-save name match. + int slot = SV_SlotForSaveName(str); + if(slot >= 0) return slot; + + // Try keyword identifiers. + if(!stricmp(str, "last") || !stricmp(str, "")) + { + return Con_GetInteger("game-save-last-slot"); + } + if(!stricmp(str, "quick") || !stricmp(str, "")) + { + return Con_GetInteger("game-save-quick-slot"); + } + if(!stricmp(str, "auto") || !stricmp(str, "")) + { + return AUTO_SLOT; + } + + // Try logical slot identifier. + if(M_IsStringValidInt(str)) + { + return atoi(str); + } + + // Unknown/not found. + return -1; +} + +int SaveSlots::slotForSaveName(char const *description) +{ + DENG_ASSERT(description != 0); + + int slot = -1; + if(description && description[0]) + { + // On first call - automatically build and populate game-save info. + if(!d->saveInfo) + { + buildSaveInfo(); + } + + int i = 0; + do + { + SaveInfo *info = d->saveInfo[i]; + if(!Str_CompareIgnoreCase(info->description(), description)) + { + // This is the one! + slot = i; + } + } while(-1 == slot && ++i < NUMSAVESLOTS); + } + + return slot; +} + +bool SaveSlots::slotInUse(int slot) +{ + if(SV_ExistingFile(composeGameSavePathForSlot(slot))) + { + return findSaveInfoForSlot(slot)->isLoadable(); + } + return false; +} + +bool SaveSlots::isValidSlot(int slot) +{ + if(slot == AUTO_SLOT) return true; +#if __JHEXEN__ + if(slot == BASE_SLOT) return true; +#endif + return (slot >= 0 && slot < NUMSAVESLOTS); +} + +bool SaveSlots::isUserWritableSlot(int slot) +{ + if(slot == AUTO_SLOT) return false; +#if __JHEXEN__ + if(slot == BASE_SLOT) return false; +#endif + return isValidSlot(slot); +} + +SaveInfo *SaveSlots::findSaveInfoForSlot(int slot) +{ + if(!isValidSlot(slot)) return d->nullSaveInfo; + + // On first call - automatically build and populate game-save info. + if(!d->saveInfo) + { + buildSaveInfo(); + } + + // Retrieve the info for this slot. + if(slot == AUTO_SLOT) return d->autoSaveInfo; +#if __JHEXEN__ + if(slot == BASE_SLOT) return d->baseSaveInfo; +#endif + return d->saveInfo[slot]; +} + +void SaveSlots::replaceSaveInfo(int slot, SaveInfo *newInfo) +{ + DENG_ASSERT(isValidSlot(slot)); + + SaveInfo **destAdr; + if(slot == AUTO_SLOT) + { + destAdr = &d->autoSaveInfo; + } +#if __JHEXEN__ + else if(slot == BASE_SLOT) + { + destAdr = &d->baseSaveInfo; + } +#endif + else + { + destAdr = &d->saveInfo[slot]; + } + + if(*destAdr) delete (*destAdr); + *destAdr = newInfo; +} + +void SaveSlots::clearSlot(int slot) +{ + if(!isValidSlot(slot)) return; + + if(d->announceOnClearingSlot(slot)) + { + AutoStr *ident = SV_ComposeSlotIdentifier(slot); + App_Log(DE2_RES_MSG, "Clearing save slot %s", Str_Text(ident)); + } + + for(int i = 0; i < MAX_HUB_MAPS; ++i) + { + AutoStr *path = composeGameSavePathForSlot(slot, i); + SV_RemoveFile(path); + } + + AutoStr *path = composeGameSavePathForSlot(slot); + SV_RemoveFile(path); + + d->updateSaveInfo(path, findSaveInfoForSlot(slot)); +} + +void SaveSlots::copySlot(int sourceSlot, int destSlot) +{ + if(!isValidSlot(sourceSlot)) + { + DENG_ASSERT(!"SaveSlots::copySlot: Source slot invalid"); + return; + } + + if(!isValidSlot(destSlot)) + { + DENG_ASSERT(!"SaveSlots::copySlot: Dest slot invalid"); + return; + } + + // Clear all save files at destination slot. + clearSlot(destSlot); + + AutoStr *src, *dst; + for(int i = 0; i < MAX_HUB_MAPS; ++i) + { + src = composeGameSavePathForSlot(sourceSlot, i); + dst = composeGameSavePathForSlot(destSlot, i); + SV_CopyFile(src, dst); + } + + src = composeGameSavePathForSlot(sourceSlot); + dst = composeGameSavePathForSlot(destSlot); + SV_CopyFile(src, dst); + + // Copy saveinfo too. + SaveInfo *info = findSaveInfoForSlot(sourceSlot); + DENG_ASSERT(info != 0); + replaceSaveInfo(destSlot, new SaveInfo(*info)); +} + +AutoStr *SaveSlots::composeGameSavePathForSlot(int slot, int map) +{ + AutoStr *path = AutoStr_NewStd(); + + // A valid slot? + if(!isValidSlot(slot)) return path; + + // Do we have a valid path? + /// @todo Do not do alter the file system until necessary. + if(!F_MakePath(SV_SavePath())) return path; + + // Compose the full game-save path and filename. + if(map >= 0) + { + Str_Appendf(path, "%s" SAVEGAMENAME "%i%02i." SAVEGAMEEXTENSION, SV_SavePath(), slot, map); + } + else + { + Str_Appendf(path, "%s" SAVEGAMENAME "%i." SAVEGAMEEXTENSION, SV_SavePath(), slot); + } + F_TranslatePath(path, path); + return path; +}