Gamedata-related changes + Crash tracking

- Combine multiple adjacent saves
    - Generally could happen during game ticking, combined via gamedata->deferredstate
    - DEFINITELY happened in splitscreen PWR handling, adjust those loops directly
- Write "dirty" state via gamedata->crashflags on everything except safe, intentful unloads
    - Add UC_CRASH, which unlocks dependent on the above "dirty" state being present at gamedata load
    - We can use this for something more useful and less funny later.
- Play "O_LOSERC" on the menu, starting with the Challenges screen, if a UC_CRASH condition has been met.
This commit is contained in:
toaster 2023-03-03 13:38:31 +00:00
parent b4217e1afd
commit 53ce2e4287
17 changed files with 117 additions and 40 deletions

View file

@ -977,6 +977,9 @@ void D_ClearState(void)
cursongcredit.def = NULL; cursongcredit.def = NULL;
if (gamedata->deferredsave)
G_SaveGameData(true);
G_SetGamestate(GS_NULL); G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL; wipegamestate = GS_NULL;
} }

View file

@ -2482,6 +2482,11 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
return; return;
} }
} }
else if (fastcmp(params[0], "CRASH"))
{
//PARAMCHECK(1);
ty = UC_CRASH;
}
else if (fastcmp(params[0], "AND")) else if (fastcmp(params[0], "AND"))
{ {
//PARAMCHECK(1); //PARAMCHECK(1);
@ -2689,7 +2694,7 @@ void readmaincfg(MYFILE *f, boolean mainfile)
if (!GoodDataFileName(word2)) if (!GoodDataFileName(word2))
I_Error("Maincfg: bad data file name '%s'\n", word2); I_Error("Maincfg: bad data file name '%s'\n", word2);
G_SaveGameData(); G_SaveGameData(false); // undirty your old gamedata
strlcpy(gamedatafilename, word2, sizeof (gamedatafilename)); strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
strlwr(gamedatafilename); strlwr(gamedatafilename);
savemoddata = true; savemoddata = true;

View file

@ -1081,7 +1081,7 @@ void F_GameEvaluationTicker(void)
++gamedata->timesBeaten; ++gamedata->timesBeaten;
M_UpdateUnlockablesAndExtraEmblems(true); M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData(); G_SaveGameData(true);
} }
else else
{ {

View file

@ -649,7 +649,7 @@ void G_UpdateRecords(void)
} }
M_UpdateUnlockablesAndExtraEmblems(true); M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData(); gamedata->deferredsave = true;
} }
// //
@ -2282,19 +2282,6 @@ static inline void G_PlayerFinishLevel(INT32 player)
p->starpostnum = 0; p->starpostnum = 0;
memset(&p->respawn, 0, sizeof (p->respawn)); memset(&p->respawn, 0, sizeof (p->respawn));
// SRB2kart: Increment the "matches played" counter.
if (player == consoleplayer)
{
if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified)
{
gamedata->matchesplayed++;
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
legitimateexit = false;
}
} }
// //
@ -3697,7 +3684,7 @@ static void G_UpdateVisited(void)
CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
M_UpdateUnlockablesAndExtraEmblems(true); M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData(); G_SaveGameData(true);
} }
static boolean CanSaveLevel(INT32 mapnum) static boolean CanSaveLevel(INT32 mapnum)
@ -4005,6 +3992,19 @@ static void G_DoCompleted(void)
if (modeattacking && pausedelay) if (modeattacking && pausedelay)
pausedelay = 0; pausedelay = 0;
if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified)
{
// Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve
gamedata->matchesplayed++;
M_UpdateUnlockablesAndExtraEmblems(true);
gamedata->deferredsave = true;
}
if (gamedata->deferredsave)
G_SaveGameData(true);
legitimateexit = false;
gameaction = ga_nothing; gameaction = ga_nothing;
if (metalplayback) if (metalplayback)
@ -4317,7 +4317,7 @@ void G_LoadGameSettings(void)
} }
#define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual #define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual
#define GD_VERSIONMINOR 1 // Change every format update #define GD_VERSIONMINOR 2 // Change every format update
static const char *G_GameDataFolder(void) static const char *G_GameDataFolder(void)
{ {
@ -4351,6 +4351,7 @@ void G_LoadGameData(void)
gamedata->totalplaytime = 0; // total play time (separate from all) gamedata->totalplaytime = 0; // total play time (separate from all)
gamedata->matchesplayed = 0; // SRB2Kart: matches played & finished gamedata->matchesplayed = 0; // SRB2Kart: matches played & finished
gamedata->crashflags = 0;
if (M_CheckParm("-nodata")) if (M_CheckParm("-nodata"))
{ {
@ -4398,6 +4399,13 @@ void G_LoadGameData(void)
gamedata->totalplaytime = READUINT32(save.p); gamedata->totalplaytime = READUINT32(save.p);
gamedata->matchesplayed = READUINT32(save.p); gamedata->matchesplayed = READUINT32(save.p);
if (versionMinor > 1)
{
gamedata->crashflags = READUINT8(save.p);
if (gamedata->crashflags & GDCRASH_LAST)
gamedata->crashflags |= GDCRASH_ANY;
}
{ {
// Quick & dirty hash for what mod this save file is for. // Quick & dirty hash for what mod this save file is for.
UINT32 modID = READUINT32(save.p); UINT32 modID = READUINT32(save.p);
@ -4543,13 +4551,15 @@ void G_LoadGameData(void)
// G_SaveGameData // G_SaveGameData
// Saves the main data file, which stores information such as emblems found, etc. // Saves the main data file, which stores information such as emblems found, etc.
void G_SaveGameData(void) void G_SaveGameData(boolean dirty)
{ {
size_t length; size_t length;
INT32 i, j; INT32 i, j;
UINT8 btemp; UINT8 btemp;
savebuffer_t save = {0}; savebuffer_t save = {0};
gamedata->deferredsave = false;
if (!gamedata->loaded) if (!gamedata->loaded)
return; // If never loaded (-nodata), don't save return; // If never loaded (-nodata), don't save
@ -4561,7 +4571,7 @@ void G_SaveGameData(void)
return; return;
} }
length = (4+1+4+4+1+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); length = (4+1+4+4+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2);
if (gamedata->challengegrid) if (gamedata->challengegrid)
{ {
length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT;
@ -4580,6 +4590,14 @@ void G_SaveGameData(void)
WRITEUINT8(save.p, GD_VERSIONMINOR); // 1 WRITEUINT8(save.p, GD_VERSIONMINOR); // 1
WRITEUINT32(save.p, gamedata->totalplaytime); // 4 WRITEUINT32(save.p, gamedata->totalplaytime); // 4
WRITEUINT32(save.p, gamedata->matchesplayed); // 4 WRITEUINT32(save.p, gamedata->matchesplayed); // 4
{
UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY);
if (dirty)
crashflags |= GDCRASH_LAST;
WRITEUINT8(save.p, crashflags); // 1
}
WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64)); WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64));
// To save space, use one bit per collected/achieved/unlocked flag // To save space, use one bit per collected/achieved/unlocked flag

View file

@ -173,7 +173,7 @@ boolean G_IsTitleCardAvailable(void);
// Can be called by the startup code or M_Responder, calls P_SetupLevel. // Can be called by the startup code or M_Responder, calls P_SetupLevel.
void G_LoadGame(UINT32 slot, INT16 mapoverride); void G_LoadGame(UINT32 slot, INT16 mapoverride);
void G_SaveGameData(void); void G_SaveGameData(boolean dirty);
void G_SaveGame(UINT32 slot, INT16 mapnum); void G_SaveGame(UINT32 slot, INT16 mapnum);

View file

@ -15,6 +15,7 @@
#include "v_video.h" #include "v_video.h"
#include "f_finale.h" #include "f_finale.h"
#include "m_misc.h" #include "m_misc.h"
#include "m_cond.h"
#ifdef PC_DOS #ifdef PC_DOS
#include <stdio.h> // for snprintf #include <stdio.h> // for snprintf
@ -338,9 +339,17 @@ boolean M_Responder(event_t *ev)
void M_PlayMenuJam(void) void M_PlayMenuJam(void)
{ {
menu_t *refMenu = (menuactive ? currentMenu : restoreMenu); menu_t *refMenu = (menuactive ? currentMenu : restoreMenu);
static boolean loserclubpermitted = false;
boolean loserclub = (loserclubpermitted && (gamedata->crashflags & GDCRASH_LOSERCLUB));
if (challengesmenu.pending) if (challengesmenu.pending)
{
S_StopMusic();
cursongcredit.def = NULL;
loserclubpermitted = true;
return; return;
}
if (Playing()) if (Playing())
return; return;
@ -351,15 +360,27 @@ void M_PlayMenuJam(void)
{ {
S_StopMusic(); S_StopMusic();
cursongcredit.def = NULL; cursongcredit.def = NULL;
return;
} }
else else if (!loserclub)
{ {
if (NotCurrentlyPlaying(refMenu->music)) if (NotCurrentlyPlaying(refMenu->music))
{ {
S_ChangeMusicInternal(refMenu->music, true); S_ChangeMusicInternal(refMenu->music, true);
S_ShowMusicCredit(); S_ShowMusicCredit();
} }
return;
} }
}
if (loserclub)
{
if (refMenu != NULL && NotCurrentlyPlaying("LOSERC"))
{
S_ChangeMusicInternal("LOSERC", true);
S_ShowMusicCredit();
}
return; return;
} }

View file

@ -397,6 +397,7 @@ void K_CashInPowerLevels(void)
{ {
SINT8 powerType = K_UsingPowerLevels(); SINT8 powerType = K_UsingPowerLevels();
UINT8 i; UINT8 i;
boolean gamedataupdate;
//CONS_Printf("\n========\n"); //CONS_Printf("\n========\n");
//CONS_Printf("Cashing in power level changes...\n"); //CONS_Printf("Cashing in power level changes...\n");
@ -417,14 +418,19 @@ void K_CashInPowerLevels(void)
{ {
pr->powerlevels[powerType] = clientpowerlevels[i][powerType]; pr->powerlevels[powerType] = clientpowerlevels[i][powerType];
M_UpdateUnlockablesAndExtraEmblems(true); gamedataupdate = true;
G_SaveGameData();
} }
} }
clientPowerAdd[i] = 0; clientPowerAdd[i] = 0;
} }
if (gamedataupdate)
{
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData(true);
}
//CONS_Printf("========\n"); //CONS_Printf("========\n");
} }
@ -638,6 +644,6 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss)
pr->powerlevels[powerType] = yourPower + inc; pr->powerlevels[powerType] = yourPower + inc;
M_UpdateUnlockablesAndExtraEmblems(true); M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData(); G_SaveGameData(true);
} }
} }

