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; +}