Allow saving in modified games.

This commit is contained in:
Sally Coolatta 2022-02-27 10:11:55 -05:00
parent e6780f2bee
commit 1b43cdddd5
12 changed files with 97 additions and 84 deletions

View file

@ -1473,6 +1473,12 @@ void D_SRB2Main(void)
//--------------------------------------------------------- CONFIG.CFG //--------------------------------------------------------- CONFIG.CFG
M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()" M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()"
if (M_CheckParm("-gamedata") && M_IsNextParm())
{
// Moved from G_LoadGameData itself, as it would cause some crazy
// confusion issues when loading mods.
strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename);
}
G_LoadGameData(); G_LoadGameData();
#if defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL) #if defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL)

View file

@ -4613,11 +4613,11 @@ static void Fishcake_OnChange(void)
static void Command_Isgamemodified_f(void) static void Command_Isgamemodified_f(void)
{ {
if (savemoddata) if (savemoddata)
CONS_Printf(M_GetText("modifiedgame is true, but you can save emblem and time data in this mod.\n")); CONS_Printf(M_GetText("modifiedgame is true, but you can save time data in this mod.\n"));
else if (modifiedgame) else if (modifiedgame)
CONS_Printf(M_GetText("modifiedgame is true, extras will not be unlocked\n")); CONS_Printf(M_GetText("modifiedgame is true, time data can't be saved\n"));
else else
CONS_Printf(M_GetText("modifiedgame is false, you can unlock extras\n")); CONS_Printf(M_GetText("modifiedgame is false, you can save time data\n"));
} }
static void Command_Cheats_f(void) static void Command_Cheats_f(void)

View file

@ -1575,7 +1575,9 @@ void F_GameEvaluationDrawer(void)
{ {
V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:"); V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:");
if (!(netgame) && (!modifiedgame || savemoddata)) if (netgame)
V_DrawString(8, 96, V_YELLOWMAP, "Multiplayer games\ncan't unlock\nextras!");
else
{ {
INT32 startcoord = 32; INT32 startcoord = 32;
@ -1590,10 +1592,6 @@ void F_GameEvaluationDrawer(void)
} }
} }
} }
else if (netgame)
V_DrawString(8, 96, V_YELLOWMAP, "Multiplayer games\ncan't unlock\nextras!");
else
V_DrawString(8, 96, V_YELLOWMAP, "Modified games\ncan't unlock\nextras!");
} }
#endif #endif
@ -1657,7 +1655,7 @@ void F_GameEvaluationTicker(void)
HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Multiplayer games can't unlock extras!"); HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Multiplayer games can't unlock extras!");
S_StartSound(NULL, sfx_s3k68); S_StartSound(NULL, sfx_s3k68);
} }
else if (!modifiedgame || savemoddata) else
{ {
++timesBeaten; ++timesBeaten;
@ -1672,13 +1670,6 @@ void F_GameEvaluationTicker(void)
G_SaveGameData(); G_SaveGameData();
} }
else
{
HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
HU_SetCEchoDuration(6);
HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Modified games can't unlock extras!");
S_StartSound(NULL, sfx_s3k68);
}
} }
} }

View file