View file

@ -486,6 +486,7 @@ void M_ClearSecrets(void)
gamedata->challengegridwidth = 0; gamedata->challengegridwidth = 0;
gamedata->timesBeaten = 0; gamedata->timesBeaten = 0;
gamedata->crashflags = 0;
// Re-unlock any always unlocked things // Re-unlock any always unlocked things
M_UpdateUnlockablesAndExtraEmblems(false); M_UpdateUnlockablesAndExtraEmblems(false);
@ -592,6 +593,13 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
return gamedata->unlocked[cn->requirement-1]; return gamedata->unlocked[cn->requirement-1];
case UC_CONDITIONSET: // requires condition set x to already be achieved case UC_CONDITIONSET: // requires condition set x to already be achieved
return M_Achieved(cn->requirement-1); return M_Achieved(cn->requirement-1);
case UC_CRASH:
if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY))
{
gamedata->crashflags |= GDCRASH_LOSERCLUB;
return true;
}
return false;
case UC_AND: // Just for string building case UC_AND: // Just for string building
return true; return true;
@ -859,6 +867,10 @@ static const char *M_GetConditionString(condition_t *cn)
gamedata->unlocked[cn->requirement-1] gamedata->unlocked[cn->requirement-1]
? unlockables[cn->requirement-1].name ? unlockables[cn->requirement-1].name
: "???"); : "???");
case UC_CRASH:
if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY))
return "Relaunch \"Dr. Robotnik's Ring Racers\" after a crash";
return NULL;
case UC_AND: case UC_AND:
return "&"; return "&";

