Merge branch 'special_save' into 'next'

Special saves! (Resolves #162)

Closes #162

See merge request STJr/SRB2!1016
This commit is contained in:
James R 2020-06-29 19:18:30 -04:00
commit 696f9707fc
5 changed files with 110 additions and 96 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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();
}

View File

@ -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);

View File

@ -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.