From 45026040074f61405dbd4b8b909356c38b8b9a6c Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 22 Jun 2020 19:00:47 +0100 Subject: [PATCH] Special saves! All this refactoring, just to resolve #162. Specifically, it engages savegame events not at level load (except for savefile start) but on level completion, just after you've gotten all the intermission bonuses but before the intermission actually starts. Also fixes a never-before-discovered bug where if the titlemap has LF_SAVEGAME, your save file will be overwritten upon returning to the title screen. This game is a mess of hacks, I swear... One unintended side effect: It may actually be faster in some speedrun circumstances in mods with cutscenes to complete the map, exit back to the title screen, and reload the file. It's a common feature of optimal runs in games with cutscenes, though, and Marathon Run has a toggle for cutscenes, so I'm not particularly bothered. --- src/g_game.c | 140 +++++++++++++++++++++++++++++--------------------- src/g_game.h | 2 +- src/p_saveg.c | 17 +++--- src/p_saveg.h | 2 +- src/p_setup.c | 33 +++++------- 5 files changed, 104 insertions(+), 90 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 73d21ad54..aea88988d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3676,6 +3676,24 @@ static void G_UpdateVisited(void) } } +static boolean CanSaveLevel(INT32 mapnum) +{ + // You can never save in a special stage. + if (G_IsSpecialStage(mapnum)) + return false; + + // If the game is complete for this save slot, then any level can save! + if (gamecomplete) + return true; + + // Be kind with Marathon Mode live event backups. + if (marathonmode) + return true; + + // Any levels that have the savegame flag can save normally. + return (mapheaderinfo[mapnum-1] && (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME)); +} + // // G_DoCompleted // @@ -3720,65 +3738,65 @@ static void G_DoCompleted(void) nextmap = 1100-1; // No infinite loop for you } - // Remember last map for when you come out of the special stage. - if (!spec) - lastmap = nextmap; - // If nextmap is actually going to get used, make sure it points to // a map of the proper gametype -- skip levels that don't support // the current gametype. (Helps avoid playing boss levels in Race, // for instance). - if (!token && !spec - && (nextmap >= 0 && nextmap < NUMMAPS)) + if (!spec) { - register INT16 cm = nextmap; - UINT32 tolflag = G_TOLFlag(gametype); - UINT8 visitedmap[(NUMMAPS+7)/8]; - - memset(visitedmap, 0, sizeof (visitedmap)); - - while (!mapheaderinfo[cm] || !(mapheaderinfo[cm]->typeoflevel & tolflag)) + if (nextmap >= 0 && nextmap < NUMMAPS) { - visitedmap[cm/8] |= (1<<(cm&7)); - if (!mapheaderinfo[cm]) - cm = -1; // guarantee error execution - else if (marathonmode && mapheaderinfo[cm]->marathonnext) - cm = (INT16)(mapheaderinfo[cm]->marathonnext-1); - else - cm = (INT16)(mapheaderinfo[cm]->nextlevel-1); + register INT16 cm = nextmap; + UINT32 tolflag = G_TOLFlag(gametype); + UINT8 visitedmap[(NUMMAPS+7)/8]; - if (cm >= NUMMAPS || cm < 0) // out of range (either 1100ish or error) + memset(visitedmap, 0, sizeof (visitedmap)); + + while (!mapheaderinfo[cm] || !(mapheaderinfo[cm]->typeoflevel & tolflag)) { - cm = nextmap; //Start the loop again so that the error checking below is executed. + visitedmap[cm/8] |= (1<<(cm&7)); + if (!mapheaderinfo[cm]) + cm = -1; // guarantee error execution + else if (marathonmode && mapheaderinfo[cm]->marathonnext) + cm = (INT16)(mapheaderinfo[cm]->marathonnext-1); + else + cm = (INT16)(mapheaderinfo[cm]->nextlevel-1); - //Make sure the map actually exists before you try to go to it! - if ((W_CheckNumForName(G_BuildMapName(cm + 1)) == LUMPERROR)) + if (cm >= NUMMAPS || cm < 0) // out of range (either 1100ish or error) { - CONS_Alert(CONS_ERROR, M_GetText("Next map given (MAP %d) doesn't exist! Reverting to MAP01.\n"), cm+1); - cm = 0; + cm = nextmap; //Start the loop again so that the error checking below is executed. + + //Make sure the map actually exists before you try to go to it! + if ((W_CheckNumForName(G_BuildMapName(cm + 1)) == LUMPERROR)) + { + CONS_Alert(CONS_ERROR, M_GetText("Next map given (MAP %d) doesn't exist! Reverting to MAP01.\n"), cm+1); + cm = 0; + break; + } + } + + if (visitedmap[cm/8] & (1<<(cm&7))) // smells familiar + { + // We got stuck in a loop, came back to the map we started on + // without finding one supporting the current gametype. + // Thus, print a warning, and just use this map anyways. + CONS_Alert(CONS_WARNING, M_GetText("Can't find a compatible map after map %d; using map %d anyway\n"), prevmap+1, cm+1); break; } } - - if (visitedmap[cm/8] & (1<<(cm&7))) // smells familiar - { - // We got stuck in a loop, came back to the map we started on - // without finding one supporting the current gametype. - // Thus, print a warning, and just use this map anyways. - CONS_Alert(CONS_WARNING, M_GetText("Can't find a compatible map after map %d; using map %d anyway\n"), prevmap+1, cm+1); - break; - } + nextmap = cm; } - nextmap = cm; + + // wrap around in race + if (nextmap >= 1100-1 && nextmap <= 1102-1 && !(gametyperules & GTR_CAMPAIGN)) + nextmap = (INT16)(spstage_start-1); + + if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1103-1) + I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1); + + lastmap = nextmap; // Remember last map for when you come out of the special stage. } - if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1103-1) - I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1); - - // wrap around in race - if (nextmap >= 1100-1 && nextmap <= 1102-1 && !(gametyperules & GTR_CAMPAIGN)) - nextmap = (INT16)(spstage_start-1); - if ((gottoken = ((gametyperules & GTR_SPECIALSTAGES) && token))) { token--; @@ -3816,7 +3834,19 @@ static void G_DoCompleted(void) if (nextmap < NUMMAPS && !mapheaderinfo[nextmap]) P_AllocMapHeader(nextmap); - // do this before going to the intermission or starting a custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c + if ((skipstats && !modeattacking) || (spec && modeattacking && stagefailed)) + { + G_UpdateVisited(); + G_AfterIntermission(); + } + else + { + G_SetGamestate(GS_INTERMISSION); + Y_StartIntermission(); + G_UpdateVisited(); + } + + // do this before running the intermission or custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c if (nextmap >= 1100-1) { if (!gamecomplete) @@ -3831,21 +3861,13 @@ static void G_DoCompleted(void) cursaveslot = 0; } else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer)) - G_SaveGame((UINT32)cursaveslot); + G_SaveGame((UINT32)cursaveslot, spstage_start); } } - - if ((skipstats && !modeattacking) || (spec && modeattacking && stagefailed)) - { - G_UpdateVisited(); - G_AfterIntermission(); - } - else - { - G_SetGamestate(GS_INTERMISSION); - Y_StartIntermission(); - G_UpdateVisited(); - } + // and doing THIS here means you don't lose your progress if you close the game mid-intermission + else if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) + && (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(lastmap+1)) + G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages } // See also F_EndCutscene, the only other place which handles intra-map/ending transitions @@ -4407,7 +4429,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // G_SaveGame // Saves your game. // -void G_SaveGame(UINT32 slot) +void G_SaveGame(UINT32 slot, INT16 mapnum) { boolean saved; char savename[256] = ""; @@ -4435,7 +4457,7 @@ void G_SaveGame(UINT32 slot) sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION); WRITEMEM(save_p, name, VERSIONSIZE); - P_SaveGame(); + P_SaveGame(mapnum); if (marathonmode) { WRITEUINT32(save_p, marathontime); diff --git a/src/g_game.h b/src/g_game.h index 21fa682b7..36d6759bf 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -166,7 +166,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride); void G_SaveGameData(void); -void G_SaveGame(UINT32 slot); +void G_SaveGame(UINT32 slot, INT16 mapnum); void G_SaveGameOver(UINT32 slot, boolean modifylives); diff --git a/src/p_saveg.c b/src/p_saveg.c index b4d7ef838..6f5446795 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -3785,16 +3785,15 @@ static void P_NetUnArchiveSpecials(void) // ======================================================================= // Misc // ======================================================================= -static inline void P_ArchiveMisc(void) +static inline void P_ArchiveMisc(INT16 mapnum) { + //lastmapsaved = mapnum; + lastmaploaded = mapnum; + if (gamecomplete) - WRITEINT16(save_p, gamemap | 8192); - else - WRITEINT16(save_p, gamemap); - - //lastmapsaved = gamemap; - lastmaploaded = gamemap; + mapnum |= 8192; + WRITEINT16(save_p, mapnum); WRITEUINT16(save_p, emeralds+357); WRITESTRINGN(save_p, timeattackfolder, sizeof(timeattackfolder)); } @@ -4035,9 +4034,9 @@ static inline boolean P_UnArchiveLuabanksAndConsistency(void) return true; } -void P_SaveGame(void) +void P_SaveGame(INT16 mapnum) { - P_ArchiveMisc(); + P_ArchiveMisc(mapnum); P_ArchivePlayer(); P_ArchiveLuabanksAndConsistency(); } diff --git a/src/p_saveg.h b/src/p_saveg.h index 012e7023b..d8756a7a9 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -21,7 +21,7 @@ // Persistent storage/archiving. // These are the load / save game routines. -void P_SaveGame(void); +void P_SaveGame(INT16 mapnum); void P_SaveNetGame(void); boolean P_LoadGame(INT16 mapoverride); boolean P_LoadNetGame(void); diff --git a/src/p_setup.c b/src/p_setup.c index ffb68b1b0..e4be1bc63 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3297,21 +3297,6 @@ static void P_InitCamera(void) } } -static boolean CanSaveLevel(INT32 mapnum) -{ - if (ultimatemode) // never save in ultimate (probably redundant with cursaveslot also being checked) - return false; - - if (G_IsSpecialStage(mapnum) // don't save in special stages - || mapnum == lastmaploaded) // don't save if the last map loaded was this one - return false; - - // Any levels that have the savegame flag can save normally. - // If the game is complete for this save slot, then any level can save! - // On the other side of the spectrum, if lastmaploaded is 0, then the save file has only just been created and needs to save ASAP! - return (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME || (gamecomplete != 0) || marathonmode || !lastmaploaded); -} - static void P_RunSpecialStageWipe(void) { tic_t starttime = I_GetTime(); @@ -3748,11 +3733,19 @@ boolean P_LoadLevel(boolean fromnetsave) P_RunCachedActions(); - if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking || players[consoleplayer].lives <= 0) - && (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(gamemap)) - G_SaveGame((UINT32)cursaveslot); - - lastmaploaded = gamemap; // HAS to be set after saving!! + // Took me 3 hours to figure out why my progression kept on getting overwritten with the titlemap... + if (!titlemapinaction) + { + if (!lastmaploaded) // Start a new game? + { + // I'd love to do this in the menu code instead of here, but everything's a mess and I can't guarantee saving proper player struct info before the first act's started. You could probably refactor it, but it'd be a lot of effort. Easier to just work off known good code. ~toast 22/06/2020 + if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) + && (!modifiedgame || savemoddata) && cursaveslot > 0) + G_SaveGame((UINT32)cursaveslot, gamemap); + // If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted. + } + lastmaploaded = gamemap; // HAS to be set after saving!! + } if (!fromnetsave) // uglier hack { // to make a newly loaded level start on the second frame.