View file

@ -43,6 +43,8 @@ typedef enum
UC_UNLOCKABLE, // UNLOCKABLE [unlockable number] UC_UNLOCKABLE, // UNLOCKABLE [unlockable number]
UC_CONDITIONSET, // CONDITIONSET [condition set number] UC_CONDITIONSET, // CONDITIONSET [condition set number]
UC_CRASH, // Hee ho !
UC_AND, // Just for string building UC_AND, // Just for string building
UCRP_REQUIRESPLAYING, // All conditions below this can only be checked if (Playing() && gamestate == GS_LEVEL). UCRP_REQUIRESPLAYING, // All conditions below this can only be checked if (Playing() && gamestate == GS_LEVEL).
@ -177,12 +179,17 @@ typedef enum
#endif #endif
#define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH) #define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH)
#define GDCRASH_LAST 0x01
#define GDCRASH_ANY 0x02
#define GDCRASH_LOSERCLUB 0x04
// GAMEDATA STRUCTURE // GAMEDATA STRUCTURE
// Everything that would get saved in gamedata.dat // Everything that would get saved in gamedata.dat
struct gamedata_t struct gamedata_t
{ {
// WHENEVER OR NOT WE'RE READY TO SAVE // WHENEVER OR NOT WE'RE READY TO SAVE
boolean loaded; boolean loaded;
boolean deferredsave;
// CONDITION SETS ACHIEVED // CONDITION SETS ACHIEVED
boolean achieved[MAXCONDITIONSETS]; boolean achieved[MAXCONDITIONSETS];
@ -204,6 +211,9 @@ struct gamedata_t
// PLAY TIME // PLAY TIME
UINT32 totalplaytime; UINT32 totalplaytime;
UINT32 matchesplayed; UINT32 matchesplayed;
// Funny
UINT8 crashflags;
}; };
extern gamedata_t *gamedata; extern gamedata_t *gamedata;

