diff --git a/src/g_game.c b/src/g_game.c index a927f8ef2..c9b570044 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3711,6 +3711,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 // @@ -3755,65 +3773,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--; @@ -3851,25 +3869,6 @@ 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 (nextmap >= 1100-1) - { - if (!gamecomplete) - gamecomplete = 2; // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission - if (cursaveslot > 0) - { - if (marathonmode) - { - // don't keep a backup around when the run is done! - if (FIL_FileExists(liveeventbackup)) - remove(liveeventbackup); - cursaveslot = 0; - } - else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer)) - G_SaveGame((UINT32)cursaveslot); - } - } - // If the current gametype has no intermission screen set, then don't start it. Y_DetermineIntermissionType(); @@ -3884,6 +3883,29 @@ static void G_DoCompleted(void) 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) + gamecomplete = 2; // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission + if (cursaveslot > 0) + { + if (marathonmode) + { + // don't keep a backup around when the run is done! + if (FIL_FileExists(liveeventbackup)) + remove(liveeventbackup); + cursaveslot = 0; + } + else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking)) + G_SaveGame((UINT32)cursaveslot, spstage_start); + } + } + // 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 @@ -3902,7 +3924,7 @@ void G_AfterIntermission(void) HU_ClearCEcho(); - if ((gametyperules & GTR_CUTSCENES) && mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1 && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene. + if ((gametyperules & GTR_CUTSCENES) && mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false); else { @@ -4445,7 +4467,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] = ""; @@ -4473,7 +4495,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 fecfa4096..c8abe560c 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 5027bab0c..f86de5ad4 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.