@ -755,7 +755,7 @@ void G_SetGameModified(boolean silent)
savemoddata = false; savemoddata = false;
if (!silent) if (!silent)
CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to record statistics.\n")); CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to play Record Attack.\n"));
// If in record attack recording, cancel it. // If in record attack recording, cancel it.
if (modeattacking) if (modeattacking)
@ -3826,8 +3826,7 @@ static void G_UpdateVisited(void)
{ {
boolean spec = G_IsSpecialStage(gamemap); boolean spec = G_IsSpecialStage(gamemap);
// Update visitation flags? // Update visitation flags?
if ((!modifiedgame || savemoddata) // Not modified if (!multiplayer && !demoplayback && (gametype == GT_COOP) // SP/RA/NiGHTS mode
&& !multiplayer && !demoplayback && (gametype == GT_COOP) // SP/RA/NiGHTS mode
&& !stagefailed) // Did not fail the stage && !stagefailed) // Did not fail the stage
{ {
UINT8 earnedEmblems; UINT8 earnedEmblems;
@ -3895,14 +3894,18 @@ static void G_HandleSaveLevel(void)
remove(liveeventbackup); remove(liveeventbackup);
cursaveslot = 0; cursaveslot = 0;
} }
else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking)) else if (!(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking))
{
G_SaveGame((UINT32)cursaveslot, spstage_start); G_SaveGame((UINT32)cursaveslot, spstage_start);
}
} }
} }
// and doing THIS here means you don't lose your progress if you close the game mid-intermission // 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) else if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking)
&& (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(lastmap+1)) && cursaveslot > 0 && CanSaveLevel(lastmap+1))
{
G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages
}
} }
// //
@ -4178,8 +4181,10 @@ static void G_DoContinued(void)
tokenlist = 0; tokenlist = 0;
token = 0; token = 0;
if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && (!modifiedgame || savemoddata) && cursaveslot > 0) if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && cursaveslot > 0)
{
G_SaveGameOver((UINT32)cursaveslot, true); G_SaveGameOver((UINT32)cursaveslot, true);
}
// Reset # of lives // Reset # of lives
pl->lives = (ultimatemode) ? 1 : startinglivesbalance[numgameovers]; pl->lives = (ultimatemode) ? 1 : startinglivesbalance[numgameovers];
@ -4244,13 +4249,17 @@ void G_LoadGameSettings(void)
S_InitRuntimeSounds(); S_InitRuntimeSounds();
} }
#define GAMEDATA_ID 0x86E4A27C // Change every major version, as usual
#define COMPAT_GAMEDATA_ID 0xFCAFE211 // Can be removed entirely for 2.3
// G_LoadGameData // G_LoadGameData
// Loads the main data file, which stores information such as emblems found, etc. // Loads the main data file, which stores information such as emblems found, etc.
void G_LoadGameData(void) void G_LoadGameData(void)
{ {
size_t length; size_t length;
INT32 i, j; INT32 i, j;
UINT8 modded = false;
UINT32 versionID;
UINT8 rtemp; UINT8 rtemp;
//For records //For records
@ -4261,6 +4270,9 @@ void G_LoadGameData(void)
UINT8 recmares; UINT8 recmares;
INT32 curmare; INT32 curmare;
// Stop saving, until we successfully load it again.
gamedataloaded = false;
// Clear things so previously read gamedata doesn't transfer // Clear things so previously read gamedata doesn't transfer
// to new gamedata // to new gamedata
G_ClearRecords(); // main and nights records G_ClearRecords(); // main and nights records
@ -4268,27 +4280,35 @@ void G_LoadGameData(void)
totalplaytime = 0; // total play time (separate from all) totalplaytime = 0; // total play time (separate from all)
if (M_CheckParm("-nodata")) if (M_CheckParm("-nodata"))
return; // Don't load.
// Allow saving of gamedata beyond this point
gamedataloaded = true;
if (M_CheckParm("-gamedata") && M_IsNextParm())
{ {
strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename); // Don't load at all.
return;
} }
if (M_CheckParm("-resetdata")) if (M_CheckParm("-resetdata"))
return; // Don't load (essentially, reset). {
// Don't load, but do save. (essentially, reset)
gamedataloaded = true;
return;
}
length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &savebuffer); length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &savebuffer);
if (!length) // Aw, no game data. Their loss! if (!length)
{
// No gamedata. We can save a new one.
gamedataloaded = true;
return; return;
}
save_p = savebuffer; save_p = savebuffer;
// Version check // Version check
if (READUINT32(save_p) != 0xFCAFE211) versionID = READUINT32(save_p);
if (versionID != GAMEDATA_ID
#ifdef COMPAT_GAMEDATA_ID // backwards compat behavior
&& versionID != COMPAT_GAMEDATA_ID
#endif
)
{ {
const char *gdfolder = "the SRB2 folder"; const char *gdfolder = "the SRB2 folder";
if (strcmp(srb2home,".")) if (strcmp(srb2home,"."))
@ -4301,13 +4321,26 @@ void G_LoadGameData(void)
totalplaytime = READUINT32(save_p); totalplaytime = READUINT32(save_p);
modded = READUINT8(save_p); #ifdef COMPAT_GAMEDATA_ID
// Ignore for backwards compat, it'll get fixed when saving.
if (versionID == COMPAT_GAMEDATA_ID)
{
// Old files use a UINT8 here.
READUINT8(save_p);
}
else
#endif
{
// Quick & dirty hash for what mod this save file is for.
UINT32 modID = READUINT32(save_p);
UINT32 expectedID = quickncasehash(timeattackfolder, sizeof timeattackfolder);
// Aha! Someone's been screwing with the save file! if (modID != expectedID)
if ((modded && !savemoddata)) {
goto datacorrupt; // Aha! Someone's been screwing with the save file!
else if (modded != true && modded != false) goto datacorrupt;
goto datacorrupt; }
}
// TODO put another cipher on these things? meh, I don't care... // TODO put another cipher on these things? meh, I don't care...
for (i = 0; i < NUMMAPS; i++) for (i = 0; i < NUMMAPS; i++)
@ -4393,6 +4426,12 @@ void G_LoadGameData(void)
Z_Free(savebuffer); Z_Free(savebuffer);
save_p = NULL; save_p = NULL;
// Don't consider loaded until it's a success!
// It used to do this much earlier, but this would cause the gamedata to
// save over itself when it I_Errors from the corruption landing point below,
// which can accidentally delete players' legitimate data if the code ever has any tiny mistakes!
gamedataloaded = true;
// Silent update unlockables in case they're out of sync with conditions // Silent update unlockables in case they're out of sync with conditions
M_SilentUpdateUnlockablesAndEmblems(); M_SilentUpdateUnlockablesAndEmblems();
@ -4432,20 +4471,12 @@ void G_SaveGameData(void)
return; return;
} }
if (modifiedgame && !savemoddata)
{
free(savebuffer);
save_p = savebuffer = NULL;
return;
}
// Version test // Version test
WRITEUINT32(save_p, 0xFCAFE211); WRITEUINT32(save_p, GAMEDATA_ID);
WRITEUINT32(save_p, totalplaytime); WRITEUINT32(save_p, totalplaytime);
btemp = (UINT8)(savemoddata || modifiedgame); WRITEUINT32(save_p, quickncasehash(timeattackfolder, sizeof timeattackfolder));
WRITEUINT8(save_p, btemp);
// TODO put another cipher on these things? meh, I don't care... // TODO put another cipher on these things? meh, I don't care...
for (i = 0; i < NUMMAPS; i++) for (i = 0; i < NUMMAPS; i++)

