diff --git a/doomsday/plugins/common/include/g_common.h b/doomsday/plugins/common/include/g_common.h index ae7066bb42..f5cc687099 100644 --- a/doomsday/plugins/common/include/g_common.h +++ b/doomsday/plugins/common/include/g_common.h @@ -154,6 +154,8 @@ D_CMD( CCmdExitLevel ); #endif #if __cplusplus +#include + class GameStateReaderFactory; class SaveSlots; @@ -162,6 +164,43 @@ class SaveSlots; */ SaveSlots &G_SaveSlots(); +/** + * 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 description which matches this. + * Search is in logical save slot creation order. + * 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 unique save slot identifier. + * + * @return The parsed slot id if found; otherwise a zero-length string. + */ +de::String G_SaveSlotIdFromUserInput(de::String str); + +/** + * To be called to schedule a save game-save action. + * + * @param slotId Unique identifier of the save slot to use. + * @param userDescription New user description for the game-save. Can be @c NULL in which + * case the name will not change if the slot has already been used. + * If an empty string a new name will be generated automatically. + * + * @return @c true iff @a slotId is valid and saving is presently possible. + */ +bool G_SaveGame(de::String slotId, de::String *userDescription = 0); + +/** + * To be called to schedule a load game-save action. + * + * @param slotId Unique identifier of the save slot to use. + * + * @return @c true iff @a slotId is in use and loading is presently possible. + */ +bool G_LoadGame(de::String slotId); + /** * Returns the game's GameStateReaderFactory. */ diff --git a/doomsday/plugins/common/include/hu_lib.h b/doomsday/plugins/common/include/hu_lib.h index d31cea494e..757930b788 100644 --- a/doomsday/plugins/common/include/hu_lib.h +++ b/doomsday/plugins/common/include/hu_lib.h @@ -622,9 +622,8 @@ typedef struct mndata_edit_s { ddstring_t oldtext; // If the current edit is canceled... uint maxLength; uint maxVisibleChars; - const char* emptyString; // Drawn when editfield is empty/null. - void* data1; - int data2; + char const *emptyString; // Drawn when editfield is empty/null. + void *data1; } mndata_edit_t; #ifdef __cplusplus diff --git a/doomsday/plugins/common/include/saveslots.h b/doomsday/plugins/common/include/saveslots.h index e24211fb06..54ee2f16d4 100644 --- a/doomsday/plugins/common/include/saveslots.h +++ b/doomsday/plugins/common/include/saveslots.h @@ -53,12 +53,23 @@ class SaveSlots DENG2_ERROR(MissingInfoError); public: - Slot(de::String const &fileName = ""); + Slot(de::String id, bool userWritable, de::String const &fileName = ""); + + /** + * Returns the unique identifier/name for the save slot. + */ + de::String const &id() const; + + /** + * Returns @c true iff the save slot is user-writable (i.e., not a special slot, + * such as the @em auto and @em base slots). + */ + bool isUserWritable() const; /** * Returns the save game file name bound to the logical save slot. */ - de::String fileName() const; + de::String const &fileName() const; /** * Change the save game file name bound to the logical save slot. @@ -103,10 +114,16 @@ class SaveSlots }; public: + SaveSlots(); + /** - * @param numSlots Number of logical slots. + * Add a new logical save slot. + * + * @param id Unique identifier for this slot. + * @param userWritable @c true= allow the user to write to this slot. + * @param fileName File name to bind to this slot. */ - SaveSlots(int numSlots); + void addSlot(de::String id, bool userWritable, de::String fileName); /** * Returns the total number of logical save slots. @@ -117,49 +134,21 @@ class SaveSlots inline int size() const { return slotCount(); } /** - * Returns @c true iff @a value is interpretable as logical slot number (in range). - * - * @see slotCount() - */ - bool isKnownSlot(int value) const; - - /** - * Composes the textual, symbolic identifier/name for save @a slotNumber. - * - * @see parseSlotIdentifier() - */ - de::String slotIdentifier(int slotNumber) const; - - /** - * 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 The parsed slot number if valid; otherwise @c -1 - * - * @see slotIdentifier() + * Returns @c true iff @a value is interpretable as logical slot identifier. */ - int parseSlotIdentifier(de::String str) const; + bool isKnownSlot(de::String value) const; /// @see slot() - inline Slot &operator [] (int slotNumber) { - return slot(slotNumber); + inline Slot &operator [] (de::String slotId) { + return slot(slotId); } /** - * Returns the logical save slot associated with @a slotNumber. + * Returns the logical save slot associated with @a slotId. * * @see isKnownSlot() */ - Slot &slot(int slotNumber) const; + Slot &slot(de::String slotId) const; /** * Clears save info for all logical save slots. @@ -176,32 +165,26 @@ class SaveSlots void updateAll(); /** - * Lookup a save slot by searching for a match on game-save description. The search is in - * ascending logical slot order 0...N (where N is the number of available save slots). + * Deletes all save game files associated with the specified save @a slotId. * - * @param description Description of the game-save to look for (not case sensitive). - * - * @return Logical slot number of the found game-save else @c -1 + * @see isKnownSlot() */ - int findSlotWithUserSaveDescription(de::String description) const; + void clearSlot(de::String slotId); /** - * Returns @c true iff save @a slotNumber is user-writable (i.e., not a special slot, such - * as the @em auto and @em base slots). + * Copies all the save game files from one slot to another. */ - bool slotIsUserWritable(int slotNumber) const; + void copySlot(de::String sourceSlotId, de::String destSlotId); /** - * Deletes all save game files associated with the specified save @a slotNumber. + * Lookup a save slot by searching for a match on game-save description. The search is in + * ascending logical slot order 0...N (where N is the number of available save slots). * - * @see isKnownSlot() - */ - void clearSlot(int slotNumber); - - /** - * Copies all the save game files from one slot to another. + * @param description Description of the game-save to look for (not case sensitive). + * + * @return Unique identifier of the found slot; otherwise a zero-length string. */ - void copySlot(int sourceSlotNumber, int destSlotNumber); + de::String findSlotWithUserSaveDescription(de::String description) const; /** * Register the console commands and variables of this module. diff --git a/doomsday/plugins/common/src/g_game.cpp b/doomsday/plugins/common/src/g_game.cpp index 459ec1b4b5..3bb2bb48fe 100644 --- a/doomsday/plugins/common/src/g_game.cpp +++ b/doomsday/plugins/common/src/g_game.cpp @@ -130,8 +130,8 @@ struct loadmap_params_t }; int G_DoLoadMap(loadmap_params_t *parm); -void G_DoLoadGame(int slotNumber); -void G_DoSaveGame(int slotNumber, char const *userDescription); +void G_DoLoadGame(de::String slotId); +void G_DoSaveGame(de::String slotId, de::String userDescription); void G_DoPlayDemo(); void G_DoMapCompleted(); void G_DoEndDebriefing(); @@ -198,17 +198,17 @@ wbstartstruct_t wmInfo; // Params for world map / intermission. #endif // Game Action Variables: -int gaSaveGameSlot; -dd_bool gaSaveGameGenerateDescription = true; -ddstring_t *gaSaveGameUserDescription; -int gaLoadGameSlot; +static de::String gaSaveGameSlot; +static bool gaSaveGameGenerateDescription = true; +static de::String gaSaveGameUserDescription; +static de::String gaLoadGameSlot; #if __JDOOM__ || __JDOOM64__ mobj_t *bodyQueue[BODYQUEUESIZE]; int bodyQueueSlot; #endif -static SaveSlots sslots(NUMSAVESLOTS); +static SaveSlots sslots; static GameStateReaderFactory gameStateReaderFactory; // vars used with game status cvars @@ -937,6 +937,43 @@ SaveSlots &G_SaveSlots() return sslots; } +de::String G_SaveSlotIdFromUserInput(de::String str) +{ + // Perhaps user save game description? + de::String slotId = G_SaveSlots().findSlotWithUserSaveDescription(str); + if(!slotId.isEmpty()) return slotId; + + // Perhaps a mnemonic? + if(!str.compareWithoutCase("last") || !str.compareWithoutCase("")) + { + return de::String::number(Con_GetInteger("game-save-last-slot")); + } + if(!str.compareWithoutCase("quick") || !str.compareWithoutCase("")) + { + return de::String::number(Con_GetInteger("game-save-quick-slot")); + } + if(!str.compareWithoutCase("auto") || !str.compareWithoutCase("")) + { + return "auto"; + } +#if __JHEXEN__ + // Never match the base slot. + if(!str.compareWithoutCase("base") || !str.compareWithoutCase("")) + { + return ""; + } +#endif + + // Perhaps a unique slot identifier? + if(G_SaveSlots().isKnownSlot(str)) + { + return str; + } + + // Unknown/not found. + return ""; +} + GameStateReaderFactory &G_GameStateReaderFactory() { return gameStateReaderFactory; @@ -966,6 +1003,16 @@ static void initGameSaveSystem() // Declare the native game state reader. gameStateReaderFactory.declareReader(&GameStateReader::recognize, &GameStateReader::make); + // Setup the logical save slot bindings. + for(int i = 0; i < NUMSAVESLOTS; ++i) + { + sslots.addSlot(de::String::number(i), true, de::String(SAVEGAMENAME "%1").arg(i)); + } + sslots.addSlot("auto", false, de::String(SAVEGAMENAME "%1").arg(AUTO_SLOT)); +#if __JHEXEN__ + sslots.addSlot("base", false, de::String(SAVEGAMENAME "%1").arg(BASE_SLOT)); +#endif + SV_InitIO(); // (Re)Initialize the saved game paths, possibly creating them if they do not exist. @@ -1352,7 +1399,7 @@ int G_DoLoadMap(loadmap_params_t *p) try { - SaveInfo &saveInfo = G_SaveSlots()[BASE_SLOT].saveInfo(); + SaveInfo &saveInfo = G_SaveSlots()["base"].saveInfo(); de::Path const path = SV_SavePath() / saveInfo.fileNameForMap(gameMap); if(!SV_OpenFile(path, false/*for read*/)) @@ -1626,17 +1673,10 @@ static void runGameAction() G_SetGameAction(GA_NONE); break; - case GA_SAVEGAME: { - char const *userDescription = 0; - - if(gaSaveGameUserDescription && !Str_IsEmpty(gaSaveGameUserDescription)) - { - userDescription = Str_Text(gaSaveGameUserDescription); - } - - G_DoSaveGame(gaSaveGameSlot, userDescription); + case GA_SAVEGAME: + G_DoSaveGame(gaSaveGameSlot, gaSaveGameUserDescription); G_SetGameAction(GA_NONE); - break; } + break; case GA_QUIT: G_DoQuitGame(); @@ -2116,20 +2156,22 @@ void G_QueueBody(mobj_t *mo) } #endif -static int rebornLoadConfirmResponse(msgresponse_t response, int userValue, void * /*context*/) +static int rebornLoadConfirmResponse(msgresponse_t response, int /*userValue*/, void *context) { + de::String *slotId = static_cast(context); + DENG2_ASSERT(slotId != 0); if(response == MSG_YES) { - gaLoadGameSlot = userValue; + gaLoadGameSlot = *slotId; G_SetGameAction(GA_LOADGAME); } else { #if __JHEXEN__ // Load the last autosave? (Not optional in Hexen). - if(G_SaveSlots()[AUTO_SLOT].isUsed()) + if(G_SaveSlots()["auto"].isUsed()) { - gaLoadGameSlot = AUTO_SLOT; + gaLoadGameSlot = "auto"; G_SetGameAction(GA_LOADGAME); } else @@ -2139,6 +2181,7 @@ static int rebornLoadConfirmResponse(msgresponse_t response, int userValue, void G_SetGameAction(GA_RESTARTMAP); } } + delete slotId; return true; } @@ -2158,42 +2201,45 @@ void G_DoReborn(int plrNum) if(G_IsLoadGamePossible()) { -#if !__JHEXEN__ - int autoSlot = -1; -#endif - int lastSlot = -1; - // First ensure we have up-to-date info. G_SaveSlots().updateAll(); // Use the latest save? + de::String lastSlot; if(cfg.loadLastSaveOnReborn) { - lastSlot = Con_GetInteger("game-save-last-slot"); - if(!G_SaveSlots()[lastSlot].isUsed()) lastSlot = -1; + lastSlot = de::String::number(Con_GetInteger("game-save-last-slot")); + if(!G_SaveSlots()[lastSlot].isUsed()) + { + lastSlot.clear(); + } } // Use the latest autosave? (Not optional in Hexen). #if !__JHEXEN__ + de::String autoSlot; if(cfg.loadAutoSaveOnReborn) { - autoSlot = AUTO_SLOT; - if(!G_SaveSlots()[autoSlot].isUsed()) autoSlot = -1; + autoSlot = "auto"; + if(!G_SaveSlots()[autoSlot].isUsed()) + { + autoSlot.clear(); + } } #endif // Have we chosen a save state to load? - if(lastSlot >= 0 + if(!lastSlot.isEmpty() #if !__JHEXEN__ - || autoSlot >= 0 + || !autoSlot.isEmpty() #endif ) { // Everything appears to be in order - schedule the game-save load! #if !__JHEXEN__ - int const chosenSlot = (lastSlot >= 0? lastSlot : autoSlot); + de::String chosenSlot = (!lastSlot.isEmpty()? lastSlot : autoSlot); #else - int const chosenSlot = lastSlot; + de::String chosenSlot = lastSlot; #endif if(!cfg.confirmRebornLoad) { @@ -2206,16 +2252,16 @@ void G_DoReborn(int plrNum) SaveInfo &saveInfo = G_SaveSlots()[chosenSlot].saveInfo(); AutoStr *msg = Str_Appendf(AutoStr_NewStd(), REBORNLOAD_CONFIRM, saveInfo.userDescription().toUtf8().constData()); S_LocalSound(SFX_REBORNLOAD_CONFIRM, NULL); - Hu_MsgStart(MSG_YESNO, Str_Text(msg), rebornLoadConfirmResponse, chosenSlot, 0); + Hu_MsgStart(MSG_YESNO, Str_Text(msg), rebornLoadConfirmResponse, 0, new de::String(chosenSlot)); } return; } // Autosave loading cannot be disabled in Hexen. #if __JHEXEN__ - if(G_SaveSlots()[AUTO_SLOT].isUsed()) + if(G_SaveSlots()["auto"].isUsed()) { - gaLoadGameSlot = AUTO_SLOT; + gaLoadGameSlot = "auto"; G_SetGameAction(GA_LOADGAME); return; } @@ -2229,13 +2275,13 @@ void G_DoReborn(int plrNum) static void G_InitNewGame() { #if __JHEXEN__ - G_SaveSlots().clearSlot(BASE_SLOT); + G_SaveSlots().clearSlot("base"); #endif /// @todo Do not clear this save slot. Instead we should set a game state /// flag to signal when a new game should be started instead of loading /// the autosave slot. - G_SaveSlots().clearSlot(AUTO_SLOT); + G_SaveSlots().clearSlot("auto"); #if __JHEXEN__ Game_InitACScriptsForNewGame(); @@ -2817,8 +2863,8 @@ static void restorePlayersInHub(playerbackup_t playerBackup[MAXPLAYERS], uint ma struct savegamestateworker_params_t { - char const *userDescription; - int slotNumber; + de::String userDescription; + de::String slotId; }; /** @@ -2831,12 +2877,12 @@ static int saveGameStateWorker(void *context) savegamestateworker_params_t &p = *static_cast(context); #if __JHEXEN__ - int const logicalSlot = BASE_SLOT; + de::String const logicalSlot = "base"; #else - int const logicalSlot = p.slotNumber; + de::String const logicalSlot = p.slotId; #endif - if(!G_SaveSlots().isKnownSlot(p.slotNumber)) + if(!G_SaveSlots().isKnownSlot(p.slotId)) { DENG2_ASSERT(!"Invalid slot specified"); @@ -2853,14 +2899,14 @@ static int saveGameStateWorker(void *context) return false; } - char const *userDescription = p.userDescription; - if(!userDescription || !userDescription[0]) + de::String userDescription = p.userDescription; + if(userDescription.isEmpty()) { - SaveInfo &saveInfo = G_SaveSlots()[p.slotNumber].saveInfo(); + SaveInfo &saveInfo = G_SaveSlots()[p.slotId].saveInfo(); if(!saveInfo.userDescription().isEmpty()) { // Slot already in use; reuse the existing description. - userDescription = Str_Text(AutoStr_FromTextStd(saveInfo.userDescription().toUtf8().constData())); + userDescription = saveInfo.userDescription(); } else if(gaSaveGameGenerateDescription) { @@ -2883,19 +2929,19 @@ static int saveGameStateWorker(void *context) #if __JHEXEN__ // Copy base slot to destination slot. - G_SaveSlots().copySlot(logicalSlot, p.slotNumber); + G_SaveSlots().copySlot(logicalSlot, p.slotId); #endif // Make note of the last used save slot. - Con_SetInteger2("game-save-last-slot", p.slotNumber, SVF_WRITE_OVERRIDE); + Con_SetInteger2("game-save-last-slot", p.slotId.toInt(), SVF_WRITE_OVERRIDE); BusyMode_WorkerEnd(); return true; } catch(de::Error const &er) { - App_Log(DE2_RES_WARNING, "Error writing to save slot #%i:\n%s", - p.slotNumber, er.asText().toLatin1().constData()); + App_Log(DE2_RES_WARNING, "Error writing to save slot '%s':\n%s", + p.slotId.toLatin1().constData(), er.asText().toLatin1().constData()); } // Discard the useless save info. @@ -2925,7 +2971,7 @@ void G_DoLeaveMap() * First, determine whether we've been to this map previously and if so, * whether we need to load the archived map state. */ - revisit = G_SaveSlots()[BASE_SLOT].saveInfo().haveMapSession(nextMap); + revisit = G_SaveSlots()["base"].saveInfo().haveMapSession(nextMap); if(gameRules.deathmatch) { revisit = false; @@ -2938,7 +2984,7 @@ void G_DoLeaveMap() if(!gameRules.deathmatch) { // Save current map. - SaveInfo &saveInfo = G_SaveSlots()[BASE_SLOT].saveInfo(); + SaveInfo &saveInfo = G_SaveSlots()["base"].saveInfo(); de::Path const path = SV_SavePath() / saveInfo.fileNameForMap(gameMap); if(!SV_OpenFile(path, true/*for write*/)) @@ -2964,7 +3010,7 @@ void G_DoLeaveMap() { if(!gameRules.deathmatch) { - G_SaveSlots().clearSlot(BASE_SLOT); + G_SaveSlots().clearSlot("base"); } } Uri_Delete(nextMapUri); nextMapUri = 0; @@ -3063,8 +3109,8 @@ void G_DoLeaveMap() // In a non-network, non-deathmatch game, save immediately into the autosave slot. if(!IS_NETGAME && !gameRules.deathmatch) { - savegamestateworker_params_t p; de::zap(p); - p.slotNumber = AUTO_SLOT; + savegamestateworker_params_t p; + p.slotId = "auto"; /// @todo Use progress bar mode and update progress during the setup. BusyMode_RunNewTaskWithName(BUSYF_ACTIVITY | /*BUSYF_PROGRESS_BAR |*/ (verbose? BUSYF_CONSOLE_OUTPUT : 0), @@ -3116,7 +3162,7 @@ dd_bool G_IsLoadGamePossible() return !(IS_CLIENT && !Get(DD_PLAYBACK)); } -dd_bool G_LoadGame(int slotNumber) +bool G_LoadGame(de::String slotId) { if(!G_IsLoadGamePossible()) return false; @@ -3127,15 +3173,21 @@ dd_bool G_LoadGame(int slotNumber) // First ensure we have up-to-date info. G_SaveSlots().updateAll(); - if(G_SaveSlots()[slotNumber].isUsed()) + try { - // Everything appears to be in order - schedule the game-save load! - gaLoadGameSlot = slotNumber; - G_SetGameAction(GA_LOADGAME); - return true; + if(G_SaveSlots()[slotId].isUsed()) + { + // Everything appears to be in order - schedule the game-save load! + gaLoadGameSlot = slotId; + G_SetGameAction(GA_LOADGAME); + return true; + } + + App_Log(DE2_RES_ERROR, "Cannot load from save slot '%s': not in use", slotId.toLatin1().constData()); } + catch(SaveSlots::InvalidSlotError const &) + {} - App_Log(DE2_RES_ERROR, "Cannot load from save slot #%i: not in use", slotNumber); return false; } @@ -3153,16 +3205,16 @@ static std::auto_ptr gameStateReaderFor(SaveInfo &info) /** * Called by G_Ticker based on gameaction. */ -void G_DoLoadGame(int slotNumber) +void G_DoLoadGame(de::String slotId) { #if __JHEXEN__ - bool mustCopyBaseToAutoSlot = (slotNumber != AUTO_SLOT) && !IS_NETGAME; + bool mustCopyBaseToAutoSlot = (slotId.compareWithoutCase("auto") && !IS_NETGAME); #endif #if __JHEXEN__ - int const logicalSlot = BASE_SLOT; + de::String const logicalSlot = "base"; #else - int const logicalSlot = slotNumber; + de::String const logicalSlot = slotId; #endif if(SV_SavePath().isEmpty()) @@ -3176,9 +3228,9 @@ void G_DoLoadGame(int slotNumber) { #if __JHEXEN__ // Copy all needed save files to the base slot. - if(slotNumber != BASE_SLOT) + if(slotId.compareWithoutCase("base")) { - G_SaveSlots().copySlot(slotNumber, BASE_SLOT); + G_SaveSlots().copySlot(slotId, "base"); } #endif @@ -3191,20 +3243,20 @@ void G_DoLoadGame(int slotNumber) gameStateReaderFor(saveInfo)->read(saveInfo); // Make note of the last used save slot. - Con_SetInteger2("game-save-last-slot", slotNumber, SVF_WRITE_OVERRIDE); + Con_SetInteger2("game-save-last-slot", slotId.toInt(), SVF_WRITE_OVERRIDE); #if __JHEXEN__ if(mustCopyBaseToAutoSlot) { // Copy the base slot to the autosave slot. - G_SaveSlots().copySlot(BASE_SLOT, AUTO_SLOT); + G_SaveSlots().copySlot("base", "auto"); } #endif } catch(de::Error const &er) { App_Log(DE2_RES_WARNING, "Error loading save slot #%i:\n%s", - slotNumber, er.asText().toLatin1().constData()); + slotId.toLatin1().constData(), er.asText().toLatin1().constData()); } } @@ -3219,40 +3271,30 @@ dd_bool G_IsSaveGamePossible() return true; } -dd_bool G_SaveGame2(int slot, char const *name) +bool G_SaveGame(de::String slotId, de::String *userDescription) { - if(0 > slot || slot >= G_SaveSlots().slotCount()) return false; - if(!G_IsSaveGamePossible()) return false; + if(!G_SaveSlots().isKnownSlot(slotId)) return false; - gaSaveGameSlot = slot; - if(!gaSaveGameUserDescription) - { - gaSaveGameUserDescription = Str_New(); - } + gaSaveGameSlot = slotId; - if(name && name[0]) + if(userDescription && !userDescription->isEmpty()) { - // A new name. + // A new description. gaSaveGameGenerateDescription = false; - Str_Set(gaSaveGameUserDescription, name); + gaSaveGameUserDescription = *userDescription; } else { // Reusing the current name or generating a new one. - gaSaveGameGenerateDescription = (name && !name[0]); - Str_Clear(gaSaveGameUserDescription); + gaSaveGameGenerateDescription = (userDescription && userDescription->isEmpty()); + gaSaveGameUserDescription.clear(); } G_SetGameAction(GA_SAVEGAME); return true; } -dd_bool G_SaveGame(int slot) -{ - return G_SaveGame2(slot, NULL); -} - AutoStr *G_GenerateUserSaveDescription() { int time = mapTime / TICRATE; @@ -3290,11 +3332,11 @@ AutoStr *G_GenerateUserSaveDescription() /** * Called by G_Ticker based on gameaction. */ -void G_DoSaveGame(int slotNumber, char const *userDescription) +void G_DoSaveGame(de::String slotId, de::String userDescription) { - savegamestateworker_params_t p; de::zap(p); + savegamestateworker_params_t p; p.userDescription = userDescription; - p.slotNumber = slotNumber; + p.slotId = slotId; /// @todo Use progress bar mode and update progress during the setup. bool didSave = (BusyMode_RunNewTaskWithName(BUSYF_ACTIVITY | /*BUSYF_PROGRESS_BAR |*/ (verbose? BUSYF_CONSOLE_OUTPUT : 0), @@ -4102,12 +4144,14 @@ D_CMD(OpenSaveMenu) return true; } -static int loadGameConfirmResponse(msgresponse_t response, int userValue, void * /*context*/) +static int loadGameConfirmResponse(msgresponse_t response, int /*userValue*/, void *context) { + de::String *slotId = static_cast(context); + DENG2_ASSERT(slotId != 0); if(response == MSG_YES) { - int const slot = userValue; - G_LoadGame(slot); + G_LoadGame(*slotId); + delete slotId; } return true; } @@ -4116,7 +4160,7 @@ D_CMD(LoadGame) { DENG_UNUSED(src); - bool const confirm = (argc == 3 && !stricmp(argv[2], "confirm")); + bool const confirmed = (argc == 3 && !stricmp(argv[2], "confirm")); if(G_QuitInProgress()) return false; if(!G_IsLoadGamePossible()) return false; @@ -4131,35 +4175,44 @@ D_CMD(LoadGame) // Ensure we have up-to-date info. G_SaveSlots().updateAll(); - int const slotNumber = G_SaveSlots().parseSlotIdentifier(argv[1]); - if(G_SaveSlots()[slotNumber].isUsed()) + de::String const slotId = G_SaveSlotIdFromUserInput(argv[1]); + try { - // A known used slot identifier. - if(confirm || !cfg.confirmQuickGameSave) + SaveSlot &sslot = G_SaveSlots()[slotId]; + if(sslot.isUsed()) { - // Try to schedule a GA_LOADGAME action. - S_LocalSound(SFX_MENU_ACCEPT, NULL); - return G_LoadGame(slotNumber); - } + // A known used slot identifier. + if(confirmed || !cfg.confirmQuickGameSave) + { + // Try to schedule a GA_LOADGAME action. + S_LocalSound(SFX_MENU_ACCEPT, NULL); + return G_LoadGame(slotId); + } - SaveInfo &saveInfo = G_SaveSlots()[slotNumber].saveInfo(); - // Compose the confirmation message. - AutoStr *msg = Str_Appendf(AutoStr_NewStd(), QLPROMPT, saveInfo.userDescription().toUtf8().constData()); + // Compose the confirmation message. + AutoStr *msg = Str_Appendf(AutoStr_NewStd(), QLPROMPT, sslot.saveInfo().userDescription().toUtf8().constData()); - S_LocalSound(SFX_QUICKLOAD_PROMPT, NULL); - Hu_MsgStart(MSG_YESNO, Str_Text(msg), loadGameConfirmResponse, slotNumber, 0); - return true; + S_LocalSound(SFX_QUICKLOAD_PROMPT, NULL); + Hu_MsgStart(MSG_YESNO, Str_Text(msg), loadGameConfirmResponse, 0, new de::String(slotId)); + return true; + } } - else if(!stricmp(argv[1], "quick") || !stricmp(argv[1], "")) + catch(SaveSlots::InvalidSlotError const &) + {} + + if(!stricmp(argv[1], "quick") || !stricmp(argv[1], "")) { S_LocalSound(SFX_QUICKLOAD_PROMPT, NULL); Hu_MsgStart(MSG_ANYKEY, QSAVESPOT, NULL, 0, NULL); return true; } - // Clearly the caller needs some assistance... - App_Log(DE2_SCR_WARNING, "Failed to determine save slot from \"%s\"", argv[1]); + if(!G_SaveSlots().isKnownSlot(slotId)) + { + App_Log(DE2_SCR_WARNING, "Failed to determine save slot from \"%s\"", argv[1]); + } + // Clearly the caller needs some assistance... // We'll open the load menu if caller is the console. // Reasoning: User attempted to load a named game-save however the name // specified didn't match anything known. Opening the load menu allows @@ -4181,17 +4234,20 @@ D_CMD(QuickLoadGame) return DD_Execute(true, "loadgame quick"); } -static int saveGameConfirmResponse(msgresponse_t response, int userValue, void *context) +struct savegameconfirmresponse_params_t { + de::String slotId; + de::String userDescription; +}; + +static int saveGameConfirmResponse(msgresponse_t response, int /*userValue*/, void *context) +{ + savegameconfirmresponse_params_t *p = static_cast(context); + DENG2_ASSERT(p != 0); if(response == MSG_YES) { - int const slot = userValue; - ddstring_t *userDescription = (ddstring_t *)context; - - G_SaveGame2(slot, Str_Text(userDescription)); - - // We're done with the description. - Str_Delete(userDescription); + G_SaveGame(p->slotId, &p->userDescription); + delete p; } return true; } @@ -4200,7 +4256,7 @@ D_CMD(SaveGame) { DENG_UNUSED(src); - bool const confirm = (argc >= 3 && !stricmp(argv[argc-1], "confirm")); + bool const confirmed = (argc >= 3 && !stricmp(argv[argc-1], "confirm")); if(G_QuitInProgress()) return false; @@ -4228,33 +4284,47 @@ D_CMD(SaveGame) // Ensure we have up-to-date info. G_SaveSlots().updateAll(); - int const slotNumber = G_SaveSlots().parseSlotIdentifier(argv[1]); - if(G_SaveSlots().slotIsUserWritable(slotNumber)) + de::String const slotId = G_SaveSlotIdFromUserInput(argv[1]); + try { - // A known slot identifier. - bool const slotIsUsed = G_SaveSlots()[slotNumber].isUsed(); - - ddstring_t localName; - Str_InitStatic(&localName, (argc >= 3 && stricmp(argv[2], "confirm"))? argv[2] : ""); - if(!slotIsUsed || confirm || !cfg.confirmQuickGameSave) + SaveSlot &sslot = G_SaveSlots()[slotId]; + if(sslot.isUserWritable()) { - // Try to schedule a GA_LOADGAME action. - S_LocalSound(SFX_MENU_ACCEPT, NULL); - return G_SaveGame2(slotNumber, Str_Text(&localName)); - } + // A known slot identifier. + bool const slotIsUsed = sslot.isUsed(); + + de::String userDescription; + if(argc >= 3 && stricmp(argv[2], "confirm")) + { + userDescription = argv[2]; + } - // Compose the confirmation message. - SaveInfo &saveInfo = G_SaveSlots()[slotNumber].saveInfo(); - AutoStr *msg = Str_Appendf(AutoStr_NewStd(), QSPROMPT, saveInfo.userDescription().toUtf8().constData()); + if(!slotIsUsed || confirmed || !cfg.confirmQuickGameSave) + { + // Try to schedule a GA_SAVEGAME action. + S_LocalSound(SFX_MENU_ACCEPT, NULL); + return G_SaveGame(slotId, &userDescription); + } - // Make a copy of the name. - ddstring_t *name = Str_Copy(Str_New(), &localName); + // Compose the confirmation message. + AutoStr *msg = Str_Appendf(AutoStr_NewStd(), QSPROMPT, sslot.saveInfo().userDescription().toUtf8().constData()); - S_LocalSound(SFX_QUICKSAVE_PROMPT, NULL); - Hu_MsgStart(MSG_YESNO, Str_Text(msg), saveGameConfirmResponse, slotNumber, (void *)name); - return true; + savegameconfirmresponse_params_t *parm = new savegameconfirmresponse_params_t; + parm->slotId = slotId; + parm->userDescription = userDescription; + + S_LocalSound(SFX_QUICKSAVE_PROMPT, NULL); + Hu_MsgStart(MSG_YESNO, Str_Text(msg), saveGameConfirmResponse, 0, parm); + return true; + } + + App_Log(DE2_LOG_ERROR, "Save slot '%s' is non-user-writable", + slotId.toLatin1().constData()); } - else if(!stricmp(argv[1], "quick") || !stricmp(argv[1], "")) + catch(SaveSlots::InvalidSlotError const &) + {} + + if(!stricmp(argv[1], "quick") || !stricmp(argv[1], "")) { // No quick-save slot has been nominated - allow doing so now. Hu_MenuCommand(MCMD_OPEN); @@ -4264,15 +4334,10 @@ D_CMD(SaveGame) return true; } - // Clearly the caller needs some assistance... - if(!G_SaveSlots().isKnownSlot(slotNumber)) + if(!G_SaveSlots().isKnownSlot(slotId)) { App_Log(DE2_SCR_WARNING, "Failed to determine save slot from \"%s\"", argv[1]); } - else - { - App_Log(DE2_LOG_ERROR, "Save slot #%i is non-user-writable", slotNumber); - } // No action means the command failed. return false; @@ -4281,40 +4346,46 @@ D_CMD(SaveGame) D_CMD(QuickSaveGame) { DENG2_UNUSED3(src, argc, argv); - - /// @todo Implement console command scripts? return DD_Execute(true, "savegame quick"); } -dd_bool G_DeleteSaveGame(int slotNumber) +bool G_DeleteSaveGame(de::String slotId) { - if(!G_SaveSlots().slotIsUserWritable(slotNumber)) return false; - if(!G_SaveSlots()[slotNumber].isUsed()) return false; - - // A known slot identifier. - G_SaveSlots().clearSlot(slotNumber); - - if(Hu_MenuIsActive()) + try { - mn_page_t *activePage = Hu_MenuActivePage(); - if(activePage == Hu_MenuFindPageByName("LoadGame") || - activePage == Hu_MenuFindPageByName("SaveGame")) + SaveSlot &sslot = G_SaveSlots()[slotId]; + if(sslot.isUserWritable() && sslot.isUsed()) { - // Re-open the current menu page. - Hu_MenuUpdateGameSaveWidgets(); - Hu_MenuSetActivePage2(activePage, true); + G_SaveSlots().clearSlot(slotId); + + if(Hu_MenuIsActive()) + { + mn_page_t *activePage = Hu_MenuActivePage(); + if(activePage == Hu_MenuFindPageByName("LoadGame") || + activePage == Hu_MenuFindPageByName("SaveGame")) + { + // Re-open the current menu page. + Hu_MenuUpdateGameSaveWidgets(); + Hu_MenuSetActivePage2(activePage, true); + } + } + return true; } } + catch(SaveSlots::InvalidSlotError const &) + {} - return true; + return false; } -static int deleteSaveGameConfirmResponse(msgresponse_t response, int userValue, void * /*context*/) +static int deleteSaveGameConfirmResponse(msgresponse_t response, int /*userValue*/, void *context) { + de::String *slotId = static_cast(context); + DENG2_ASSERT(slotId != 0); if(response == MSG_YES) { - int const slot = userValue; - G_DeleteSaveGame(slot); + G_DeleteSaveGame(*slotId); + delete slotId; } return true; } @@ -4323,41 +4394,39 @@ D_CMD(DeleteGameSave) { DENG2_UNUSED(src); - bool const confirm = (argc >= 3 && !stricmp(argv[argc-1], "confirm")); + bool const confirmed = (argc >= 3 && !stricmp(argv[argc-1], "confirm")); if(G_QuitInProgress()) return false; // Ensure we have up-to-date info. G_SaveSlots().updateAll(); - int const slotNumber = G_SaveSlots().parseSlotIdentifier(argv[1]); - if(G_SaveSlots().slotIsUserWritable(slotNumber) && - G_SaveSlots()[slotNumber].isUsed()) + de::String slotId = G_SaveSlotIdFromUserInput(argv[1]); + try { - // A known slot identifier. - if(confirm) - { - return G_DeleteSaveGame(slotNumber); - } - else + SaveSlot &sslot = G_SaveSlots()[slotId]; + if(sslot.isUserWritable() && sslot.isUsed()) { - // Compose the confirmation message. - SaveInfo &saveInfo = G_SaveSlots()[slotNumber].saveInfo(); - AutoStr *msg = Str_Appendf(AutoStr_NewStd(), DELETESAVEGAME_CONFIRM, saveInfo.userDescription().toUtf8().constData()); - S_LocalSound(SFX_DELETESAVEGAME_CONFIRM, NULL); - Hu_MsgStart(MSG_YESNO, Str_Text(msg), deleteSaveGameConfirmResponse, slotNumber, 0); + // A known slot identifier. + if(confirmed) + { + return G_DeleteSaveGame(slotId); + } + else + { + // Compose the confirmation message. + AutoStr *msg = Str_Appendf(AutoStr_NewStd(), DELETESAVEGAME_CONFIRM, sslot.saveInfo().userDescription().toUtf8().constData()); + S_LocalSound(SFX_DELETESAVEGAME_CONFIRM, NULL); + Hu_MsgStart(MSG_YESNO, Str_Text(msg), deleteSaveGameConfirmResponse, 0, new de::String(slotId)); + } + return true; } - return true; - } - // Clearly the caller needs some assistance... - if(!G_SaveSlots().isKnownSlot(slotNumber)) - { - App_Log(DE2_SCR_WARNING, "Failed to determine save slot from \"%s\"", argv[1]); + App_Log(DE2_LOG_ERROR, "Save slot '%s' is non-user-writable", slotId.toLatin1().constData()); } - else + catch(SaveSlots::InvalidSlotError const &) { - App_Log(DE2_LOG_ERROR, "Save slot #%i is non-user-writable", slotNumber); + App_Log(DE2_SCR_WARNING, "Failed to determine save slot from '%s'", argv[1]); } // No action means the command failed. @@ -4371,17 +4440,17 @@ D_CMD(InspectGameSave) // Ensure we have up-to-date info. G_SaveSlots().updateAll(); + de::String slotId = G_SaveSlotIdFromUserInput(argv[1]); try { - int const slotNumber = G_SaveSlots().parseSlotIdentifier(argv[1]); - if(G_SaveSlots()[slotNumber].isUsed()) + SaveSlot &sslot = G_SaveSlots()[slotId]; + if(sslot.isUsed()) { - SaveInfo &saveInfo = G_SaveSlots()[slotNumber].saveInfo(); - App_Log(DE2_LOG_MESSAGE, "%s", saveInfo.description().toLatin1().constData()); + App_Log(DE2_LOG_MESSAGE, "%s", sslot.saveInfo().description().toLatin1().constData()); return true; } - App_Log(DE2_LOG_ERROR, "Save slot #%i is not in use", slotNumber); + App_Log(DE2_LOG_ERROR, "Save slot '%s' is not in use", slotId.toLatin1().constData()); } catch(SaveSlots::InvalidSlotError const &) { diff --git a/doomsday/plugins/common/src/hu_lib.c b/doomsday/plugins/common/src/hu_lib.c index 3173e4f4f1..b478ed098e 100644 --- a/doomsday/plugins/common/src/hu_lib.c +++ b/doomsday/plugins/common/src/hu_lib.c @@ -1855,24 +1855,23 @@ int MNText_SetFlags(mn_object_t* ob, flagop_t op, int flags) return ob->_flags; } -mn_object_t* MNEdit_New(void) +mn_object_t *MNEdit_New(void) { - mn_object_t* ob = Z_Calloc(sizeof(*ob), PU_GAMESTATIC, 0); - if(!ob) Con_Error("MNEdit::New: Failed on allocation of %lu bytes for new MNEdit.", (unsigned long) sizeof(*ob)); - ob->_typedata = Z_Calloc(sizeof(mndata_edit_t), PU_GAMESTATIC, 0); - if(!ob->_typedata) Con_Error("MNEdit::New: Failed on allocation of %lu bytes for mndata_edit_t.", (unsigned long) sizeof(mndata_edit_t)); + mn_object_t *ob = Z_Calloc(sizeof(*ob), PU_GAMESTATIC, 0); - ob->_type = MN_EDIT; - ob->_pageFontIdx = MENU_FONT1; - ob->_pageColorIdx = MENU_COLOR1; - ob->drawer = MNEdit_Drawer; - ob->ticker = MNEdit_Ticker; + ob->_typedata = Z_Calloc(sizeof(mndata_edit_t), PU_GAMESTATIC, 0); + ob->_type = MN_EDIT; + ob->_pageFontIdx = MENU_FONT1; + ob->_pageColorIdx = MENU_COLOR1; + ob->drawer = MNEdit_Drawer; + ob->ticker = MNEdit_Ticker; ob->updateGeometry = MNEdit_UpdateGeometry; - ob->cmdResponder = MNEdit_CommandResponder; - ob->responder = MNEdit_Responder; - { mndata_edit_t *edit = (mndata_edit_t *) ob->_typedata; - Str_Init(&edit->text); - Str_Init(&edit->oldtext); + ob->cmdResponder = MNEdit_CommandResponder; + ob->responder = MNEdit_Responder; + { + mndata_edit_t *edit = (mndata_edit_t *) ob->_typedata; + Str_Init(&edit->text); + Str_Init(&edit->oldtext); } return ob; @@ -1880,8 +1879,11 @@ mn_object_t* MNEdit_New(void) void MNEdit_Delete(mn_object_t *ob) { - mndata_edit_t *edit = (mndata_edit_t *) ob->_typedata; - DENG_ASSERT(ob != 0 && ob->_type == MN_EDIT); + mndata_edit_t *edit; + if(!ob) return; + + edit = (mndata_edit_t *) ob->_typedata; + DENG_ASSERT(ob->_type == MN_EDIT); Str_Free(&edit->text); Str_Free(&edit->oldtext); Z_Free(ob->_typedata); diff --git a/doomsday/plugins/common/src/hu_menu.cpp b/doomsday/plugins/common/src/hu_menu.cpp index b0ead33993..9f48b8afac 100644 --- a/doomsday/plugins/common/src/hu_menu.cpp +++ b/doomsday/plugins/common/src/hu_menu.cpp @@ -1925,19 +1925,19 @@ void Hu_MenuInitFilesPage() } #endif -static void deleteGameSave(int slot) +static void deleteGameSave(de::String slotId) { - DD_Executef(true, "deletegamesave %i", slot); + DD_Executef(true, "deletegamesave %s", slotId.toLatin1().constData()); } int Hu_MenuLoadSlotCommandResponder(mn_object_t *ob, menucommand_e cmd) { - DENG_ASSERT(ob && ob->_type == MN_EDIT); + DENG_ASSERT(ob != 0 && ob->_type == MN_EDIT); if(MCMD_DELETE == cmd && (ob->_flags & MNF_FOCUS) && !(ob->_flags & MNF_ACTIVE) && !(ob->_flags & MNF_DISABLED)) { - mndata_edit_t* edit = (mndata_edit_t*)ob->_typedata; - deleteGameSave(edit->data2); + mndata_edit_t *edit = (mndata_edit_t*)ob->_typedata; + deleteGameSave((char *)edit->data1); return true; } return MNObject_DefaultCommandResponder(ob, cmd); @@ -1945,12 +1945,12 @@ int Hu_MenuLoadSlotCommandResponder(mn_object_t *ob, menucommand_e cmd) int Hu_MenuSaveSlotCommandResponder(mn_object_t *ob, menucommand_e cmd) { - assert(ob); + DENG_ASSERT(ob != 0); if(MCMD_DELETE == cmd && (ob->_flags & MNF_FOCUS) && !(ob->_flags & MNF_ACTIVE) && !(ob->_flags & MNF_DISABLED)) { - mndata_edit_t* edit = (mndata_edit_t*)ob->_typedata; - deleteGameSave(edit->data2); + mndata_edit_t *edit = (mndata_edit_t *)ob->_typedata; + deleteGameSave((char *)edit->data1); return true; } return MNEdit_CommandResponder(ob, cmd); @@ -1975,8 +1975,8 @@ void Hu_MenuInitLoadGameAndSaveGamePages() for(int i = 0; i < NUMSAVESLOTS; ++i) { mndata_edit_t *slot = saveSlots + i; - slot->emptyString = (const char*) TXT_EMPTYSTRING; - slot->data2 = i; + slot->emptyString = (char const *) TXT_EMPTYSTRING; + slot->data1 = Str_Text(Str_Appendf(Str_New(), "%i", i)); slot->maxLength = 24; } @@ -5523,15 +5523,13 @@ int Hu_MenuFallbackResponder(event_t* ev) int Hu_MenuSelectLoadSlot(mn_object_t *obj, mn_actionid_t action, void * /*context*/) { mndata_edit_t *edit = (mndata_edit_t *)obj->_typedata; - int const saveSlot = edit->data2; - mn_page_t *saveGamePage; if(MNA_ACTIVEOUT != action) return 1; - saveGamePage = Hu_MenuFindPageByName("SaveGame"); + mn_page_t *saveGamePage = Hu_MenuFindPageByName("SaveGame"); MNPage_SetFocus(saveGamePage, MNPage_FindObject(saveGamePage, 0, obj->data2)); - G_LoadGame(saveSlot); + G_LoadGame((char *)edit->data1); Hu_MenuCommand(chooseCloseMethod()); return 0; } @@ -5766,7 +5764,7 @@ void Hu_MenuUpdateGameSaveWidgets() mndata_edit_t *edit = (mndata_edit_t *) obj->_typedata; MNObject_SetFlags(obj, FO_SET, MNF_DISABLED); - SaveSlot &sslot = G_SaveSlots()[edit->data2]; + SaveSlot &sslot = G_SaveSlots()[(char *)edit->data1]; if(sslot.isUsed()) { MNEdit_SetText(obj, MNEDIT_STF_NO_ACTION, sslot.saveInfo().userDescription().toUtf8().constData()); @@ -5782,25 +5780,26 @@ void Hu_MenuUpdateGameSaveWidgets() /** * Called after the save name has been modified and to action the game-save. */ -int Hu_MenuSelectSaveSlot(mn_object_t *ob, mn_actionid_t action, void *parameters) +int Hu_MenuSelectSaveSlot(mn_object_t *ob, mn_actionid_t action, void * /*context*/) { - mndata_edit_t* edit = (mndata_edit_t*)ob->_typedata; - const int saveSlot = edit->data2; - mn_page_t *page; - - DENG_UNUSED(parameters); + mndata_edit_t *edit = (mndata_edit_t *)ob->_typedata; + char const *saveSlotId = (char *)edit->data1; if(MNA_ACTIVEOUT != action) return 1; if(menuNominatingQuickSaveSlot) { - Con_SetInteger("game-save-quick-slot", saveSlot); + Con_SetInteger("game-save-quick-slot", de::String(saveSlotId).toInt()); menuNominatingQuickSaveSlot = false; } - if(!G_SaveGame2(saveSlot, Str_Text(MNEdit_Text(ob)))) return 0; + de::String userDescription = Str_Text(MNEdit_Text(ob)); + if(!G_SaveGame(saveSlotId, &userDescription)) + { + return 0; + } - page = Hu_MenuFindPageByName("SaveGame"); + mn_page_t *page = Hu_MenuFindPageByName("SaveGame"); MNPage_SetFocus(page, MN_MustFindObjectOnPage(page, 0, ob->data2)); page = Hu_MenuFindPageByName("LoadGame"); @@ -5810,14 +5809,13 @@ int Hu_MenuSelectSaveSlot(mn_object_t *ob, mn_actionid_t action, void *parameter return 0; } -int Hu_MenuCvarButton(mn_object_t *obj, mn_actionid_t action, void *parameters) +int Hu_MenuCvarButton(mn_object_t *obj, mn_actionid_t action, void * /*context*/) { mndata_button_t *btn = (mndata_button_t *)obj->_typedata; cvarbutton_t const *cb = (cvarbutton_t *)obj->data1; cvartype_t varType = Con_GetVariableType(cb->cvarname); int value; - DENG_UNUSED(parameters); if(MNA_MODIFIED != action) return 1; //strcpy(btn->text, cb->active? cb->yes : cb->no); diff --git a/doomsday/plugins/common/src/hu_msg.c b/doomsday/plugins/common/src/hu_msg.c index 030555fc65..aec12558db 100644 --- a/doomsday/plugins/common/src/hu_msg.c +++ b/doomsday/plugins/common/src/hu_msg.c @@ -29,6 +29,7 @@ #include "hu_msg.h" #include "hu_menu.h" #include "hu_stuff.h" +#include D_CMD(MsgResponse); @@ -240,27 +241,29 @@ dd_bool Hu_IsMessageActiveWithCallback(msgfunc_t callback) return messageToPrint && msgCallback == callback; } -void Hu_MsgStart(msgtype_t type, const char* msg, msgfunc_t callback, - int userValue, void* userPointer) +void Hu_MsgStart(msgtype_t type, char const *msg, msgfunc_t callback, + int userValue, void *userPointer) { - DENG_ASSERT(msg); + DENG_ASSERT(msg != 0); DENG_ASSERT(!awaitingResponse); awaitingResponse = true; - messageResponse = 0; - messageToPrint = 1; + messageResponse = 0; + messageToPrint = 1; - msgType = type; - msgCallback = callback; - msgUserValue = userValue; + msgType = type; + msgCallback = callback; + msgUserValue = userValue; msgUserPointer = userPointer; // Take a copy of the message string. - msgText = calloc(1, strlen(msg)+1); + msgText = M_Calloc(strlen(msg) + 1); strncpy(msgText, msg, strlen(msg)); if(msgType == MSG_YESNO) + { composeYesNoMessage(); + } if(!(Get(DD_DEDICATED) || Get(DD_NOVIDEO))) { diff --git a/doomsday/plugins/common/src/saveslots.cpp b/doomsday/plugins/common/src/saveslots.cpp index 828b2f0280..304b514653 100644 --- a/doomsday/plugins/common/src/saveslots.cpp +++ b/doomsday/plugins/common/src/saveslots.cpp @@ -21,44 +21,60 @@ #include "common.h" #include "saveslots.h" -#include "p_savedef.h" #include "p_saveio.h" #include "saveinfo.h" #include #include -#include +#include #define MAX_HUB_MAPS 99 static int cvarLastSlot = -1; ///< @c -1= Not yet loaded/saved in this game session. static int cvarQuickSlot = -1; ///< @c -1= Not yet chosen/determined. +using namespace de; + DENG2_PIMPL_NOREF(SaveSlots::Slot) { - de::String saveName; + String id; + bool userWritable; + String fileName; SaveInfo *info; - Instance() : info(0) {} + Instance() : userWritable(true), info(0) {} ~Instance() { delete info; } }; -SaveSlots::Slot::Slot(de::String const &saveName) : d(new Instance) +SaveSlots::Slot::Slot(String id, bool userWritable, String const &fileName) + : d(new Instance) +{ + d->id = id; + d->userWritable = userWritable; + d->fileName = fileName; +} + +String const &SaveSlots::Slot::id() const +{ + return d->id; +} + +bool SaveSlots::Slot::isUserWritable() const { - d->saveName = saveName; + return d->userWritable; } -de::String SaveSlots::Slot::fileName() const +String const &SaveSlots::Slot::fileName() const { - return d->saveName; + return d->fileName; } -void SaveSlots::Slot::bindFileName(de::String newSaveName) +void SaveSlots::Slot::bindFileName(String newName) { - if(d->saveName.compareWithoutCase(newSaveName)) + if(d->fileName.compareWithoutCase(newName)) { clearSaveInfo(); } - d->saveName = newSaveName; + d->fileName = newName; } bool SaveSlots::Slot::isUsed() const @@ -94,48 +110,22 @@ SaveInfo &SaveSlots::Slot::saveInfo() const throw MissingInfoError("SaveSlots::Slot::saveInfo", "No SaveInfo exists"); } -/// @todo We should look at all files on the save path and not just those which match -/// the default game-save file naming convention. -DENG2_PIMPL(SaveSlots) +DENG2_PIMPL_NOREF(SaveSlots) { - int slotCount; - typedef std::vector Slots; + typedef std::map Slots; Slots sslots; - Slot autoSlot; -#if __JHEXEN__ - Slot baseSlot; -#endif - Instance(Public *i, int slotCount) - : Base(i) - , slotCount(de::max(1, slotCount)) - , autoSlot(de::String(SAVEGAMENAME "%1").arg(AUTO_SLOT)) -#if __JHEXEN__ - , baseSlot(de::String(SAVEGAMENAME "%1").arg(BASE_SLOT)) -#endif + Instance() {} + ~Instance() { DENG2_FOR_EACH(Slots, i, sslots) { delete i->second; } } + + SaveSlot *slotById(String id) { - for(int i = 0; i < slotCount; ++i) + Slots::const_iterator found = sslots.find(id); + if(found != sslots.end()) { - sslots.push_back(new Slot(de::String(SAVEGAMENAME "%1").arg(i))); + return found->second; } - } - - ~Instance() - { - DENG2_FOR_EACH(Slots, i, sslots) { delete *i; } - } - - /// Determines whether to announce when the specified @a slotNumber is cleared. - bool shouldAnnounceWhenClearing(int slotNumber) - { -#ifdef DENG_DEBUG - return true; // Always. -#endif -#if __JHEXEN__ - return (slotNumber != AUTO_SLOT && slotNumber != BASE_SLOT); -#else - return (slotNumber != AUTO_SLOT); -#endif + return 0; // Not found. } /// Re-build save info by re-scanning the save paths and populating the list. @@ -143,41 +133,34 @@ DENG2_PIMPL(SaveSlots) { DENG2_FOR_EACH(Slots, i, sslots) { - SaveSlot &sslot = **i; + SaveSlot &sslot = *i->second; if(!sslot.hasSaveInfo()) { sslot.replaceSaveInfo(new SaveInfo(sslot.fileName())); } if(update) sslot.saveInfo().updateFromFile(); } - if(!autoSlot.hasSaveInfo()) - { - autoSlot.replaceSaveInfo(new SaveInfo(autoSlot.fileName())); - } - if(update) autoSlot.saveInfo().updateFromFile(); -#if __JHEXEN__ - if(!baseSlot.hasSaveInfo()) - { - baseSlot.replaceSaveInfo(new SaveInfo(baseSlot.fileName())); - } - if(update) baseSlot.saveInfo().updateFromFile(); -#endif } }; -SaveSlots::SaveSlots(int slotCount) : d(new Instance(this, slotCount)) +SaveSlots::SaveSlots() : d(new Instance) {} +void SaveSlots::addSlot(String id, bool userWritable, String fileName) +{ + // Ensure the slot identifier is unique. + if(d->slotById(id)) return; + + // Insert a new save slot. + d->sslots.insert(std::pair(id, new Slot(id, userWritable, fileName))); +} + void SaveSlots::clearAll() { DENG2_FOR_EACH(Instance::Slots, i, d->sslots) { - (*i)->clearSaveInfo(); + i->second->clearSaveInfo(); } - d->autoSlot.clearSaveInfo(); -#if __JHEXEN__ - d->baseSlot.clearSaveInfo(); -#endif // Reset last-used and quick-save slot tracking. Con_SetInteger2("game-save-last-slot", -1, SVF_WRITE_OVERRIDE); @@ -189,111 +172,36 @@ void SaveSlots::updateAll() d->buildInfosIfNeeded(true/*update session headers from source files*/); } -de::String SaveSlots::slotIdentifier(int slot) const -{ - if(slot < 0) return "(invalid slot)"; - if(slot == AUTO_SLOT) return ""; -#if __JHEXEN__ - if(slot == BASE_SLOT) return ""; -#endif - return de::String::number(slot); -} - -int SaveSlots::parseSlotIdentifier(de::String str) const -{ - // Try game-save name match. - int slot = findSlotWithUserSaveDescription(str); - if(slot >= 0) return slot; - - // Try keyword identifiers. - if(!str.compareWithoutCase("last") || !str.compareWithoutCase("")) - { - return Con_GetInteger("game-save-last-slot"); - } - if(!str.compareWithoutCase("quick") || !str.compareWithoutCase("")) - { - return Con_GetInteger("game-save-quick-slot"); - } - if(!str.compareWithoutCase("auto") || !str.compareWithoutCase("")) - { - return AUTO_SLOT; - } - - // Try logical slot identifier. - bool ok; - slot = str.toInt(&ok); - if(ok) return slot; - - // Unknown/not found. - return -1; -} - -int SaveSlots::findSlotWithUserSaveDescription(de::String description) const -{ - if(!description.isEmpty()) - { - for(int i = 0; i < (signed)d->sslots.size(); ++i) - { - SaveSlot &sslot = *d->sslots[i]; - if(sslot.hasSaveInfo() && - !sslot.saveInfo().userDescription().compareWithoutCase(description)) - { - return i; - } - } - } - return -1; // Not found. -} - int SaveSlots::slotCount() const { - return d->slotCount; + return int(d->sslots.size()); } -bool SaveSlots::isKnownSlot(int slot) const +bool SaveSlots::isKnownSlot(String value) const { - if(slot == AUTO_SLOT) return true; -#if __JHEXEN__ - if(slot == BASE_SLOT) return true; -#endif - return (slot >= 0 && slot < d->slotCount); + return d->slotById(value) != 0; } -bool SaveSlots::slotIsUserWritable(int slot) const +SaveSlots::Slot &SaveSlots::slot(String slotId) const { - if(slot == AUTO_SLOT) return false; -#if __JHEXEN__ - if(slot == BASE_SLOT) return false; -#endif - return isKnownSlot(slot); -} + d->buildInfosIfNeeded(); -SaveSlots::Slot &SaveSlots::slot(int slotNumber) const -{ - if(!isKnownSlot(slotNumber)) + if(SaveSlot *sslot = d->slotById(slotId)) { - /// @throw InvalidSlotError An invalid slot was specified. - throw InvalidSlotError("SaveSlots::slot", "Invalid slot #" + de::String::number(slotNumber)); + return *sslot; } - - d->buildInfosIfNeeded(); - - if(slotNumber == AUTO_SLOT) return d->autoSlot; -#if __JHEXEN__ - if(slotNumber == BASE_SLOT) return d->baseSlot; -#endif - DENG2_ASSERT(slotNumber >= 0 && slotNumber < int( d->sslots.size() )); - return *d->sslots[slotNumber]; + /// @throw InvalidSlotError An invalid slot was specified. + throw InvalidSlotError("SaveSlots::slot", "Invalid slot id '" + slotId + "'"); } -void SaveSlots::clearSlot(int slotNumber) +void SaveSlots::clearSlot(String slotId) { if(SV_SavePath().isEmpty()) { return; } - Slot &sslot = slot(slotNumber); + Slot &sslot = slot(slotId); if(!sslot.hasSaveInfo()) { sslot.replaceSaveInfo(new SaveInfo(sslot.fileName())); @@ -301,9 +209,12 @@ void SaveSlots::clearSlot(int slotNumber) SaveInfo &saveInfo = sslot.saveInfo(); - if(d->shouldAnnounceWhenClearing(slotNumber)) + // Should we announce this? +#if !defined DENG_DEBUG // Always + if(sslot.isUserWritable()) +#endif { - App_Log(DE2_RES_MSG, "Clearing save slot %s", slotIdentifier(slotNumber).toLatin1().constData()); + App_Log(DE2_RES_MSG, "Clearing save slot '%s'", slotId.toLatin1().constData()); } for(int i = 0; i < MAX_HUB_MAPS; ++i) @@ -317,7 +228,7 @@ void SaveSlots::clearSlot(int slotNumber) saveInfo.setSessionId(0); } -void SaveSlots::copySlot(int sourceSlotNumber, int destSlotNumber) +void SaveSlots::copySlot(String sourceSlotId, String destSlotId) { LOG_AS("SaveSlots::copySlot"); @@ -326,11 +237,17 @@ void SaveSlots::copySlot(int sourceSlotNumber, int destSlotNumber) return; } - Slot &sourceSlot = slot(sourceSlotNumber); - Slot &destSlot = slot(destSlotNumber); + Slot &sourceSlot = slot(sourceSlotId); + Slot &destSlot = slot(destSlotId); + + // Sanity check. + if(&sourceSlot == &destSlot) + { + return; + } // Clear all save files at destination slot. - clearSlot(destSlotNumber); + clearSlot(destSlotId); for(int i = 0; i < MAX_HUB_MAPS; ++i) { @@ -347,6 +264,24 @@ void SaveSlots::copySlot(int sourceSlotNumber, int destSlotNumber) destSlot.saveInfo().setFileName(destSlot.fileName()); } +String SaveSlots::findSlotWithUserSaveDescription(String description) const +{ + if(!description.isEmpty()) + { + DENG2_FOR_EACH_CONST(Instance::Slots, i, d->sslots) + { + SaveSlot &sslot = *i->second; + if(!sslot.hasSaveInfo()) continue; + + if(!sslot.saveInfo().userDescription().compareWithoutCase(description)) + { + return sslot.id(); + } + } + } + return ""; // Not found. +} + void SaveSlots::consoleRegister() // static { C_VAR_INT("game-save-last-slot", &cvarLastSlot, CVF_NO_MIN|CVF_NO_MAX|CVF_NO_ARCHIVE|CVF_READ_ONLY, 0, 0); diff --git a/doomsday/plugins/doom/include/g_game.h b/doomsday/plugins/doom/include/g_game.h index c78e780d48..b310b5fc3f 100644 --- a/doomsday/plugins/doom/include/g_game.h +++ b/doomsday/plugins/doom/include/g_game.h @@ -39,9 +39,6 @@ #include "d_player.h" #include "gamerules.h" -DENG_EXTERN_C int gaSaveGameSlot; -DENG_EXTERN_C int gaLoadGameSlot; - DENG_EXTERN_C player_t players[MAXPLAYERS]; DENG_EXTERN_C dd_bool gameInProgress; @@ -95,27 +92,9 @@ void G_QuitGame(void); /// @return @c true = loading is presently possible. dd_bool G_IsLoadGamePossible(void); -/** - * To be called to schedule a load game-save action. - * @param slot Logical identifier of the save slot to use. - * @return @c true iff @a slot is in use and loading is presently possible. - */ -dd_bool G_LoadGame(int slot); - /// @return @c true = saving is presently possible. dd_bool G_IsSaveGamePossible(void); -/** - * To be called to schedule a save game-save action. - * @param slot Logical identifier of the save slot to use. - * @param name New name for the game-save. Can be @c NULL in which case - * the name will not change if the slot has already been used. - * If an empty string a new name will be generated automatically. - * @return @c true iff @a slot is valid and saving is presently possible. - */ -dd_bool G_SaveGame2(int slot, char const *name); -dd_bool G_SaveGame(int slot); - void G_StopDemo(void); /** diff --git a/doomsday/plugins/doom/src/d_main.cpp b/doomsday/plugins/doom/src/d_main.cpp index a8182af7f4..aa8d2f973b 100644 --- a/doomsday/plugins/doom/src/d_main.cpp +++ b/doomsday/plugins/doom/src/d_main.cpp @@ -452,12 +452,17 @@ void D_PostInit() p = CommandLine_Check("-loadgame"); if(p && p < myargc - 1) { - int const slotNumber = G_SaveSlots().parseSlotIdentifier(CommandLine_At(p + 1)); - if(G_SaveSlots().slotIsUserWritable(slotNumber) && G_LoadGame(slotNumber)) + try { - // No further initialization is to be done. - return; + de::String const slotId = G_SaveSlotIdFromUserInput(CommandLine_At(p + 1)); + if(G_SaveSlots()[slotId].isUserWritable() && G_LoadGame(slotId)) + { + // No further initialization is to be done. + return; + } } + catch(SaveSlots::InvalidSlotError const &) + {} } p = CommandLine_Check("-skill"); diff --git a/doomsday/plugins/doom64/include/g_game.h b/doomsday/plugins/doom64/include/g_game.h index b6f8c121a7..a13c0077b7 100644 --- a/doomsday/plugins/doom64/include/g_game.h +++ b/doomsday/plugins/doom64/include/g_game.h @@ -84,27 +84,9 @@ void G_QuitGame(void); /// @return @c true = loading is presently possible. dd_bool G_IsLoadGamePossible(void); -/** - * To be called to schedule a load game-save action. - * @param slot Logical identifier of the save slot to use. - * @return @c true iff @a saveSlot is in use and loading is presently possible. - */ -dd_bool G_LoadGame(int slot); - /// @return @c true = saving is presently possible. dd_bool G_IsSaveGamePossible(void); -/** - * To be called to schedule a save game-save action. - * @param slot Logical identifier of the save slot to use. - * @param name New name for the game-save. Can be @c NULL in which case - * the name will not change if the slot has already been used. - * If an empty string a new name will be generated automatically. - * @return @c true iff @a saveSlot is valid and saving is presently possible. - */ -dd_bool G_SaveGame2(int slot, const char* name); -dd_bool G_SaveGame(int slot); - void G_StopDemo(void); int G_BriefingEnabled(Uri const *mapUri, ddfinale_t *fin); diff --git a/doomsday/plugins/doom64/src/d_main.cpp b/doomsday/plugins/doom64/src/d_main.cpp index c4ae741e61..859e84d5f9 100644 --- a/doomsday/plugins/doom64/src/d_main.cpp +++ b/doomsday/plugins/doom64/src/d_main.cpp @@ -358,12 +358,17 @@ void D_PostInit() p = CommandLine_Check("-loadgame"); if(p && p < myargc - 1) { - int const slotNumber = G_SaveSlots().parseSlotIdentifier(CommandLine_At(p + 1)); - if(G_SaveSlots().slotIsUserWritable(slotNumber) && G_LoadGame(slotNumber)) + try { - // No further initialization is to be done. - return; + de::String const slotId = G_SaveSlotIdFromUserInput(CommandLine_At(p + 1)); + if(G_SaveSlots()[slotId].isUserWritable() && G_LoadGame(slotId)) + { + // No further initialization is to be done. + return; + } } + catch(SaveSlots::InvalidSlotError const &) + {} } p = CommandLine_Check("-skill"); diff --git a/doomsday/plugins/heretic/include/g_game.h b/doomsday/plugins/heretic/include/g_game.h index a50fab305b..daf9b354c9 100644 --- a/doomsday/plugins/heretic/include/g_game.h +++ b/doomsday/plugins/heretic/include/g_game.h @@ -83,27 +83,9 @@ void G_QuitGame(void); /// @return @c true = loading is presently possible. dd_bool G_IsLoadGamePossible(void); -/** - * To be called to schedule a load game-save action. - * @param slot Logical identifier of the save slot to use. - * @return @c true iff @a saveSlot is in use and loading is presently possible. - */ -dd_bool G_LoadGame(int slot); - /// @return @c true = saving is presently possible. dd_bool G_IsSaveGamePossible(void); -/** - * To be called to schedule a save game-save action. - * @param slot Logical identifier of the save slot to use. - * @param name New name for the game-save. Can be @c NULL in which case - * the name will not change if the slot has already been used. - * If an empty string a new name will be generated automatically. - * @return @c true iff @a saveSlot is valid and saving is presently possible. - */ -dd_bool G_SaveGame2(int slot, char const *name); -dd_bool G_SaveGame(int slot); - void G_StopDemo(void); int G_BriefingEnabled(Uri const *mapUri, ddfinale_t *fin); diff --git a/doomsday/plugins/heretic/src/h_main.cpp b/doomsday/plugins/heretic/src/h_main.cpp index 0c556ab527..bed4e13b1c 100644 --- a/doomsday/plugins/heretic/src/h_main.cpp +++ b/doomsday/plugins/heretic/src/h_main.cpp @@ -380,12 +380,17 @@ void H_PostInit() p = CommandLine_Check("-loadgame"); if(p && p < myargc - 1) { - int const slotNumber = G_SaveSlots().parseSlotIdentifier(CommandLine_At(p + 1)); - if(G_SaveSlots().slotIsUserWritable(slotNumber) && G_LoadGame(slotNumber)) + try { - // No further initialization is to be done. - return; + de::String const slotId = G_SaveSlotIdFromUserInput(CommandLine_At(p + 1)); + if(G_SaveSlots()[slotId].isUserWritable() && G_LoadGame(slotId)) + { + // No further initialization is to be done. + return; + } } + catch(SaveSlots::InvalidSlotError const &) + {} } p = CommandLine_Check("-skill"); diff --git a/doomsday/plugins/hexen/include/g_game.h b/doomsday/plugins/hexen/include/g_game.h index 1113ee1c08..b7bd3e4993 100644 --- a/doomsday/plugins/hexen/include/g_game.h +++ b/doomsday/plugins/hexen/include/g_game.h @@ -78,27 +78,9 @@ void G_QuitGame(void); /// @return @c true = loading is presently possible. dd_bool G_IsLoadGamePossible(void); -/** - * To be called to schedule a load game-save action. - * @param slot Logical identifier of the save slot to use. - * @return @c true iff @a saveSlot is in use and loading is presently possible. - */ -dd_bool G_LoadGame(int slot); - /// @return @c true = saving is presently possible. dd_bool G_IsSaveGamePossible(void); -/** - * To be called to schedule a save game-save action. - * @param slot Logical identifier of the save slot to use. - * @param name New name for the game-save. Can be @c NULL in which case - * the name will not change if the slot has already been used. - * If an empty string a new name will be generated automatically. - * @return @c true iff @a saveSlot is valid and saving is presently possible. - */ -dd_bool G_SaveGame2(int slot, char const *name); -dd_bool G_SaveGame(int slot); - void G_CommonPreInit(void); void G_CommonPostInit(void); diff --git a/doomsday/plugins/hexen/src/acscript.cpp b/doomsday/plugins/hexen/src/acscript.cpp index 87ee38fc70..2e166b5354 100644 --- a/doomsday/plugins/hexen/src/acscript.cpp +++ b/doomsday/plugins/hexen/src/acscript.cpp @@ -257,7 +257,6 @@ bool ACScriptInterpreter::startScript(int scriptNumber, Uri const *mapUri, byte const args[], mobj_t *activator, Line *line, int side) { DENG_ASSERT(!IS_CLIENT); - DENG_ASSERT(mapUri != 0); if(mapUri) { diff --git a/doomsday/plugins/hexen/src/h2_main.cpp b/doomsday/plugins/hexen/src/h2_main.cpp index a31a346d9a..95226631df 100644 --- a/doomsday/plugins/hexen/src/h2_main.cpp +++ b/doomsday/plugins/hexen/src/h2_main.cpp @@ -355,12 +355,17 @@ void X_PostInit() p = CommandLine_CheckWith("-loadgame", 1); if(p != 0) { - int const slotNumber = G_SaveSlots().parseSlotIdentifier(CommandLine_At(p + 1)); - if(G_SaveSlots().slotIsUserWritable(slotNumber) && G_LoadGame(slotNumber)) + try { - // No further initialization is to be done. - return; + de::String const slotId = G_SaveSlotIdFromUserInput(CommandLine_At(p + 1)); + if(G_SaveSlots()[slotId].isUserWritable() && G_LoadGame(slotId)) + { + // No further initialization is to be done. + return; + } } + catch(SaveSlots::InvalidSlotError const &) + {} } if((p = CommandLine_CheckWith("-skill", 1)) != 0)