diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 67718cc4b..2849589ba 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -653,7 +653,6 @@ typedef enum static void GetPackets(void); static cl_mode_t cl_mode = CL_SEARCHING; -static cl_mode_t cl_requestmode = CL_ABORTED; #ifdef HAVE_CURL char http_source[MAX_MIRROR_LENGTH]; @@ -1671,43 +1670,37 @@ void CL_UpdateServerList (void) SendAskInfo(BROADCASTADDR); } -static boolean M_ConfirmConnect(void) +static void M_ConfirmConnect(INT32 choice) { - if (G_PlayerInputDown(0, gc_b, 1) || G_PlayerInputDown(0, gc_x, 1) || G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) - { - cl_requestmode = CL_ABORTED; - return true; - } - - if (G_PlayerInputDown(0, gc_a, 1) || G_GetDeviceGameKeyDownArray(0)[KEY_ENTER]) + if (choice == MA_YES) { if (totalfilesrequestednum > 0) { -#ifdef HAVE_CURL + #ifdef HAVE_CURL if (http_source[0] == '\0' || curl_failedwebdownload) -#endif + #endif { if (CL_SendFileRequest()) { - cl_requestmode = CL_DOWNLOADFILES; + cl_mode = CL_DOWNLOADFILES; } else { - cl_requestmode = CL_DOWNLOADFAILED; + cl_mode = CL_DOWNLOADFAILED; } } -#ifdef HAVE_CURL + #ifdef HAVE_CURL else - cl_requestmode = CL_PREPAREHTTPFILES; -#endif + cl_mode = CL_PREPAREHTTPFILES; + #endif } else - cl_requestmode = CL_LOADFILES; + cl_mode = CL_LOADFILES; - return true; + return; } - return false; + cl_mode = CL_ABORTED; } static boolean CL_FinishedFileList(void) @@ -1759,7 +1752,7 @@ static boolean CL_FinishedFileList(void) "This server is full!\n" "\n" "You may load server addons (if any), and wait for a slot.\n" - ), NULL, MM_NOTHING, "Continue", "Back to Menu"); + ), &M_ConfirmConnect, MM_YESNO, "Continue", "Back to Menu"); cl_mode = CL_CONFIRMCONNECT; } else @@ -1822,13 +1815,13 @@ static boolean CL_FinishedFileList(void) "\n" "You may download, load server addons,\n" "and wait for a slot.\n" - ), downloadsize), NULL, MM_NOTHING, "Continue", "Back to Menu"); + ), downloadsize), &M_ConfirmConnect, MM_YESNO, "Continue", "Back to Menu"); else M_StartMessage("Server Connection", va(M_GetText( "Download of %s additional content\n" "is required to join.\n" - ), downloadsize), NULL, MM_NOTHING, "Continue", "Back to Menu"); + ), downloadsize), &M_ConfirmConnect, MM_YESNO, "Continue", "Back to Menu"); Z_Free(downloadsize); cl_mode = CL_CONFIRMCONNECT; @@ -1969,6 +1962,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic { boolean waitmore; INT32 i; + const UINT8 pid = 0; switch (cl_mode) { @@ -2168,26 +2162,27 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic G_MapEventsToControls(&events[eventtail]); } +#ifdef HAVE_THREADS + I_lock_mutex(&k_menu_mutex); +#endif + M_UpdateMenuCMD(0, true); + if (cl_mode == CL_CONFIRMCONNECT) { -#ifdef HAVE_THREADS - I_lock_mutex(&k_menu_mutex); -#endif - if (M_MenuMessageTick() && M_ConfirmConnect()) - M_StopMessage(0); - else if (menumessage.active == false) - cl_mode = cl_requestmode; -#ifdef HAVE_THREADS - I_unlock_mutex(k_menu_mutex); -#endif + if (menumessage.active) + M_HandleMenuMessage(); } else { - if (G_PlayerInputDown(0, gc_b, 1) - || G_PlayerInputDown(0, gc_x, 1) - || G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) + if (M_MenuBackPressed(pid)) cl_mode = CL_ABORTED; } + + M_ScreenshotTicker(); + +#ifdef HAVE_THREADS + I_unlock_mutex(k_menu_mutex); +#endif } if (cl_mode == CL_ABORTED) @@ -2265,7 +2260,6 @@ static void CL_ConnectToServer(void) lastfilenum = -1; cl_mode = CL_SEARCHING; - cl_requestmode = CL_ABORTED; // sane default // Don't get a corrupt savegame error because tmpsave already exists if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1) diff --git a/src/d_main.c b/src/d_main.c index a4036cbb5..aa79e6584 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -140,7 +140,7 @@ UINT16 numskincolors; menucolor_t *menucolorhead, *menucolortail; char savegamename[256]; -char liveeventbackup[256]; +char gpbackup[256]; char srb2home[256] = "."; char srb2path[256] = "."; @@ -295,7 +295,7 @@ void D_ProcessEvents(void) // Update menu CMD for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - M_UpdateMenuCMD(i); + M_UpdateMenuCMD(i, false); } } @@ -982,7 +982,6 @@ void D_ClearState(void) cht_debug = 0; emeralds = 0; memset(&luabanks, 0, sizeof(luabanks)); - lastmaploaded = 0; // In case someone exits out at the same time they start a time attack run, // reset modeattacking @@ -1327,7 +1326,7 @@ void D_SRB2Main(void) // default savegame strcpy(savegamename, SAVEGAMENAME"%u.ssg"); - strcpy(liveeventbackup, "live"SAVEGAMENAME".bkp"); // intentionally not ending with .ssg + strcpy(gpbackup, "gp"SAVEGAMENAME".bkp"); // intentionally not ending with .ssg // Init the joined IP table for quick rejoining of past games. M_InitJoinedIPArray(); @@ -1358,7 +1357,7 @@ void D_SRB2Main(void) // can't use sprintf since there is %u in savegamename strcatbf(savegamename, srb2home, PATHSEP); - strcatbf(liveeventbackup, srb2home, PATHSEP); + strcatbf(gpbackup, srb2home, PATHSEP); snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", srb2home); #else // DEFAULTDIR @@ -1370,7 +1369,7 @@ void D_SRB2Main(void) // can't use sprintf since there is %u in savegamename strcatbf(savegamename, userhome, PATHSEP); - strcatbf(liveeventbackup, userhome, PATHSEP); + strcatbf(gpbackup, userhome, PATHSEP); snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", userhome); #endif // DEFAULTDIR diff --git a/src/deh_soc.c b/src/deh_soc.c index d0ca37e45..eb30c33ed 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2997,8 +2997,8 @@ void readmaincfg(MYFILE *f, boolean mainfile) // can't use sprintf since there is %u in savegamename strcatbf(savegamename, srb2home, PATHSEP); - strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder)); - strcatbf(liveeventbackup, srb2home, PATHSEP); + strcpy(gpbackup, va("gp%s.bkp", timeattackfolder)); + strcatbf(gpbackup, srb2home, PATHSEP); refreshdirmenu |= REFRESHDIR_GAMEDATA; gamedataadded = true; diff --git a/src/doomdef.h b/src/doomdef.h index 0e09a6aa6..8eac43761 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -517,9 +517,9 @@ 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]; +extern char gpbackup[256]; // m_misc.h #ifdef GETTEXT diff --git a/src/doomstat.h b/src/doomstat.h index 53071a319..e6787116e 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -51,8 +51,6 @@ extern UINT8 mapmusrng; extern UINT32 maptol; extern INT32 cursaveslot; -//extern INT16 lastmapsaved; -extern INT16 lastmaploaded; extern UINT8 gamecomplete; // Extra abilities/settings for skins (combinable stuff) diff --git a/src/f_finale.c b/src/f_finale.c index 14011b1c9..a6f4fb900 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -2131,8 +2131,10 @@ void F_TitleScreenTicker(boolean run) // Now start the music S_ChangeMusicInternal("_title", looptitle); } - else if (menumessage.fadetimer < 9) - menumessage.fadetimer++; + else if (menumessage.active) + { + M_MenuMessageTick(); + } finalecount++; } diff --git a/src/g_game.c b/src/g_game.c index 8d8610741..1b3dfa770 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -123,8 +123,6 @@ 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 gamecomplete = 0; marathonmode_t marathonmode = 0; @@ -3351,6 +3349,7 @@ void G_ExitLevel(void) else { // Back to the menu with you. + G_HandleSaveLevel(true); D_QuitNetGame(); CL_Reset(); D_ClearState(); @@ -4018,39 +4017,29 @@ void G_UpdateVisited(void) G_SaveGameData(); } -static boolean CanSaveLevel(INT32 mapnum) +void G_HandleSaveLevel(boolean removecondition) { - // SRB2Kart: No save files yet - (void)mapnum; - return false; -} + if (!grandprixinfo.gp || !grandprixinfo.cup + || splitscreen || netgame) + 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 (!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 (removecondition) + goto doremove; + + if (gamestate != GS_LEVEL + || roundqueue.size == 0) + return; + + if (roundqueue.position == 1 + || players[consoleplayer].lives <= 1) // because a life is lost on reload + goto doremove; + + G_SaveGame(); + return; + +doremove: + if (FIL_FileExists(gpbackup)) + remove(gpbackup); } // Next map apparatus @@ -4157,7 +4146,7 @@ void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencor } } -static void G_GetNextMap(void) +void G_GetNextMap(void) { INT32 i; boolean setalready = false; @@ -4540,7 +4529,6 @@ void G_AfterIntermission(void) if (gamestate != GS_VOTING) { G_GetNextMap(); - G_HandleSaveLevel(); } if ((grandprixinfo.gp == true) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. @@ -4645,9 +4633,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,24 +5658,23 @@ void G_SaveGameData(void) // G_InitFromSavegame // Can be called by the startup code or the menu task. // -void G_LoadGame(UINT32 slot, INT16 mapoverride) + +#define SAV_VERSIONMINOR 2 + +void G_LoadGame(void) { - char vcheck[VERSIONSIZE]; + char vcheck[VERSIONSIZE+1]; char savename[255]; + UINT8 versionMinor; savebuffer_t save = {0}; // 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) - strcpy(savename, liveeventbackup); - else - sprintf(savename, savegamename, slot); + //if (makelivebackup) + strcpy(savename, gpbackup); + //else + //sprintf(savename, savegamename, cursaveslot); if (P_SaveBufferFromFile(&save, savename) == false) { @@ -5698,23 +5682,16 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) return; } - memset(vcheck, 0, sizeof (vcheck)); - sprintf(vcheck, (marathonmode ? "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(); - P_SaveBufferFree(&save); + versionMinor = READUINT8(save.p); - // no cheating! - memset(&savedata, 0, sizeof(savedata)); -#endif + memset(vcheck, 0, sizeof (vcheck)); + sprintf(vcheck, "version %d", VERSION); + + if (versionMinor != SAV_VERSIONMINOR + || memcmp(save.p, vcheck, VERSIONSIZE)) + { + M_StartMessage("Savegame Load", va(M_GetText("Savegame %s is from\na different version."), savename), NULL, MM_NOTHING, NULL, NULL); + P_SaveBufferFree(&save); return; // bad version } save.p += VERSIONSIZE; @@ -5726,62 +5703,91 @@ 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 could not be loaded.\n" + "Check the console log for more info.\n"), savename), NULL, MM_NOTHING, NULL, NULL); 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(); } +void G_GetBackupCupData(boolean actuallygetdata) +{ + if (actuallygetdata == false) + { + cupsavedata.cup = NULL; + return; + } + + char vcheck[VERSIONSIZE+1]; + char savename[255]; + UINT8 versionMinor; + savebuffer_t save = {0}; + + //if (makelivebackup) + strcpy(savename, gpbackup); + //else + //sprintf(savename, savegamename, cursaveslot); + + if (P_SaveBufferFromFile(&save, savename) == false) + { + cupsavedata.cup = NULL; + return; + } + + versionMinor = READUINT8(save.p); + + memset(vcheck, 0, sizeof (vcheck)); + sprintf(vcheck, "version %d", VERSION); + + if (versionMinor != SAV_VERSIONMINOR + || memcmp(save.p, vcheck, VERSIONSIZE)) + { + cupsavedata.cup = NULL; + P_SaveBufferFree(&save); + return; // bad version + } + save.p += VERSIONSIZE; + + P_GetBackupCupData(&save); + + if (cv_dummygpdifficulty.value != cupsavedata.difficulty + || !!cv_dummygpencore.value != cupsavedata.encore) + { + // Still not compatible. + cupsavedata.cup = NULL; + } + + // done + P_SaveBufferFree(&save); +} + // // 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) - strcpy(savename, liveeventbackup); - else - sprintf(savename, savegamename, slot); - backup = va("%s",savename); + //if (makelivebackup) + strcpy(savename, gpbackup); + //else + //sprintf(savename, savegamename, cursaveslot); gameaction = ga_nothing; { - char name[VERSIONSIZE]; + char name[VERSIONSIZE+1]; size_t length; if (P_SaveBufferAlloc(&save, SAVEGAMESIZE) == false) @@ -5790,139 +5796,27 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) return; } + WRITEUINT8(save.p, SAV_VERSIONMINOR); + memset(name, 0, sizeof (name)); - sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION); + sprintf(name, "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..71dd4ada8 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -187,16 +187,14 @@ 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(boolean removecondition); +void G_SaveGame(void); +void G_LoadGame(void); +void G_GetBackupCupData(boolean actuallygetdata); 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); @@ -209,6 +207,7 @@ boolean G_GametypeHasSpectators(void); INT16 G_SometimesGetDifferentEncore(void); void G_ExitLevel(void); void G_NextLevel(void); +void G_GetNextMap(void); void G_Continue(void); void G_UseContinue(void); void G_AfterIntermission(void); 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_menu.h b/src/k_menu.h index bca035b59..7eefd06ea 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -19,6 +19,7 @@ #include "command.h" #include "doomstat.h" // MAXSPLITSCREENPLAYERS #include "g_demo.h" //menudemo_t +#include "p_saveg.h" // savedata_cup_t #include "k_profiles.h" // profile data & functions #include "g_input.h" // gc_ #include "i_threads.h" @@ -517,10 +518,11 @@ typedef enum } manswer_e; #define MAXMENUMESSAGE 256 +#define MENUMESSAGECLOSE 2 extern struct menumessage_s { boolean active; - boolean closing; + UINT8 closing; INT32 flags; // MM_ const char *header; @@ -533,6 +535,7 @@ extern struct menumessage_s void (*routine)(INT32 choice); // Normal routine //void (*eroutine)(event_t *ev); // Event routine (MM_EVENTHANDLER) + INT32 answer; const char *defaultstr; const char *confirmstr; @@ -599,7 +602,7 @@ void M_SetMenuDelay(UINT8 i); void M_SortServerList(void); -void M_UpdateMenuCMD(UINT8 i); +void M_UpdateMenuCMD(UINT8 i, boolean bailrequired); boolean M_Responder(event_t *ev); boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt); boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b08e251b0..d01bd593d 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -606,15 +606,15 @@ static void M_DrawMenuTyping(void) // Draw the message popup submenu void M_DrawMenuMessage(void) { + if (!menumessage.active) + return; + INT32 x = (BASEVIDWIDTH - menumessage.x)/2; INT32 y = (BASEVIDHEIGHT - menumessage.y)/2 + floor(pow(2, (double)(9 - menumessage.fadetimer))); size_t i, start = 0; char string[MAXMENUMESSAGE]; const char *msg = menumessage.message; - if (!menumessage.active) - return; - V_DrawFadeScreen(31, menumessage.fadetimer); V_DrawFill(0, y, BASEVIDWIDTH, menumessage.y, 159); @@ -629,25 +629,64 @@ void M_DrawMenuMessage(void) INT32 workx = x + menumessage.x; INT32 worky = y + menumessage.y; + boolean push; + + if (menumessage.closing) + push = (menumessage.answer != MA_YES); + else + { + const UINT8 anim_duration = 16; + push = ((menumessage.timer % (anim_duration * 2)) < anim_duration); + } + workx -= V_ThinStringWidth(menumessage.defaultstr, V_6WIDTHSPACE|V_ALLOWLOWERCASE); - V_DrawThinString(workx, worky + 1, V_6WIDTHSPACE|V_ALLOWLOWERCASE, menumessage.defaultstr); + V_DrawThinString( + workx, worky + 1, + V_6WIDTHSPACE|V_ALLOWLOWERCASE + | ((push && (menumessage.closing & MENUMESSAGECLOSE)) ? highlightflags : 0), + menumessage.defaultstr + ); + + workx -= 2; workx -= SHORT(kp_button_x[1][0]->width); - K_drawButtonAnim(workx, worky, 0, kp_button_x[1], menumessage.timer); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_x[1], + push + ); workx -= SHORT(kp_button_b[1][0]->width); - K_drawButtonAnim(workx, worky, 0, kp_button_b[1], menumessage.timer); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_b[1], + push + ); if (menumessage.confirmstr) { workx -= 12; + if (menumessage.closing) + push = !push; + workx -= V_ThinStringWidth(menumessage.confirmstr, V_6WIDTHSPACE|V_ALLOWLOWERCASE); - V_DrawThinString(workx, worky + 1, V_6WIDTHSPACE|V_ALLOWLOWERCASE, menumessage.confirmstr); + V_DrawThinString( + workx, worky + 1, + V_6WIDTHSPACE|V_ALLOWLOWERCASE + | ((push && (menumessage.closing & MENUMESSAGECLOSE)) ? highlightflags : 0), + menumessage.confirmstr + ); + + workx -= 2; } workx -= SHORT(kp_button_a[1][0]->width); - K_drawButtonAnim(workx, worky, 0, kp_button_a[1], menumessage.timer); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_a[1], + push + ); } x -= 4; @@ -2329,6 +2368,7 @@ static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch) void M_DrawCupSelect(void) { UINT8 i, j, temp = 0; + INT16 x, y; UINT8 *colormap = NULL; cupwindata_t *windata = NULL; levelsearch_t templevelsearch = levellist.levelsearch; // full copy @@ -2339,7 +2379,6 @@ void M_DrawCupSelect(void) { size_t id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); patch_t *patch = NULL; - INT16 x, y; INT16 icony = 7; char status = 'A'; char monitor; @@ -2424,6 +2463,13 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(templevelsearch.cup->icon, PU_CACHE)); V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); + if (cupgrid.grandprix == true + && templevelsearch.cup == cupsavedata.cup + && id != CUPMENU_CURSORID) + { + V_DrawScaledPatch(x + 32, y + 32, 0, W_CachePatchName("CUPBKUP1", PU_CACHE)); + } + if (!windata) ; else if (windata->best_placement != 0) @@ -2523,17 +2569,38 @@ void M_DrawCupSelect(void) } } - V_DrawScaledPatch(14 + (cupgrid.x*42) - 4, - 20 + (cupgrid.y*44) - 1 - (24*menutransition.tics), - 0, W_CachePatchName("CUPCURS", PU_CACHE) - ); + x = 14 + (cupgrid.x*42); + y = 20 + (cupgrid.y*44) - (30*menutransition.tics); + + V_DrawScaledPatch(x - 4, y - 1, 0, W_CachePatchName("CUPCURS", PU_CACHE)); templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; + if (cupgrid.grandprix == true + && templevelsearch.cup != NULL + && templevelsearch.cup == cupsavedata.cup) + { + V_DrawScaledPatch(x + 32, y + 32, 0, W_CachePatchName("CUPBKUP2", PU_CACHE)); + } + V_DrawFill(0, 146 + (24*menutransition.tics), BASEVIDWIDTH, 54, 31); M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch); M_DrawCupTitle(120 - (24*menutransition.tics), &templevelsearch); + + if (cupgrid.numpages > 1) + { + x = 3 - (skullAnimCounter/5); + y = 20 + (44 - 1) - (30*menutransition.tics); + + patch_t *cuparrow = W_CachePatchName("CUPARROW", PU_CACHE); + + if (cupgrid.pageno != 0) + V_DrawScaledPatch(x, y, 0, cuparrow); + + if (cupgrid.pageno != cupgrid.numpages-1) + V_DrawScaledPatch(BASEVIDWIDTH-x, y, V_FLIP, cuparrow); + } } static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 2e7800be0..5acbb0e7d 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -539,6 +539,21 @@ void M_StartControlPanel(void) // (We can change this timer later when extra animation is added.) if (finalecount < 1) return; + + if (menumessage.active) + { + if (!menumessage.closing && menumessage.fadetimer == 9) + { + // The following doesn't work with MM_YESNO. + // However, because there's no guarantee a profile + // is selected or controls set up to our liking, + // we can't call M_HandleMenuMessage. + + M_StopMessage(MA_NONE); + } + + return; + } } menuactive = true; @@ -549,8 +564,6 @@ void M_StartControlPanel(void) } else if (!Playing()) { - M_StopMessage(0); // Doesn't work with MM_YESNO or MM_EVENTHANDLER... but good enough to get the game as it is currently functional again - if (gamestate != GS_MENU) { G_SetGamestate(GS_MENU); @@ -754,7 +767,7 @@ void M_SetMenuDelay(UINT8 i) } } -void M_UpdateMenuCMD(UINT8 i) +void M_UpdateMenuCMD(UINT8 i, boolean bailrequired) { UINT8 mp = max(1, setup_numplayers); @@ -792,6 +805,11 @@ void M_UpdateMenuCMD(UINT8 i) if (G_PlayerInputDown(i, gc_start, mp)) { menucmd[i].buttons |= MBT_START; } + if (bailrequired && i == 0) + { + if (G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) { menucmd[i].buttons |= MBT_B; } + } + if (menucmd[i].dpad_ud == 0 && menucmd[i].dpad_lr == 0 && menucmd[i].buttons == 0) { // Reset delay count with no buttons. diff --git a/src/k_podium.c b/src/k_podium.c index 7b658c944..630048e98 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -35,6 +35,7 @@ #include "y_inter.h" #include "m_cond.h" #include "p_local.h" +#include "p_saveg.h" #include "p_setup.h" #include "st_stuff.h" // hud hiding #include "fastcmp.h" @@ -252,6 +253,12 @@ boolean K_StartCeremony(void) maptol = mapheaderinfo[gamemap-1]->typeoflevel; globalweather = mapheaderinfo[gamemap-1]->weather; + if (savedata.lives > 0) + { + K_LoadGrandPrixSaveGame(); + savedata.lives = 0; + } + // Make sure all of the GAME OVER'd players can spawn // and be present for the podium for (i = 0; i < MAXPLAYERS; i++) 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..1f81c370f 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -80,31 +80,45 @@ static UINT8 cheatf_warp(void) } } + M_ClearMenus(true); + + const char *text; + if (success) { G_SetUsedCheats(); - M_ClearMenus(true); S_StartSound(0, sfx_kc42); - M_StartMessage("Tournament Mode", - M_GetText( + text = M_GetText( "All challenges temporarily unlocked.\n" "Saving is disabled - the game will\n" "return to normal on next launch.\n" - ), NULL, MM_NOTHING, NULL, NULL); + ); } else { S_StartSound(0, sfx_s3k7b); - M_StartMessage("Tournament Mode", - M_GetText( - "This is the correct password, but\n" - "you already have every challenge\n" - "unlocked, so saving is still allowed!\n" - ), NULL, MM_NOTHING, NULL, NULL); + if (usedCheats) + { + text = M_GetText( + "This is the correct password, but\n" + "you already have every challenge\n" + "unlocked, so nothing has changed.\n" + ); + } + else + { + text = M_GetText( + "This is the correct password, but\n" + "you already have every challenge\n" + "unlocked, so saving is still allowed!\n" + ); + } } + M_StartMessage("Tournament Mode", text, NULL, MM_NOTHING, NULL, NULL); + return 1; } diff --git a/src/menus/extras-addons.c b/src/menus/extras-addons.c index dfa21b876..8610992d3 100644 --- a/src/menus/extras-addons.c +++ b/src/menus/extras-addons.c @@ -101,11 +101,12 @@ static boolean prevmajormods = false; static void M_AddonsClearName(INT32 choice) { + (void)choice; + if (!majormods || prevmajormods) { CLEARNAME; } - M_StopMessage(choice); } // Handles messages for addon errors. diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 9e7d4ca60..edd3c99c8 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -484,6 +484,7 @@ void M_CharacterSelectInit(void) setup_page = 0; } + void M_CharacterSelect(INT32 choice) { (void)choice; diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c index 454080573..ed5990587 100644 --- a/src/menus/play-local-race-time-attack.c +++ b/src/menus/play-local-race-time-attack.c @@ -414,7 +414,6 @@ static void M_WriteGuestReplay(INT32 ch) if (FIL_FileExists(rguest)) { - //M_StopMessage(0); remove(rguest); } diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index c97246e04..f146cd74f 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -7,6 +7,9 @@ #include "../../v_video.h" #include "../../k_grandprix.h" #include "../../r_local.h" // SplitScreen_OnChange +#include "../../k_podium.h" // K_StartCeremony +#include "../../m_misc.h" // FIL_FileExists +#include "../../d_main.h" // D_ClearState menuitem_t PLAY_CupSelect[] = { @@ -32,6 +35,150 @@ menu_t PLAY_CupSelectDef = { struct cupgrid_s cupgrid; +static void M_StartCup(UINT8 entry) +{ + UINT8 ssplayers = cv_splitplayers.value-1; + + if (ssplayers > 0) + { + // Splitscreen is not accomodated with this recovery feature. + entry = 0; + } + + S_StartSound(NULL, sfx_s3k63); + + paused = false; + + S_StopMusicCredit(); + + // Early fadeout to let the sound finish playing + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + if (entry == 0) + { + memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + + // read our dummy cvars + + grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); + grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); + + grandprixinfo.gp = true; + grandprixinfo.initalize = true; + grandprixinfo.cup = levellist.levelsearch.cup; + + // Populate the roundqueue + memset(&roundqueue, 0, sizeof(struct roundqueue)); + G_GPCupIntoRoundQueue(levellist.levelsearch.cup, levellist.newgametype, (boolean)cv_dummygpencore.value); + roundqueue.position = roundqueue.roundnum = 1; + roundqueue.netcommunicate = true; // relevant for future Online GP + } + else + { + // Silently change player setup + { + 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); + } + + // Skip Bonus rounds. + if (roundqueue.entries[entry].gametype != roundqueue.entries[0].gametype + && roundqueue.entries[entry].rankrestricted == false) + { + G_GetNextMap(); // updates position in the roundqueue + entry = roundqueue.position-1; + } + } + + paused = false; + + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); + + M_ClearMenus(true); + restoreMenu = &PLAY_CupSelectDef; + + if (entry < roundqueue.size) + { + D_MapChange( + roundqueue.entries[entry].mapnum + 1, + roundqueue.entries[entry].gametype, + roundqueue.entries[entry].encore, + true, + 1, + false, + roundqueue.entries[entry].rankrestricted + ); + } + else if (entry == 0) + { + I_Error("M_StartCup: roundqueue is empty on startup!!"); + } + else + { + if (K_StartCeremony() == false) + { + // Accomodate our buffoonery + D_ClearState(); + M_StartControlPanel(); + + M_StartMessage( + "Grand Prix Backup", + "The session is concluded!\n" + "You exited a final Bonus Round,\n" + "and the Podium failed to load.\n", + NULL, MM_NOTHING, NULL, NULL + ); + + G_HandleSaveLevel(true); + + return; + } + } +} + +static void M_GPBackup(INT32 choice) +{ + if (choice == MA_YES) + { + G_LoadGame(); + + if (savedata.lives != 0) + { + M_StartCup(roundqueue.position-1); + } + + return; + } + + M_StartCup(0); +} + void M_CupSelectHandler(INT32 choice) { const UINT8 pid = 0; @@ -104,67 +251,24 @@ void M_CupSelectHandler(INT32 choice) if (cupgrid.grandprix == true) { - UINT8 ssplayers = cv_splitplayers.value-1; - - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - S_StopMusicCredit(); - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) + if (newcup == cupsavedata.cup + && FIL_FileExists(gpbackup)) { - splitscreen = ssplayers; - SplitScreen_OnChange(); + M_StartMessage( + "Grand Prix Backup", + "A progress backup was found.\n" + "Do you want to resurrect your\n" + "last Grand Prix session?\n", + M_GPBackup, + MM_YESNO, + "Yes, let's try again", + "No, start from Round 1" + ); + + return; } - // read our dummy cvars - - grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); - grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); - - grandprixinfo.gp = true; - grandprixinfo.initalize = true; - grandprixinfo.cup = newcup; - - // Populate the roundqueue - memset(&roundqueue, 0, sizeof(struct roundqueue)); - G_GPCupIntoRoundQueue(newcup, levellist.newgametype, (boolean)cv_dummygpencore.value); - roundqueue.position = roundqueue.roundnum = 1; - roundqueue.netcommunicate = true; // relevant for future Online GP - - paused = false; - - // Don't restart the server if we're already in a game lol - if (gamestate == GS_MENU) - { - SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); - } - - D_MapChange( - roundqueue.entries[0].mapnum + 1, - roundqueue.entries[0].gametype, - roundqueue.entries[0].encore, - true, - 1, - false, - roundqueue.entries[0].rankrestricted - ); - - M_ClearMenus(true); - - restoreMenu = &PLAY_CupSelectDef; + M_StartCup(0); } else if (count == 1 && levellist.levelsearch.timeattack == true) { diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index a54096b1b..a759f7c0b 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -8,6 +8,8 @@ #include "../../r_local.h" // SplitScreen_OnChange #include "../../f_finale.h" // F_WipeStartScreen #include "../../v_video.h" +#include "../../g_game.h" // G_GetBackupCupData +#include "../../p_saveg.h" // cupsavedata cupheader_t dummy_lostandfound; @@ -262,6 +264,11 @@ boolean M_LevelListFromGametype(INT16 gt) const size_t pagelen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); boolean foundany = false, currentvalid = false; + G_GetBackupCupData( + cupgrid.grandprix == true + || cv_splitplayers.value <= 1 + ); + templevelsearch.cup = kartcupheaders; #if 0 @@ -331,7 +338,8 @@ boolean M_LevelListFromGametype(INT16 gt) if (Playing() ? (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) - : (gt == -1 && levellist.levelsearch.cup == templevelsearch.cup)) + : (cupsavedata.cup == templevelsearch.cup + || (gt == -1 && levellist.levelsearch.cup == templevelsearch.cup))) { GRID_FOCUSCUP; } diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index f677ba992..50ef95fb7 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -14,11 +14,14 @@ struct menumessage_s menumessage; // static inline size_t M_StringHeight(const char *string) { - size_t h = 8, i; + size_t h = 16, i, len = strlen(string); - for (i = 0; i < strlen(string); i++) - if (string[i] == '\n') - h += 8; + for (i = 0; i < len-1; i++) + { + if (string[i] != '\n') + continue; + h += 8; + } return h; } @@ -68,9 +71,10 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 menumessage.header = header; menumessage.flags = itemtype; menumessage.routine = routine; + menumessage.answer = MA_NONE; menumessage.fadetimer = 1; menumessage.timer = 0; - menumessage.closing = false; + menumessage.closing = 0; menumessage.active = true; start = 0; @@ -79,18 +83,21 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 if (!routine) { menumessage.flags = MM_NOTHING; - menumessage.routine = M_StopMessage; } - if (menumessage.flags == MM_YESNO && !defaultstr) + // Set action strings + switch (menumessage.flags) { - menumessage.defaultstr = "No"; - menumessage.confirmstr = "Yes"; - } - else - { - menumessage.defaultstr = defaultstr ? defaultstr : "OK"; - menumessage.confirmstr = confirmstr; + // Send 1 to the routine if we're pressing A, 2 if B/X, 0 otherwise. + case MM_YESNO: + menumessage.defaultstr = defaultstr ? defaultstr : "No"; + menumessage.confirmstr = confirmstr ? confirmstr : "Yes"; + break; + + default: + menumessage.defaultstr = defaultstr ? defaultstr : "OK"; + menumessage.confirmstr = NULL; + break; } // event routine @@ -133,11 +140,25 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 void M_StopMessage(INT32 choice) { - const char pid = 0; - (void) choice; + if (!menumessage.active || menumessage.closing) + return; + + const char pid = 0; + + // Set the answer. + menumessage.answer = choice; + +#if 1 + // The below was cool, but it felt annoyingly unresponsive. + menumessage.closing = MENUMESSAGECLOSE+1; +#else + // Intended length of time. + menumessage.closing = (TICRATE/2); + + // This weird operation is necessary so the text flash is consistently timed. + menumessage.closing |= ((2*MENUMESSAGECLOSE) - 1); +#endif - menumessage.closing = true; - menumessage.timer = 0; M_SetMenuDelay(pid); } @@ -145,14 +166,26 @@ boolean M_MenuMessageTick(void) { if (menumessage.closing) { - if (menumessage.fadetimer > 0) + if (menumessage.closing > MENUMESSAGECLOSE) { - menumessage.fadetimer--; + menumessage.closing--; } - - if (menumessage.fadetimer == 0) + else { - menumessage.active = false; + if (menumessage.fadetimer > 0) + { + menumessage.fadetimer--; + } + + if (menumessage.fadetimer == 0) + { + menumessage.active = false; + + if (menumessage.routine) + { + menumessage.routine(menumessage.answer); + } + } } return false; @@ -180,38 +213,22 @@ void M_HandleMenuMessage(void) switch (menumessage.flags) { - // Send 1 to the routine if we're pressing A/B/X - case MM_NOTHING: - { - if (btok || btnok) - menumessage.routine(0); - - break; - } // Send 1 to the routine if we're pressing A, 2 if B/X, 0 otherwise. case MM_YESNO: { - INT32 answer = MA_NONE; if (btok) - answer = MA_YES; + M_StopMessage(MA_YES); else if (btnok) - answer = MA_NO; - - // send 1 if btok is pressed, 2 if nok is pressed, 0 otherwise. - if (answer) - { - menumessage.routine(answer); - M_StopMessage(0); - } + M_StopMessage(MA_NO); break; } - // MM_EVENTHANDLER: In M_Responder to allow full event compat. default: - break; - } + { + if (btok || btnok) + M_StopMessage(MA_NONE); - // if we detect any keypress, don't forget to set the menu delay regardless. - if (btok || btnok) - M_SetMenuDelay(pid); + break; + } + } } diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 3f638aebd..c3b640ea8 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -358,6 +358,8 @@ void M_ConfirmEnterGame(INT32 choice) static void M_ExitGameResponse(INT32 ch) { + const UINT8 pid = 0; + if (ch != MA_YES) return; @@ -368,7 +370,7 @@ static void M_ExitGameResponse(INT32 ch) else { G_SetExitGameFlag(); - M_ClearMenus(true); + M_SetMenuDelay(pid); // prevent another input } } diff --git a/src/p_saveg.c b/src/p_saveg.c index 1e443a962..f80ffff7b 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" @@ -47,6 +48,7 @@ #include "k_zvote.h" savedata_t savedata; +savedata_cup_t cupsavedata; // Block UINT32s to attempt to ensure that the correct data is // being sent and received @@ -82,25 +84,106 @@ 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); + // Prevent an exploit from occuring. + WRITESINT8(save->p, (player->lives - 1)); WRITEUINT32(save->p, player->score); + WRITEUINT16(save->p, player->totalring); + + INT32 skin = player->skin; + if (skin > numskins) + skin = 0; + + WRITESTRINGN(save->p, skins[skin].name, SKINNAMESIZE); + + if (player->followerskin < 0 || player->followerskin >= numfollowers) + WRITESTRINGN(save->p, "None", SKINNAMESIZE); + else + WRITESTRINGN(save->p, followers[player->followerskin].name, SKINNAMESIZE); + + WRITEUINT16(save->p, player->skincolor); + 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); + + skin = players[i].skin; + if (skin > numskins) + skin = 0; + + WRITESTRINGN(save->p, skins[skin].name, SKINNAMESIZE); + + 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); + + char skinname[SKINNAMESIZE+1]; + INT32 skin; + + READSTRINGN(save->p, skinname, SKINNAMESIZE); + skin = R_SkinAvailable(skinname); + + if (skin == -1) + { + CONS_Alert(CONS_ERROR, "P_UnArchivePlayer: Character \"%s\" is not currently loaded.\n", skinname); + return false; + } + + savedata.skin = skin; + + READSTRINGN(save->p, skinname, SKINNAMESIZE); + savedata.followerskin = K_FollowerAvailable(skinname); + + savedata.skincolor = READUINT16(save->p); + savedata.followercolor = READUINT16(save->p); + + memset(&savedata.bots, 0, sizeof(savedata.bots)); + + UINT8 pid; + const UINT8 defaultbotskin = R_BotDefaultSkin(); + + while ((pid = READUINT8(save->p)) < MAXPLAYERS) + { + savedata.bots[pid].valid = true; + + READSTRINGN(save->p, skinname, SKINNAMESIZE); + skin = R_SkinAvailable(skinname); + + if (skin == -1) + { + // It is not worth destroying an otherwise good savedata over extra added skins. + // Let's just say they didn't show up to the rematch, so some Eggrobos subbed in. + CONS_Alert(CONS_WARNING, "P_UnArchivePlayer: Bot's character \"%s\" was not loaded, replacing with default \"%s\".\n", skinname, skins[defaultbotskin].name); + skin = defaultbotskin; + } + + savedata.bots[pid].skin = skin; + + 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 +1122,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 +5343,230 @@ 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; - - if (gamecomplete) - mapnum |= 8192; - - WRITEINT16(save->p, mapnum); - WRITEUINT16(save->p, emeralds+357); WRITESTRINGN(save->p, timeattackfolder, sizeof(timeattackfolder)); + + // Grand Prix information + + WRITEUINT8(save->p, grandprixinfo.gamespeed); + WRITEUINT8(save->p, (UINT8)grandprixinfo.encore); + WRITEUINT8(save->p, (UINT8)grandprixinfo.masterbots); + + WRITESTRINGL(save->p, grandprixinfo.cup->name, MAXCUPNAME); + + // Round Queue information + + WRITEUINT8(save->p, roundqueue.position); + WRITEUINT8(save->p, roundqueue.size); + WRITEUINT8(save->p, roundqueue.roundnum); + + UINT8 i; + for (i = 0; i < roundqueue.size; i++) + { + UINT16 mapnum = roundqueue.entries[i].mapnum; + UINT32 val = 0; // no good default, will all-but-guarantee bad save + if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL) + val = mapheaderinfo[mapnum]->lumpnamehash; + + WRITEUINT32(save->p, val); + } + + // Rank information + + { + 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); + } + + // Marathon information + + 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) +void P_GetBackupCupData(savebuffer_t *save) { 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."); + cupsavedata.cup = NULL; + return; } - memset(playeringame, 0, sizeof(*playeringame)); - playeringame[consoleplayer] = true; + // Grand Prix information + + cupsavedata.difficulty = READUINT8(save->p); + cupsavedata.encore = (boolean)READUINT8(save->p); + boolean masterbots = (boolean)READUINT8(save->p); + + if (masterbots == true) + cupsavedata.difficulty = KARTGP_MASTER; + + // Find the relevant cup. + char cupname[MAXCUPNAME]; + READSTRINGL(save->p, cupname, sizeof(cupname)); + UINT32 hash = quickncasehash(cupname, MAXCUPNAME); + + for (cupsavedata.cup = kartcupheaders; cupsavedata.cup; cupsavedata.cup = cupsavedata.cup->next) + { + if (cupsavedata.cup->namehash != hash) + continue; + + if (strcmp(cupsavedata.cup->name, cupname)) + continue; + + break; + } + + // Okay, no further! We've got everything we need. +} + +static boolean P_UnArchiveSPGame(savebuffer_t *save) +{ + char testname[sizeof(timeattackfolder)]; + + READSTRINGN(save->p, testname, sizeof(testname)); + + if (strcmp(testname, timeattackfolder)) + { + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Corrupt mod ID.\n"); + return false; + } + + // 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; + + // Grand Prix information + + grandprixinfo.gamespeed = READUINT8(save->p); + grandprixinfo.encore = (boolean)READUINT8(save->p); + grandprixinfo.masterbots = (boolean)READUINT8(save->p); + + // Find the relevant cup. + char cupname[MAXCUPNAME]; + READSTRINGL(save->p, cupname, sizeof(cupname)); + UINT32 hash = quickncasehash(cupname, MAXCUPNAME); + + for (grandprixinfo.cup = kartcupheaders; grandprixinfo.cup; grandprixinfo.cup = grandprixinfo.cup->next) + { + if (grandprixinfo.cup->namehash != hash) + continue; + + if (strcmp(grandprixinfo.cup->name, cupname)) + continue; + + break; + } + + if (!grandprixinfo.cup) + { + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Cup \"%s\" is not currently loaded.\n", cupname); + return false; + } + + // Round Queue information + + memset(&roundqueue, 0, sizeof(roundqueue)); + + G_GPCupIntoRoundQueue(grandprixinfo.cup, GT_RACE, grandprixinfo.encore); + + roundqueue.position = READUINT8(save->p); + UINT8 size = READUINT8(save->p); + roundqueue.roundnum = READUINT8(save->p); + + if (roundqueue.size != size) + { + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Cup \"%s\"'s level composition has changed between game launches (differs in level count).\n", cupname); + return false; + } + + if (roundqueue.position == 0 || roundqueue.position > size) + { + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Position in the round queue is invalid.\n"); + return false; + } + + UINT8 i; + for (i = 0; i < roundqueue.size; i++) + { + UINT32 val = READUINT32(save->p); + UINT16 mapnum = roundqueue.entries[i].mapnum; + + if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL) + { + if (mapheaderinfo[mapnum]->lumpnamehash == val) + continue; + } + + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Cup \"%s\"'s level composition has changed between game launches (differs at level %u).\n", cupname, i); + return false; + } + + // Rank information + + { + 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); + } + + // Marathon information + + marathonmode = READUINT8(save->p); + marathontime = READUINT32(save->p); + + return true; } static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) @@ -5718,9 +5979,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 +6036,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 +6044,24 @@ 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; - - // 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 + goto badloadgame; 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..66345367d 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -31,24 +31,51 @@ extern "C" { // Persistent storage/archiving. // These are the load / save game routines. -void P_SaveGame(savebuffer_t *save, INT16 mapnum); +// Local Play +void P_SaveGame(savebuffer_t *save); +boolean P_LoadGame(savebuffer_t *save); +void P_GetBackupCupData(savebuffer_t *save); + +// Online void P_SaveNetGame(savebuffer_t *save, boolean resending); -boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride); 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; +struct savedata_cup_t +{ + cupheader_t *cup; + UINT8 difficulty; + boolean encore; +}; + +extern savedata_cup_t cupsavedata; + struct savebuffer_t { UINT8 *buffer; diff --git a/src/p_setup.c b/src/p_setup.c index c9ca10345..ee07ccf14 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(gamestate == GS_CEREMONY); + if (marathonmode & MA_INGAME) { marathonmode &= ~MA_INIT; diff --git a/src/typedef.h b/src/typedef.h index a54ae82e5..275a30da4 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -297,6 +297,7 @@ TYPEDEF (polyfadedata_t); // p_saveg.h TYPEDEF (savedata_t); +TYPEDEF (savedata_cup_t); TYPEDEF (savebuffer_t); // p_setup.h