View file

@ -324,7 +324,7 @@ void M_ChallengesTick(void)
{ {
// All done! Let's save the unlocks we've busted open. // All done! Let's save the unlocks we've busted open.
challengesmenu.pending = false; challengesmenu.pending = false;
G_SaveGameData(); G_SaveGameData(true);
} }
} }
else if (challengesmenu.fade < 5) else if (challengesmenu.fade < 5)

View file

@ -554,7 +554,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
gamedata->collected[special->health-1] = gotcollected = true; gamedata->collected[special->health-1] = gotcollected = true;
if (!M_UpdateUnlockablesAndExtraEmblems(true)) if (!M_UpdateUnlockablesAndExtraEmblems(true))
S_StartSound(NULL, sfx_ncitem); S_StartSound(NULL, sfx_ncitem);
G_SaveGameData(); gamedata->deferredsave = true;
} }
if (netgame) if (netgame)

View file

@ -7444,6 +7444,7 @@ static void P_InitGametype(void)
// Started a game? Move on to the next jam when you go back to the title screen // Started a game? Move on to the next jam when you go back to the title screen
CV_SetValue(&cv_menujam_update, 1); CV_SetValue(&cv_menujam_update, 1);
gamedata->crashflags &= ~GDCRASH_LOSERCLUB;
} }
struct minimapinfo minimapinfo; struct minimapinfo minimapinfo;
@ -7936,7 +7937,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED;
M_UpdateUnlockablesAndExtraEmblems(true); M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData(); G_SaveGameData(true);
} }
G_AddMapToBuffer(gamemap-1); G_AddMapToBuffer(gamemap-1);

View file

@ -3310,7 +3310,7 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
// Unlocked something? // Unlocked something?
if (M_UpdateUnlockablesAndExtraEmblems(true)) if (M_UpdateUnlockablesAndExtraEmblems(true))
{ {
G_SaveGameData(); // only save if unlocked something gamedata->deferredsave = true; // only save if unlocked something
} }
} }
} }

View file

@ -632,8 +632,9 @@ void P_Ticker(boolean run)
ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time;
// TODO would this be laggy with more conditions in play... // TODO would this be laggy with more conditions in play...
if (M_UpdateUnlockablesAndExtraEmblems(true)) if ((!demo.playback && M_UpdateUnlockablesAndExtraEmblems(true))
G_SaveGameData(); || gamedata->deferredsave)
G_SaveGameData(true);
} }
// Keep track of how long they've been playing! // Keep track of how long they've been playing!