View file

@ -2994,7 +2994,7 @@ static void HU_DrawCoopOverlay(void)
V_DrawSmallScaledPatch(148, 172, 0, tokenicon); V_DrawSmallScaledPatch(148, 172, 0, tokenicon);
} }
if (LUA_HudEnabled(hud_tabemblems) && (!modifiedgame || savemoddata)) if (LUA_HudEnabled(hud_tabemblems))
{ {
V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(), numemblems+numextraemblems)); V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(), numemblems+numextraemblems));
V_DrawScaledPatch(128, 144 - emblemicon->height/4, 0, emblemicon); V_DrawScaledPatch(128, 144 - emblemicon->height/4, 0, emblemicon);

View file

@ -200,9 +200,6 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
char cechoText[992] = ""; char cechoText[992] = "";
UINT8 cechoLines = 0; UINT8 cechoLines = 0;
if (modifiedgame && !savemoddata)
return false;
M_CheckUnlockConditions(); M_CheckUnlockConditions();
// Go through extra emblems // Go through extra emblems

View file

@ -757,12 +757,12 @@ static menuitem_t SR_EmblemHintMenu[] =
static menuitem_t SP_MainMenu[] = static menuitem_t SP_MainMenu[] =
{ {
// Note: If changing the positions here, also change them in M_SinglePlayerMenu() // Note: If changing the positions here, also change them in M_SinglePlayerMenu()
{IT_CALL | IT_STRING, NULL, "Start Game", M_LoadGame, 76}, {IT_CALL | IT_STRING, NULL, "Start Game", M_LoadGame, 76},
{IT_SECRET, NULL, "Record Attack", M_TimeAttack, 84}, {IT_SECRET, NULL, "Record Attack", M_TimeAttack, 84},
{IT_SECRET, NULL, "NiGHTS Mode", M_NightsAttack, 92}, {IT_SECRET, NULL, "NiGHTS Mode", M_NightsAttack, 92},
{IT_SECRET, NULL, "Marathon Run", M_Marathon, 100}, {IT_SECRET, NULL, "Marathon Run", M_Marathon, 100},
{IT_CALL | IT_STRING, NULL, "Tutorial", M_StartTutorial, 108}, {IT_CALL | IT_STRING, NULL, "Tutorial", M_StartTutorial, 108},
{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics", M_Statistics, 116} {IT_CALL | IT_STRING, NULL, "Statistics", M_Statistics, 116}
}; };
enum enum
@ -3514,9 +3514,11 @@ boolean M_Responder(event_t *ev)
{ {
if (((currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_CALL if (((currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_CALL
|| (currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_SUBMENU) || (currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_SUBMENU)
&& (currentMenu->menuitems[itemOn].status & IT_CALLTYPE)) && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE))
{ {
#ifndef DEVELOP #ifndef DEVELOP
// TODO: Replays are scary, so I left the remaining instances of this alone.
// It'd be nice to get rid of this once and for all though!
if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && modifiedgame && !savemoddata) if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && modifiedgame && !savemoddata)
{ {
S_StartSound(NULL, sfx_skid); S_StartSound(NULL, sfx_skid);
@ -8701,12 +8703,6 @@ static void M_DrawLoad(void)
loadgameoffset = 0; loadgameoffset = 0;
M_DrawLoadGameData(); M_DrawLoadGameData();
if (modifiedgame && !savemoddata)
{
V_DrawCenteredThinString(BASEVIDWIDTH/2, 184, 0, "\x85WARNING: \x80The game is modified.");
V_DrawCenteredThinString(BASEVIDWIDTH/2, 192, 0, "Progress will not be saved.");
}
} }
// //
@ -9008,18 +9004,12 @@ static void M_HandleLoadSave(INT32 choice)
break; break;
case KEY_ENTER: case KEY_ENTER:
if (ultimate_selectable && saveSlotSelected == NOSAVESLOT && !savemoddata && !modifiedgame) if (ultimate_selectable && saveSlotSelected == NOSAVESLOT)
{ {
loadgamescroll = 0; loadgamescroll = 0;
S_StartSound(NULL, sfx_skid); S_StartSound(NULL, sfx_skid);
M_StartMessage("Are you sure you want to play\n\x85ultimate mode\x80? It isn't remotely fair,\nand you don't even get an emblem for it.\n\n(Press 'Y' to confirm)\n",M_SaveGameUltimateResponse,MM_YESNO); M_StartMessage("Are you sure you want to play\n\x85ultimate mode\x80? It isn't remotely fair,\nand you don't even get an emblem for it.\n\n(Press 'Y' to confirm)\n",M_SaveGameUltimateResponse,MM_YESNO);
} }
else if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected-1].lives == -42 && !(!modifiedgame || savemoddata))
{
loadgamescroll = 0;
S_StartSound(NULL, sfx_skid);
M_StartMessage(M_GetText("This cannot be done in a modified game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
}
else if (saveSlotSelected == NOSAVESLOT || savegameinfo[saveSlotSelected-1].lives != -666) // don't allow loading of "bad saves" else if (saveSlotSelected == NOSAVESLOT || savegameinfo[saveSlotSelected-1].lives != -666) // don't allow loading of "bad saves"
{ {
loadgamescroll = 0; loadgamescroll = 0;

View file

@ -2583,7 +2583,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && numgameovers < maxgameovers) if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && numgameovers < maxgameovers)
{ {
numgameovers++; numgameovers++;
if ((!modifiedgame || savemoddata) && cursaveslot > 0) if (cursaveslot > 0)
G_SaveGameOver((UINT32)cursaveslot, (target->player->continues <= 0)); G_SaveGameOver((UINT32)cursaveslot, (target->player->continues <= 0));
} }
} }

View file

@ -11972,11 +11972,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
break; break;
case MT_EMBLEM: case MT_EMBLEM:
if (netgame || multiplayer) if (netgame || multiplayer)
return false; // Single player return false; // Single player (You're next on my shit list)
if (modifiedgame && !savemoddata)
return false; // No cheating!!
break; break;
default: default:
break; break;

View file

@ -7746,7 +7746,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
nextmapoverride = 0; nextmapoverride = 0;
skipstats = 0; skipstats = 0;
if (!(netgame || multiplayer || demoplayback) && (!modifiedgame || savemoddata)) if (!(netgame || multiplayer || demoplayback))
mapvisited[gamemap-1] |= MV_VISITED; mapvisited[gamemap-1] |= MV_VISITED;
else if (netgame || multiplayer) else if (netgame || multiplayer)
mapvisited[gamemap-1] |= MV_MP; // you want to record that you've been there this session, but not permanently mapvisited[gamemap-1] |= MV_MP; // you want to record that you've been there this session, but not permanently
@ -7764,8 +7764,10 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
{ {
// 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 // 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 || marathonmode) if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking || marathonmode)
&& (!modifiedgame || savemoddata) && cursaveslot > 0) && cursaveslot > 0)
{
G_SaveGame((UINT32)cursaveslot, gamemap); G_SaveGame((UINT32)cursaveslot, gamemap);
}
// If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted. // If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted.
} }
lastmaploaded = gamemap; // HAS to be set after saving!! lastmaploaded = gamemap; // HAS to be set after saving!!

View file

@ -2937,7 +2937,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
break; break;
case 441: // Trigger unlockable case 441: // Trigger unlockable
if ((!modifiedgame || savemoddata) && !(netgame || multiplayer)) if (!(netgame || multiplayer))
{ {
INT32 trigid = line->args[0]; INT32 trigid = line->args[0];

View file

@ -1092,7 +1092,7 @@ void Y_Ticker(void)
S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching! S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
// Update when done with tally // Update when done with tally
if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback) if (!(netgame || multiplayer) && !demoplayback)
{ {
if (M_UpdateUnlockablesAndExtraEmblems()) if (M_UpdateUnlockablesAndExtraEmblems())
S_StartSound(NULL, sfx_s3k68); S_StartSound(NULL, sfx_s3k68);
@ -1223,7 +1223,7 @@ void Y_Ticker(void)
S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching! S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
// Update when done with tally // Update when done with tally
if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback) if (!(netgame || multiplayer) && !demoplayback)
{ {
if (M_UpdateUnlockablesAndExtraEmblems()) if (M_UpdateUnlockablesAndExtraEmblems())
S_StartSound(NULL, sfx_s3k68); S_StartSound(NULL, sfx_s3k68);