From e372d348cd5d3f891eb4136f81e757c6c7e1a617 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 01:11:34 +0100 Subject: [PATCH] Live Event Backup Associated menu stuff is rough and unpolished, but it's 1am for the author of this commit. - Activated for this session by either of the following Passwords. - `savetheframes` - `savetheanimals` - The idea is that if Ring Racers were ever ran at a GDQ, runners would swear their alliegance to a particular Metroid routing at the startup stage, because I think it's funny and cute. - A live event backup is created/overwritten on non-cheated grandprix level change. - It's wiped on reaching the podium. - When hitting PLAY on the main menu, if a live event backup exists, make a Menu Message. - If you hit A, turn live event backups on for this session, and load the backup. - If you hit B/X, delete the backup and proceed to character select. - Done this way to avoid cheating a different character to the end of GP. - Unlike the (maybe a little over-)engineering of ringdata.dat, liveringbak.bkp is streamlined as all hells and has very little recovery for differing file lists. --- src/d_main.c | 2 +- src/doomdef.h | 2 +- src/doomstat.h | 4 +- src/g_game.c | 235 +++++-------------------------- src/g_game.h | 9 +- src/k_grandprix.c | 38 +++++ src/k_grandprix.h | 10 ++ src/k_rank.h | 1 + src/m_cheat.c | 32 +++++ src/menus/play-char-select.c | 89 ++++++++++++ src/p_saveg.c | 259 +++++++++++++++++++++++++++-------- src/p_saveg.h | 26 +++- src/p_setup.c | 34 ++--- 13 files changed, 441 insertions(+), 300 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index a4036cbb5..610acbd11 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -982,7 +982,7 @@ void D_ClearState(void) cht_debug = 0; emeralds = 0; memset(&luabanks, 0, sizeof(luabanks)); - lastmaploaded = 0; + lastqueuesaved = 0; // In case someone exits out at the same time they start a time attack run, // reset modeattacking diff --git a/src/doomdef.h b/src/doomdef.h index 0e09a6aa6..b653f926a 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -517,7 +517,7 @@ void CONS_Debug(UINT32 debugflags, const char *fmt, ...) FUNCDEBUG; #include "m_swap.h" // Things that used to be in dstrings.h -#define SAVEGAMENAME "srb2sav" +#define SAVEGAMENAME "ringsav" extern char savegamename[256]; extern char liveeventbackup[256]; diff --git a/src/doomstat.h b/src/doomstat.h index 53071a319..827eec3fb 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -51,8 +51,8 @@ extern UINT8 mapmusrng; extern UINT32 maptol; extern INT32 cursaveslot; -//extern INT16 lastmapsaved; -extern INT16 lastmaploaded; +extern UINT8 lastqueuesaved; +extern boolean makelivebackup; extern UINT8 gamecomplete; // Extra abilities/settings for skins (combinable stuff) diff --git a/src/g_game.c b/src/g_game.c index 8d8610741..9633a2e23 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -123,8 +123,8 @@ precipprops_t precipprops[MAXPRECIP] = preciptype_t precip_freeslot = PRECIP_FIRSTFREESLOT; INT32 cursaveslot = 0; // Auto-save 1p savegame slot -//INT16 lastmapsaved = 0; // Last map we auto-saved at -INT16 lastmaploaded = 0; // Last map the game loaded +UINT8 lastqueuesaved = 0; +boolean makelivebackup = false; UINT8 gamecomplete = 0; marathonmode_t marathonmode = 0; @@ -4018,39 +4018,26 @@ void G_UpdateVisited(void) G_SaveGameData(); } -static boolean CanSaveLevel(INT32 mapnum) +void G_HandleSaveLevel(void) { - // SRB2Kart: No save files yet - (void)mapnum; - return false; -} + if (!grandprixinfo.gp || !grandprixinfo.cup + || splitscreen || netgame + || !(makelivebackup || cursaveslot > 0)) + return; -static void G_HandleSaveLevel(void) -{ - // 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 >= NEXTMAP_SPECIAL) + if (gamestate == GS_CEREMONY && makelivebackup) { - 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 (!usedCheats && !(netgame || multiplayer || ultimatemode || demo.recording || metalrecording || modeattacking)) - G_SaveGame((UINT32)cursaveslot, 0); // TODO when we readd a campaign one day - } - } - // and doing THIS here means you don't lose your progress if you close the game mid-intermission - else if (!(ultimatemode || demo.playback || demo.recording || metalrecording || modeattacking) - && cursaveslot > 0 && CanSaveLevel(lastmap+1)) - { - G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages + if (FIL_FileExists(liveeventbackup)) + remove(liveeventbackup); + return; } + + if (gamestate != GS_LEVEL + || roundqueue.size == 0 + || lastqueuesaved == roundqueue.position) + return; + + G_SaveGame(); } // Next map apparatus @@ -4645,9 +4632,6 @@ static void G_DoContinued(void) // Reset score pl->score = 0; - if (!(netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking) && !usedCheats && cursaveslot > 0) - G_SaveGameOver((UINT32)cursaveslot, true); - // Reset # of lives pl->lives = 3; @@ -5673,7 +5657,7 @@ void G_SaveGameData(void) // G_InitFromSavegame // Can be called by the startup code or the menu task. // -void G_LoadGame(UINT32 slot, INT16 mapoverride) +void G_LoadGame(void) { char vcheck[VERSIONSIZE]; char savename[255]; @@ -5682,15 +5666,10 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // memset savedata to all 0, fixes calling perfectly valid saves corrupt because of bots memset(&savedata, 0, sizeof(savedata)); -#ifdef SAVEGAME_OTHERVERSIONS - //Oh christ. The force load response needs access to mapoverride too... - startonmapnum = mapoverride; -#endif - - if (marathonmode) + if (makelivebackup) strcpy(savename, liveeventbackup); else - sprintf(savename, savegamename, slot); + sprintf(savename, savegamename, cursaveslot); if (P_SaveBufferFromFile(&save, savename) == false) { @@ -5699,22 +5678,11 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) } memset(vcheck, 0, sizeof (vcheck)); - sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); + sprintf(vcheck, (makelivebackup ? "back-up %d" : "version %d"), VERSION); if (strcmp((const char *)save.p, (const char *)vcheck)) { -#ifdef SAVEGAME_OTHERVERSIONS - M_StartMessage("Savegame Load", M_GetText("Save game from different version.\nYou can load this savegame, but\nsaving afterwards will be disabled.\n\nDo you want to continue anyway?\n"), - M_ForceLoadGameResponse, MM_YESNO, NULL, NULL); - //Freeing done by the callback function of the above message -#else - M_ClearMenus(true); // so ESC backs out to title - M_StartMessage("Savegame Load", M_GetText("Save game from different version\n\n"), NULL, MM_NOTHING, NULL, "Return to Menu"); - Command_ExitGame_f(); + M_StartMessage("Savegame Load", va(M_GetText("Save game %s from different version"), savename), NULL, MM_NOTHING, NULL, "Return to Menu"); P_SaveBufferFree(&save); - - // no cheating! - memset(&savedata, 0, sizeof(savedata)); -#endif return; // bad version } save.p += VERSIONSIZE; @@ -5726,39 +5694,18 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // automapactive = false; // dearchive all the modifications - if (!P_LoadGame(&save, mapoverride)) + if (!P_LoadGame(&save)) { - M_ClearMenus(true); // so ESC backs out to title - M_StartMessage("Savegame Load", M_GetText("Savegame file corrupted\n"), NULL, MM_NOTHING, NULL, "Return to Menu"); - Command_ExitGame_f(); + M_StartMessage("Savegame Load", va(M_GetText("Savegame %s corrupted\n"), savename), NULL, MM_NOTHING, NULL, "Return to Menu"); Z_Free(save.buffer); save.p = save.buffer = NULL; - // no cheating! - memset(&savedata, 0, sizeof(savedata)); return; } - if (marathonmode) - { - marathontime = READUINT32(save.p); - marathonmode |= READUINT8(save.p); - } // done P_SaveBufferFree(&save); -// gameaction = ga_nothing; -// G_SetGamestate(GS_LEVEL); - displayplayers[0] = consoleplayer; - multiplayer = false; - splitscreen = 0; - SplitScreen_OnChange(); // not needed? - -// G_DeferedInitNew(sk_medium, G_BuildMapName(1), 0, 0, 1); - if (setsizeneeded) - R_ExecuteSetViewSize(); - - M_ClearMenus(true); CON_ToggleOff(); } @@ -5766,18 +5713,16 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // G_SaveGame // Saves your game. // -void G_SaveGame(UINT32 slot, INT16 mapnum) +void G_SaveGame(void) { boolean saved; char savename[256] = ""; - const char *backup; savebuffer_t save = {0}; - if (marathonmode) + if (makelivebackup) strcpy(savename, liveeventbackup); else - sprintf(savename, savegamename, slot); - backup = va("%s",savename); + sprintf(savename, savegamename, cursaveslot); gameaction = ga_nothing; { @@ -5791,138 +5736,24 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) } memset(name, 0, sizeof (name)); - sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION); + sprintf(name, (makelivebackup ? "back-up %d" : "version %d"), VERSION); WRITEMEM(save.p, name, VERSIONSIZE); - P_SaveGame(&save, mapnum); - if (marathonmode) - { - UINT32 writetime = marathontime; - if (!(marathonmode & MA_INGAME)) - writetime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map - WRITEUINT32(save.p, writetime); - WRITEUINT8(save.p, (marathonmode & ~MA_INIT)); - } + P_SaveGame(&save); length = save.p - save.buffer; - saved = FIL_WriteFile(backup, save.buffer, length); + saved = FIL_WriteFile(savename, save.buffer, length); P_SaveBufferFree(&save); } gameaction = ga_nothing; if (cht_debug && saved) - CONS_Printf(M_GetText("Game saved.\n")); + CONS_Printf(M_GetText("%s saved.\n"), savename); else if (!saved) - CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); + CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s\n"), savename); } -#define BADSAVE goto cleanup; -#define CHECKPOS if (save.p >= save.end) BADSAVE -void G_SaveGameOver(UINT32 slot, boolean modifylives) -{ - boolean saved = false; - size_t length; - char vcheck[VERSIONSIZE]; - char savename[255]; - const char *backup; - savebuffer_t save = {0}; - - if (marathonmode) - strcpy(savename, liveeventbackup); - else - sprintf(savename, savegamename, slot); - backup = va("%s",savename); - - if (P_SaveBufferFromFile(&save, savename) == false) - { - CONS_Printf(M_GetText("Couldn't read file %s\n"), savename); - return; - } - - length = save.size; - - { - char temp[sizeof(timeattackfolder)]; - UINT8 *lives_p; - SINT8 pllives; - - // Version check - memset(vcheck, 0, sizeof (vcheck)); - sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); - if (strcmp((const char *)save.p, (const char *)vcheck)) BADSAVE - save.p += VERSIONSIZE; - - // P_UnArchiveMisc() - (void)READINT16(save.p); - CHECKPOS - (void)READUINT16(save.p); // emeralds - CHECKPOS - READSTRINGN(save.p, temp, sizeof(temp)); // mod it belongs to - if (strcmp(temp, timeattackfolder)) BADSAVE - - // P_UnArchivePlayer() - CHECKPOS - (void)READUINT16(save.p); - CHECKPOS - - WRITEUINT8(save.p, numgameovers); - CHECKPOS - - lives_p = save.p; - pllives = READSINT8(save.p); // lives - CHECKPOS - if (modifylives && pllives < startinglivesbalance[numgameovers]) - { - pllives = startinglivesbalance[numgameovers]; - WRITESINT8(lives_p, pllives); - } - - (void)READINT32(save.p); // Score - CHECKPOS - (void)READINT32(save.p); // continues - - // File end marker check - CHECKPOS - switch (READUINT8(save.p)) - { - case 0xb7: - { - UINT8 i, banksinuse; - CHECKPOS - banksinuse = READUINT8(save.p); - CHECKPOS - if (banksinuse > NUM_LUABANKS) - BADSAVE - for (i = 0; i < banksinuse; i++) - { - (void)READINT32(save.p); - CHECKPOS - } - if (READUINT8(save.p) != 0x1d) - BADSAVE - } - case 0x1d: - break; - default: - BADSAVE - } - - // done - saved = FIL_WriteFile(backup, save.buffer, length); - } - -cleanup: - if (cht_debug && saved) - CONS_Printf(M_GetText("Game saved.\n")); - else if (!saved) - CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); - - P_SaveBufferFree(&save); -} -#undef CHECKPOS -#undef BADSAVE - // // G_DeferedInitNew // Can be called by the startup code or the menu task, diff --git a/src/g_game.h b/src/g_game.h index 65eb78aa3..949bcd3c7 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -187,16 +187,13 @@ void G_StartTitleCard(void); void G_PreLevelTitleCard(void); boolean G_IsTitleCardAvailable(void); -// Can be called by the startup code or M_Responder, calls P_SetupLevel. -void G_LoadGame(UINT32 slot, INT16 mapoverride); +void G_HandleSaveLevel(void); +void G_SaveGame(void); +void G_LoadGame(void); void G_SaveGameData(void); void G_DirtyGameData(void); -void G_SaveGame(UINT32 slot, INT16 mapnum); - -void G_SaveGameOver(UINT32 slot, boolean modifylives); - void G_SetGametype(INT16 gametype); char *G_PrepareGametypeConstant(const char *newgtconst); void G_AddTOL(UINT32 newtol, const char *tolname); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 47b4c22ab..dfbb65669 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -274,6 +274,44 @@ void K_InitGrandPrixBots(void) } } +/*-------------------------------------------------- + void K_LoadGrandPrixSaveGame(void) + + See header file for description. +---------------------------------------------------*/ + +void K_LoadGrandPrixSaveGame(void) +{ + if (splitscreen) + { + // You're not doing splitscreen runs at GDQ. + // We are literally 14 days from code freeze + // and I am not accomodating weird setup this + // second in my last minute QoL feature. + // I will *actually* fight you + return; + } + + players[consoleplayer].lives = savedata.lives; + players[consoleplayer].score = savedata.score; + players[consoleplayer].totalring = savedata.totalring; + + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (savedata.bots[i].valid == false) + continue; + + K_SetBot(i, savedata.bots[i].skin, savedata.bots[i].difficulty, BOT_STYLE_NORMAL); + + players[i].botvars.rival = savedata.bots[i].rival; + players[i].score = savedata.bots[i].score; + + players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); + } +} + /*-------------------------------------------------- static INT16 K_RivalScore(player_t *bot) diff --git a/src/k_grandprix.h b/src/k_grandprix.h index ba19bdb61..34315ba48 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -28,6 +28,7 @@ typedef enum GPEVENT_SPECIAL, } gpEvent_e; +// Please also see P_ArchiveMisc extern struct grandprixinfo { boolean gp; ///< If true, then we are in a Grand Prix. @@ -100,6 +101,15 @@ UINT8 K_GetGPPlayerCount(UINT8 humans); void K_InitGrandPrixBots(void); +/*-------------------------------------------------- + void K_LoadGrandPrixSaveGame(void) + + Handles loading savedata_t info for Grand Prix context. +---------------------------------------------------*/ + +void K_LoadGrandPrixSaveGame(void); + + /*-------------------------------------------------- void K_UpdateGrandPrixBots(void); diff --git a/src/k_rank.h b/src/k_rank.h index 701c01b7c..6d4887218 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -20,6 +20,7 @@ extern "C" { #endif +// Please also see P_ArchiveMisc struct gpRank_t { UINT8 players; diff --git a/src/m_cheat.c b/src/m_cheat.c index a8ac759c0..e4bac635a 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -116,6 +116,26 @@ static UINT8 cheatf_wrongwarp(void) return 1; } +static UINT8 cheatf_backup(void) +{ + /*if (modifiedgame) + return 0;*/ + + makelivebackup = true; + + M_ClearMenus(true); + S_StartSound(0, sfx_kc42); + + M_StartMessage("Live Event Mode", + M_GetText( + "Your progression in GP cups will be.\n" + "backed up whenever you complete a\n" + "round, in case of game crashes.\n" + ), NULL, MM_NOTHING, NULL, NULL); + + return 1; +} + #ifdef DEVELOP static UINT8 cheatf_devmode(void) { @@ -152,6 +172,16 @@ static cheatseq_t cheat_wrongwarp = { (UINT8[]){ SCRAMBLE('b'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), 0xff } }; +static cheatseq_t cheat_backup1 = { + NULL, cheatf_backup, + (UINT8[]){ SCRAMBLE('s'), SCRAMBLE('a'), SCRAMBLE('v'), SCRAMBLE('e'), SCRAMBLE('t'), SCRAMBLE('h'), SCRAMBLE('e'), SCRAMBLE('f'), SCRAMBLE('r'), SCRAMBLE('a'), SCRAMBLE('m'), SCRAMBLE('e'), SCRAMBLE('s'), 0xff } +}; + +static cheatseq_t cheat_backup2 = { + NULL, cheatf_backup, + (UINT8[]){ SCRAMBLE('s'), SCRAMBLE('a'), SCRAMBLE('v'), SCRAMBLE('e'), SCRAMBLE('t'), SCRAMBLE('h'), SCRAMBLE('e'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('i'), SCRAMBLE('m'), SCRAMBLE('a'), SCRAMBLE('l'), SCRAMBLE('s'), 0xff } +}; + #ifdef DEVELOP static cheatseq_t cheat_devmode = { NULL, cheatf_devmode, @@ -163,6 +193,8 @@ cheatseq_t *cheatseqlist[] = { &cheat_warp, &cheat_wrongwarp, + &cheat_backup1, + &cheat_backup2, #ifdef DEVELOP &cheat_devmode, #endif diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 9e7d4ca60..ac208e8c7 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -6,6 +6,8 @@ #include "../s_sound.h" #include "../k_grandprix.h" // K_CanChangeRules #include "../m_cond.h" // Condition Sets +#include "../r_local.h" // SplitScreen_OnChange +#include "../m_misc.h" // FIL_FileExists //#define CHARSELECT_DEVICEDEBUG @@ -484,9 +486,96 @@ void M_CharacterSelectInit(void) setup_page = 0; } + +static void M_MarathonLiveEventBackup(INT32 choice) +{ + if (choice == MA_YES) + { + makelivebackup = true; + G_LoadGame(); + + if (savedata.lives != 0) + { + // Only do this after confirming savegame is ok + const UINT8 ssplayers = 0; + + { + CV_StealthSetValue(&cv_playercolor[0], savedata.skincolor); + + // follower + if (savedata.followerskin < 0 || savedata.followerskin > numfollowers) + CV_StealthSet(&cv_follower[0], "None"); + else + CV_StealthSet(&cv_follower[0], followers[savedata.followerskin].name); + + // finally, call the skin[x] console command. + // This will call SendNameAndColor which will synch everything we sent here and apply the changes! + + CV_StealthSet(&cv_skin[0], skins[savedata.skin].name); + + // ...actually, let's do this last - Skin_OnChange has some return-early occasions + // follower color + CV_SetValue(&cv_followercolor[0], savedata.followercolor); + } + + paused = false; + + S_StopMusicCredit(); + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + const UINT8 entry = lastqueuesaved-1; + + SV_StartSinglePlayerServer(roundqueue.entries[entry].gametype, false); + + D_MapChange( + roundqueue.entries[entry].mapnum + 1, + roundqueue.entries[entry].gametype, + roundqueue.entries[entry].encore, + true, + 1, + false, + roundqueue.entries[lastqueuesaved-1].rankrestricted + ); + + M_ClearMenus(true); + } + } + else if (choice == MA_NO) + { + if (FIL_FileExists(liveeventbackup)) // just in case someone deleted it while we weren't looking. + remove(liveeventbackup); + + M_CharacterSelect(0); + } +} + void M_CharacterSelect(INT32 choice) { (void)choice; + + if (currentMenu == &MainDef + && FIL_FileExists(liveeventbackup)) + { + M_StartMessage( + "Live Event Backup", + "A Live Event Backup was found.\n" + "Do you want to resurrect the last run?\n" + "(Fs in chat if we crashed on stream.)\n", + M_MarathonLiveEventBackup, + MM_YESNO, + "Resume the last run", + "Delete, play another way"); + return; + } + PLAY_CharSelectDef.music = currentMenu->music; PLAY_CharSelectDef.prevMenu = currentMenu; M_SetupNextMenu(&PLAY_CharSelectDef, false); diff --git a/src/p_saveg.c b/src/p_saveg.c index 1e443a962..728ae1f16 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -38,6 +38,7 @@ #include "m_cond.h" // netUnlocked // SRB2Kart +#include "k_grandprix.h" #include "k_battle.h" #include "k_pwrlv.h" #include "k_terrain.h" @@ -82,25 +83,66 @@ typedef enum static inline void P_ArchivePlayer(savebuffer_t *save) { const player_t *player = &players[consoleplayer]; - INT16 skininfo = player->skin; - SINT8 pllives = player->lives; - if (pllives < startinglivesbalance[numgameovers]) // Bump up to 3 lives if the player - pllives = startinglivesbalance[numgameovers]; // has less than that. - WRITEUINT16(save->p, skininfo); - WRITEUINT8(save->p, numgameovers); - WRITESINT8(save->p, pllives); + WRITESINT8(save->p, player->lives); WRITEUINT32(save->p, player->score); + WRITEUINT16(save->p, player->totalring); + + WRITEUINT8(save->p, player->skin); + WRITEUINT16(save->p, player->skincolor); + WRITEINT32(save->p, player->followerskin); + WRITEUINT16(save->p, player->followercolor); + + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false) + continue; + if (players[i].bot == false) + continue; + + WRITEUINT8(save->p, i); + + WRITEUINT8(save->p, players[i].skin); + + WRITEUINT8(save->p, players[i].botvars.difficulty); + WRITEUINT8(save->p, (UINT8)players[i].botvars.rival); + + WRITEUINT32(save->p, players[i].score); + } + + WRITEUINT8(save->p, 0xFE); } -static inline void P_UnArchivePlayer(savebuffer_t *save) +static boolean P_UnArchivePlayer(savebuffer_t *save) { - INT16 skininfo = READUINT16(save->p); - savedata.skin = skininfo; - - savedata.numgameovers = READUINT8(save->p); savedata.lives = READSINT8(save->p); savedata.score = READUINT32(save->p); + savedata.totalring = READUINT16(save->p); + + savedata.skin = READUINT8(save->p); + savedata.skincolor = READUINT16(save->p); + savedata.followerskin = READINT32(save->p); + savedata.followercolor = READUINT16(save->p); + + if (savedata.skin >= numskins) + return false; + + memset(&savedata.bots, 0, sizeof(savedata.bots)); + + UINT8 pid; + + while ((pid = READUINT8(save->p)) < MAXPLAYERS) + { + savedata.bots[pid].valid = true; + savedata.bots[pid].skin = READUINT8(save->p); + savedata.bots[pid].difficulty = READUINT8(save->p); + savedata.bots[pid].rival = (boolean)READUINT8(save->p); + savedata.bots[pid].score = READUINT32(save->p); + } + + return (pid == 0xFE); } static void P_NetArchivePlayers(savebuffer_t *save) @@ -1039,6 +1081,9 @@ static void P_NetUnArchiveRoundQueue(savebuffer_t *save) roundqueue.position = READUINT8(save->p); roundqueue.size = READUINT8(save->p); + if (roundqueue.size > ROUNDQUEUE_MAX) + I_Error("Bad $$$.sav at illegitimate roundqueue size"); + roundqueue.roundnum = READUINT8(save->p); for (i = 0; i < roundqueue.size; i++) @@ -5257,55 +5302,148 @@ static void P_NetUnArchiveSpecials(savebuffer_t *save) // ======================================================================= // Misc // ======================================================================= -static inline void P_ArchiveMisc(savebuffer_t *save, INT16 mapnum) +static inline void P_ArchiveMisc(savebuffer_t *save) { - //lastmapsaved = mapnum; - lastmaploaded = mapnum; + UINT8 i; - if (gamecomplete) - mapnum |= 8192; - - WRITEINT16(save->p, mapnum); - WRITEUINT16(save->p, emeralds+357); WRITESTRINGN(save->p, timeattackfolder, sizeof(timeattackfolder)); + + WRITEUINT8(save->p, grandprixinfo.gamespeed); + WRITEUINT8(save->p, (UINT8)grandprixinfo.encore); + WRITEUINT8(save->p, (UINT8)grandprixinfo.masterbots); + + WRITEUINT16(save->p, grandprixinfo.cup->id); + + { + WRITEUINT8(save->p, grandprixinfo.rank.players); + WRITEUINT8(save->p, grandprixinfo.rank.totalPlayers); + + WRITEUINT8(save->p, grandprixinfo.rank.position); + WRITEUINT8(save->p, grandprixinfo.rank.skin); + + WRITEUINT32(save->p, grandprixinfo.rank.winPoints); + WRITEUINT32(save->p, grandprixinfo.rank.totalPoints); + + WRITEUINT32(save->p, grandprixinfo.rank.laps); + WRITEUINT32(save->p, grandprixinfo.rank.totalLaps); + + WRITEUINT32(save->p, grandprixinfo.rank.continuesUsed); + + WRITEUINT32(save->p, grandprixinfo.rank.prisons); + WRITEUINT32(save->p, grandprixinfo.rank.totalPrisons); + + WRITEUINT32(save->p, grandprixinfo.rank.rings); + WRITEUINT32(save->p, grandprixinfo.rank.totalRings); + + WRITEUINT8(save->p, (UINT8)grandprixinfo.rank.specialWon); + } + + WRITEUINT8(save->p, roundqueue.position); + WRITEUINT8(save->p, roundqueue.size); + WRITEUINT8(save->p, roundqueue.roundnum); + + for (i = 0; i < roundqueue.size; i++) + { + WRITEUINT16(save->p, roundqueue.entries[i].mapnum); + WRITEUINT8(save->p, roundqueue.entries[i].gametype); + WRITEUINT8(save->p, (UINT8)roundqueue.entries[i].encore); + WRITEUINT8(save->p, (UINT8)roundqueue.entries[i].rankrestricted); + } + + WRITEUINT8(save->p, (marathonmode & ~MA_INIT)); + + UINT32 writetime = marathontime; + if (!(marathonmode & MA_INGAME)) + writetime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map + WRITEUINT32(save->p, writetime); } -static inline void P_UnArchiveSPGame(savebuffer_t *save, INT16 mapoverride) +static boolean P_UnArchiveSPGame(savebuffer_t *save) { + UINT8 i; char testname[sizeof(timeattackfolder)]; - gamemap = READINT16(save->p); - - if (mapoverride != 0) - { - gamemap = mapoverride; - gamecomplete = 1; - } - else - gamecomplete = 0; - - // gamemap changed; we assume that its map header is always valid, - // so make it so - if (!gamemap || gamemap > nummapheaders || !mapheaderinfo[gamemap-1]) - I_Error("P_UnArchiveSPGame: Internal map ID %d not found (nummapheaders = %d)", gamemap-1, nummapheaders); - - //lastmapsaved = gamemap; - lastmaploaded = gamemap; - - savedata.emeralds = READUINT16(save->p)-357; - READSTRINGN(save->p, testname, sizeof(testname)); if (strcmp(testname, timeattackfolder)) { - if (modifiedgame) - I_Error("Save game not for this modification."); - else - I_Error("This save file is for a particular mod, it cannot be used with the regular game."); + return false; } - memset(playeringame, 0, sizeof(*playeringame)); - playeringame[consoleplayer] = true; + // TODO do not work off grandprixinfo/roundqueue directly + // This is only strictly necessary if we ever re-add a save + // select screen or something, for live event backup only + // it's *fine* and, more importantly, shippable + + memset(&grandprixinfo, 0, sizeof(grandprixinfo)); + + grandprixinfo.gp = true; + + grandprixinfo.gamespeed = READUINT8(save->p); + grandprixinfo.encore = (boolean)READUINT8(save->p); + grandprixinfo.masterbots = (boolean)READUINT8(save->p); + + UINT16 cupid = READUINT16(save->p); + grandprixinfo.cup = kartcupheaders; + while (grandprixinfo.cup) + { + if (grandprixinfo.cup->id == cupid) + break; + grandprixinfo.cup = grandprixinfo.cup->next; + } + + if (!grandprixinfo.cup) + return false; + + { + grandprixinfo.rank.players = READUINT8(save->p); + grandprixinfo.rank.totalPlayers = READUINT8(save->p); + + grandprixinfo.rank.position = READUINT8(save->p); + grandprixinfo.rank.skin = READUINT8(save->p); + + grandprixinfo.rank.winPoints = READUINT32(save->p); + grandprixinfo.rank.totalPoints = READUINT32(save->p); + + grandprixinfo.rank.laps = READUINT32(save->p); + grandprixinfo.rank.totalLaps = READUINT32(save->p); + + grandprixinfo.rank.continuesUsed = READUINT32(save->p); + + grandprixinfo.rank.prisons = READUINT32(save->p); + grandprixinfo.rank.totalPrisons = READUINT32(save->p); + + grandprixinfo.rank.rings = READUINT32(save->p); + grandprixinfo.rank.totalRings = READUINT32(save->p); + + grandprixinfo.rank.specialWon = (boolean)READUINT8(save->p); + } + + memset(&roundqueue, 0, sizeof(roundqueue)); + + roundqueue.position = READUINT8(save->p); + roundqueue.size = READUINT8(save->p); + if (roundqueue.size > ROUNDQUEUE_MAX + || roundqueue.position > roundqueue.size) + return false; + + roundqueue.roundnum = READUINT8(save->p); + + for (i = 0; i < roundqueue.size; i++) + { + roundqueue.entries[i].mapnum = READUINT16(save->p); + if (roundqueue.entries[i].mapnum >= nummapheaders) + return false; + + roundqueue.entries[i].gametype = READUINT8(save->p); + roundqueue.entries[i].encore = (boolean)READUINT8(save->p); + roundqueue.entries[i].rankrestricted = (boolean)READUINT8(save->p); + } + + marathonmode = READUINT8(save->p); + marathontime = READUINT32(save->p); + + return true; } static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) @@ -5718,9 +5856,9 @@ static inline void P_NetUnArchiveRNG(savebuffer_t *save) } } -void P_SaveGame(savebuffer_t *save, INT16 mapnum) +void P_SaveGame(savebuffer_t *save) { - P_ArchiveMisc(save, mapnum); + P_ArchiveMisc(save); P_ArchivePlayer(save); P_ArchiveLuabanksAndConsistency(save); } @@ -5775,7 +5913,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) P_ArchiveLuabanksAndConsistency(save); } -boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride) +boolean P_LoadGame(savebuffer_t *save) { if (gamestate == GS_INTERMISSION) Y_EndIntermission(); @@ -5783,17 +5921,26 @@ boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride) Y_EndVote(); G_SetGamestate(GS_NULL); // should be changed in P_UnArchiveMisc - P_UnArchiveSPGame(save, mapoverride); - P_UnArchivePlayer(save); + if (!P_UnArchiveSPGame(save)) + goto badloadgame; + if (!P_UnArchivePlayer(save)) + goto badloadgame; if (!P_UnArchiveLuabanksAndConsistency(save)) - return false; + goto badloadgame; - // Only do this after confirming savegame is ok - G_DeferedInitNew(false, gamemap, savedata.skin, 0, true); - COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this + lastqueuesaved = roundqueue.position; return true; + +badloadgame: + // these are the side effects of P_UnarchiveSPGame + savedata.lives = 0; + roundqueue.size = 0; + grandprixinfo.gp = false; + marathonmode = 0; + + return false; } boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) diff --git a/src/p_saveg.h b/src/p_saveg.h index 30eeaef66..b8e5bcf0d 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -31,20 +31,34 @@ extern "C" { // Persistent storage/archiving. // These are the load / save game routines. -void P_SaveGame(savebuffer_t *save, INT16 mapnum); +void P_SaveGame(savebuffer_t *save); void P_SaveNetGame(savebuffer_t *save, boolean resending); -boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride); +boolean P_LoadGame(savebuffer_t *save); boolean P_LoadNetGame(savebuffer_t *save, boolean reloading); mobj_t *P_FindNewPosition(UINT32 oldposition); +struct savedata_bot_s +{ + boolean valid; + UINT8 skin; + UINT8 difficulty; + boolean rival; + UINT32 score; +}; + struct savedata_t { + UINT32 score; + SINT8 lives; + UINT16 totalring; + UINT8 skin; - INT32 score; - INT32 lives; - UINT16 emeralds; - UINT8 numgameovers; + UINT16 skincolor; + INT32 followerskin; + UINT16 followercolor; + + struct savedata_bot_s bots[MAXPLAYERS]; }; extern savedata_t savedata; diff --git a/src/p_setup.c b/src/p_setup.c index c9ca10345..16e58a109 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7734,7 +7734,12 @@ static void P_InitGametype(void) if (grandprixinfo.gp == true) { - if (grandprixinfo.initalize == true) + if (savedata.lives > 0) + { + K_LoadGrandPrixSaveGame(); + savedata.lives = 0; + } + else if (grandprixinfo.initalize == true) { K_InitGrandPrixRank(&grandprixinfo.rank); K_InitGrandPrixBots(); @@ -8152,15 +8157,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) R_InitMobjInterpolators(); P_InitCachedActions(); - if (!fromnetsave && savedata.lives > 0) - { - numgameovers = savedata.numgameovers; - players[consoleplayer].lives = savedata.lives; - players[consoleplayer].score = savedata.score; - emeralds = savedata.emeralds; - savedata.lives = 0; - } - // internal game map maplumpname = mapheaderinfo[gamemap-1]->lumpname; lastloadedmaplumpnum = mapheaderinfo[gamemap-1]->lumpnum; @@ -8311,22 +8307,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) P_MapEnd(); // tm.thing is no longer needed from this point onwards - // Took me 3 hours to figure out why my progression kept on getting overwritten with the titlemap... - if (gamestate == GS_LEVEL) - { - 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 || demo.playback || demo.recording || metalrecording || modeattacking || marathonmode) - && !usedCheats && 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) { INT32 buf = gametic % BACKUPTICS; @@ -8382,6 +8362,8 @@ void P_PostLoadLevel(void) P_RunCachedActions(); + G_HandleSaveLevel(); + if (marathonmode & MA_INGAME) { marathonmode &= ~MA_INIT;