View file

@ -1737,7 +1737,7 @@ void I_Quit(void)
if (Playing()) if (Playing())
K_PlayerForfeit(consoleplayer, true); K_PlayerForfeit(consoleplayer, true);
G_SaveGameData(); // Tails 12-08-2002 G_SaveGameData(false); // Tails 12-08-2002 -- undirty your save
//added:16-02-98: when recording a demo, should exit using 'q' key, //added:16-02-98: when recording a demo, should exit using 'q' key,
// but sometimes we forget and use 'F10'.. so save here too. // but sometimes we forget and use 'F10'.. so save here too.
@ -1821,7 +1821,7 @@ void I_Error(const char *error, ...)
if (errorcount == 8) if (errorcount == 8)
{ {
M_SaveConfig(NULL); M_SaveConfig(NULL);
G_SaveGameData(); G_SaveGameData(true);
} }
if (errorcount > 20) if (errorcount > 20)
{ {
@ -1852,7 +1852,7 @@ void I_Error(const char *error, ...)
M_SaveConfig(NULL); // save game config, cvars.. M_SaveConfig(NULL); // save game config, cvars..
D_SaveBan(); // save the ban list D_SaveBan(); // save the ban list
G_SaveGameData(); // Tails 12-08-2002 G_SaveGameData(true); // Tails 12-08-2002
// Shutdown. Here might be other errors. // Shutdown. Here might be other errors.

View file

@ -2983,7 +2983,7 @@ void I_Quit(void)
if (Playing()) if (Playing())
K_PlayerForfeit(consoleplayer, true); K_PlayerForfeit(consoleplayer, true);
G_SaveGameData(); // Tails 12-08-2002 G_SaveGameData(false); // Tails 12-08-2002 -- undirty your save
//added:16-02-98: when recording a demo, should exit using 'q' key, //added:16-02-98: when recording a demo, should exit using 'q' key,
// but sometimes we forget and use 'F10'.. so save here too. // but sometimes we forget and use 'F10'.. so save here too.
@ -3078,7 +3078,7 @@ void I_Error(const char *error, ...)
if (errorcount == 9) if (errorcount == 9)
{ {
M_SaveConfig(NULL); M_SaveConfig(NULL);
G_SaveGameData(); G_SaveGameData(true);
} }
if (errorcount > 20) if (errorcount > 20)
{ {
@ -3142,7 +3142,7 @@ void I_Error(const char *error, ...)
#ifndef NONET #ifndef NONET
D_SaveBan(); // save the ban list D_SaveBan(); // save the ban list
#endif #endif
G_SaveGameData(); // Tails 12-08-2002 G_SaveGameData(true); // Tails 12-08-2002
// Shutdown. Here might be other errors. // Shutdown. Here might be other errors.
if (demorecording) if (demorecording)

View file

@ -607,7 +607,7 @@ void I_Error(const char *error, ...)
if (errorcount == 7) if (errorcount == 7)
{ {
M_SaveConfig(NULL); M_SaveConfig(NULL);
G_SaveGameData(); G_SaveGameData(true);
} }
if (errorcount > 20) if (errorcount > 20)
{ {
@ -636,7 +636,7 @@ void I_Error(const char *error, ...)
if (!errorcount) if (!errorcount)
{ {
M_SaveConfig(NULL); // save game config, cvars.. M_SaveConfig(NULL); // save game config, cvars..
G_SaveGameData(); G_SaveGameData(true);
} }
// save demo, could be useful for debug // save demo, could be useful for debug
@ -726,7 +726,7 @@ void I_Quit(void)
G_CheckDemoStatus(); G_CheckDemoStatus();
M_SaveConfig(NULL); // save game config, cvars.. M_SaveConfig(NULL); // save game config, cvars..
G_SaveGameData(); G_SaveGameData(false); // undirty your save
// maybe it needs that the ticcount continues, // maybe it needs that the ticcount continues,
// or something else that will be finished by I_ShutdownSystem(), // or something else that will be finished by I_ShutdownSystem(),