diff --git a/src/g_game.c b/src/g_game.c index aa60e5b1d..5c889ccc3 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3932,15 +3932,68 @@ void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencor // At the end of the Cup is a Rank-restricted treat. // So we append it to the end of the roundqueue. // (as long as it exists, of course!) - cupLevelNum = cup->cachedlevels[CUPCACHE_SPECIAL]; - if (cupLevelNum < nummapheaders) { - G_MapIntoRoundQueue( - cupLevelNum, - G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel), - setencore, // if this isn't correct, Got_Mapcmd will fix it - true // Rank-restricted! - ); + // Of course, this last minute game design tweak + // has to make things a little complicated. We + // basically just make sure they're dispensed + // at the intended difficulty sequence until + // you've got them all, at which point they + // become their intended order permanently. + // ~toast 010324 + cupheader_t *emeraldcup = NULL; + + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || cup->id >= basenumkartcupheaders // custom content + || M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order + { + // Standard order. + emeraldcup = cup; + } + else + { + // Determine order from sealedswaps. + for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++) + { + if (gamedata->sealedswaps[i] != grandprixinfo.cup) + continue; + + // Repeat visit, grab the same ID. + break; + } + + // If there's pending stars, get them from the associated cup order. + if (i < GDMAX_SEALEDSWAPS) + { + emeraldcup = kartcupheaders; + while (emeraldcup) + { + if (emeraldcup->id >= basenumkartcupheaders) + { + emeraldcup = NULL; + break; + } + + if (emeraldcup->emeraldnum == i+1) + break; + + emeraldcup = emeraldcup->next; + } + } + } + + if (emeraldcup) + { + cupLevelNum = emeraldcup->cachedlevels[CUPCACHE_SPECIAL]; + if (cupLevelNum < nummapheaders) + { + G_MapIntoRoundQueue( + cupLevelNum, + G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel), + setencore, // if this isn't correct, Got_Mapcmd will fix it + true // Rank-restricted! + ); + } + } } if (roundqueue.size == 0) diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index 105f997ad..d522ce7ab 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -64,6 +64,7 @@ void srb2::save_ng_gamedata() ng.milestones.majorkeyskipattempted = gamedata->majorkeyskipattempted; ng.milestones.finishedtutorialchallenge = gamedata->finishedtutorialchallenge; ng.milestones.enteredtutorialchallenge = gamedata->enteredtutorialchallenge; + ng.milestones.sealedswapalerted = gamedata->sealedswapalerted; ng.milestones.gonerlevel = gamedata->gonerlevel; ng.prisons.thisprisoneggpickup = gamedata->thisprisoneggpickup; ng.prisons.prisoneggstothispickup = gamedata->prisoneggstothispickup; @@ -176,7 +177,7 @@ void srb2::save_ng_gamedata() } for (auto cup = kartcupheaders; cup; cup = cup->next) { - if (cup->windata[0].best_placement == 0) + if (cup->windata[0].best_placement == 0 && cup->windata[1].got_emerald == false) { continue; } @@ -229,6 +230,17 @@ void srb2::save_ng_gamedata() ng.cups[cupdata.name] = std::move(cupdata); } + for (int i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++) + { + srb2::GamedataSealedSwapJson sealedswap {}; + + cupheader_t* cup = gamedata->sealedswaps[i]; + + sealedswap.name = std::string(cup->name); + + ng.sealedswaps.emplace_back(std::move(sealedswap)); + } + std::string gamedataname_s {gamedatafilename}; fs::path savepath {fmt::format("{}/{}", srb2home, gamedataname_s)}; fs::path tmpsavepath {fmt::format("{}/{}.tmp", srb2home, gamedataname_s)}; @@ -418,6 +430,7 @@ void srb2::load_ng_gamedata() gamedata->majorkeyskipattempted = js.milestones.majorkeyskipattempted; gamedata->finishedtutorialchallenge = js.milestones.finishedtutorialchallenge; gamedata->enteredtutorialchallenge = js.milestones.enteredtutorialchallenge; + gamedata->sealedswapalerted = js.milestones.sealedswapalerted; gamedata->gonerlevel = js.milestones.gonerlevel; gamedata->thisprisoneggpickup = js.prisons.thisprisoneggpickup; gamedata->prisoneggstothispickup = js.prisons.prisoneggstothispickup; @@ -720,6 +733,33 @@ void srb2::load_ng_gamedata() } } + size_t sealedswaps_size = js.sealedswaps.size(); + for (size_t i = 0; i < std::min((size_t)GDMAX_SEALEDSWAPS, sealedswaps_size); i++) + { + cupheader_t* cup = nullptr; + + // Find BASE cups only + for (cup = kartcupheaders; cup; cup = cup->next) + { + if (cup->id >= basenumkartcupheaders) + { + cup = NULL; + break; + } + + std::string cupname = std::string(cup->name); + if (cupname == js.sealedswaps[i].name) + { + break; + } + } + + if (cup) + { + gamedata->sealedswaps[i] = cup; + } + } + M_FinaliseGameData(); } diff --git a/src/g_gamedata.h b/src/g_gamedata.h index d476f2e46..f479da427 100644 --- a/src/g_gamedata.h +++ b/src/g_gamedata.h @@ -70,6 +70,7 @@ struct GamedataMilestonesJson final bool majorkeyskipattempted; bool finishedtutorialchallenge; bool enteredtutorialchallenge; + bool sealedswapalerted; NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMilestonesJson, @@ -81,7 +82,8 @@ struct GamedataMilestonesJson final chaokeytutorial, majorkeyskipattempted, finishedtutorialchallenge, - enteredtutorialchallenge + enteredtutorialchallenge, + sealedswapalerted ) }; @@ -184,6 +186,13 @@ struct GamedataCupJson final NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupJson, name, records) }; +struct GamedataSealedSwapJson final +{ + std::string name; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSealedSwapJson, name) +}; + struct GamedataJson final { GamedataPlaytimeJson playtime; @@ -203,6 +212,7 @@ struct GamedataJson final std::unordered_map maps; std::vector spraycans; std::unordered_map cups; + std::vector sealedswaps; NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataJson, @@ -222,7 +232,8 @@ struct GamedataJson final skins, maps, spraycans, - cups + cups, + sealedswaps ) }; diff --git a/src/k_menu.h b/src/k_menu.h index d84d97414..049559ce4 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -680,6 +680,8 @@ void M_Init(void); void M_PlayMenuJam(void); +boolean M_ConsiderSealedSwapAlert(void); + void M_OpenVirtualKeyboard(boolean gamepad); void M_MenuTypingInput(INT32 key); @@ -1347,6 +1349,8 @@ extern struct challengesmenu_s { boolean chaokeyadd, keywasadded; UINT8 chaokeyhold; + boolean considersealedswapalert; + boolean requestflip; UINT16 unlockcount[CMC_MAX]; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index bb9205fe2..0929e714e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2823,6 +2823,57 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi rankx += 19 - (rankw / 2); cupwindata_t *windata = &(cup->windata[difficulty]); + UINT8 emeraldnum = UINT8_MAX; + + if (!noemerald) + { + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || cup->id >= basenumkartcupheaders // custom content + || M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order + { + // Standard order. + if (windata->got_emerald == true) + { + emeraldnum = cup->emeraldnum; + } + } + else + { + // Determine order from sealedswaps. + UINT8 i; + for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++) + { + if (gamedata->sealedswaps[i] != cup) + continue; + + break; + } + + // If there's pending stars, get them from the associated cup order. + if (i < GDMAX_SEALEDSWAPS) + { + cupheader_t *emeraldcup = kartcupheaders; + while (emeraldcup) + { + if (emeraldcup->id >= basenumkartcupheaders) + { + emeraldcup = NULL; + break; + } + + if (emeraldcup->emeraldnum == i+1) + { + if (emeraldcup->windata[difficulty].got_emerald == true) + emeraldnum = i+1; + break; + } + + emeraldcup = emeraldcup->next; + } + } + } + } + if (windata->best_placement == 0) { if (statsmode) @@ -2832,14 +2883,13 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi V_DrawCharacter((14-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false); rankx += 14 + 1; V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false); - - if (!noemerald) - { - rankx += 12 + 1; - V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false); - } } - return rankw; + else + { + rankx += 14 + 1; + } + + goto windataemeraldmaybe; } UINT8 *colormap = NULL; @@ -2934,13 +2984,15 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi if (charPat) V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, charPat, colormap); +windataemeraldmaybe: + rankx += 12 + 1; if (noemerald) ; - else if (windata->got_emerald == true) + else if (emeraldnum != UINT8_MAX) { - if (cup->emeraldnum == 0) + if (emeraldnum == 0) V_DrawCharacter(rankx+2, ranky+2, '+', false); else { @@ -2948,14 +3000,14 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi if (!flash) { - UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (cup->emeraldnum-1) % 7; + UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (emeraldnum-1) % 7; colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); } const char *emname = va( "%sMAP%c", - (cup->emeraldnum > 7) ? "SUP" : "EME", + (emeraldnum > 7) ? "SUP" : "EME", colormap ? '\0' : 'B' ); @@ -3116,7 +3168,7 @@ void M_DrawCupSelect(void) y += 44; //(8 + 100) - (20 + 44) } - if (windata && windata->best_placement != 0) + if (windata) { M_DrawCupWinData( x, diff --git a/src/k_menufunc.c b/src/k_menufunc.c index efb78969d..7833da1e3 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -560,6 +560,35 @@ void M_PlayMenuJam(void) #undef IsCurrentlyPlaying +boolean M_ConsiderSealedSwapAlert(void) +{ + if (gamedata->sealedswapalerted == true) + return false; + + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order + { + gamedata->sealedswapalerted = true; + + // Don't make a message if no Sealed Stars have yet been found. + if (gamedata->everseenspecial == false) + return false; + + M_StartMessage( + "Message from the Stars", + "As if called by fate, the Emeralds you've\n" + "collected return to their rightful places...\n" + "\n" + "The Sealed Stars are now ordered via Cups!\n", + NULL, MM_NOTHING, NULL, NULL + ); + + return true; + } + + return false; +} + void M_ValidateRestoreMenu(void) { if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef) @@ -629,6 +658,11 @@ menu_t *M_SpecificMenuRestore(menu_t *torestore) M_SetupPlayMenu(-1); PLAY_CharSelectDef.prevMenu = &MainDef; + if (torestore != &MISC_ChallengesDef) + { + M_ConsiderSealedSwapAlert(); + } + return torestore; } diff --git a/src/k_podium.cpp b/src/k_podium.cpp index 103640d02..30e863298 100644 --- a/src/k_podium.cpp +++ b/src/k_podium.cpp @@ -83,6 +83,7 @@ static struct podiumData_s sfxenum_t gradeVoice; cupheader_t *cup; + UINT8 emeraldnum; boolean fastForward; @@ -102,6 +103,7 @@ void podiumData_s::Init(void) { rank = grandprixinfo.rank; cup = grandprixinfo.cup; + emeraldnum = cup->emeraldnum; } else { @@ -119,6 +121,7 @@ void podiumData_s::Init(void) cup = cup->next; } + emeraldnum = 0; memset(&rank, 0, sizeof(gpRank_t)); rank.skin = players[consoleplayer].skin; @@ -628,12 +631,7 @@ void podiumData_s::Draw(void) case GPEVENT_SPECIAL: { srb2::Draw drawer_emerald = drawer_gametype; - UINT8 emeraldNum = 0; - - if (cup != nullptr) - { - emeraldNum = cup->emeraldnum; - } + UINT8 emeraldNum = g_podiumData.emeraldnum; boolean useWhiteFrame = ((leveltime & 1) || !dta->gotSpecialPrize); patch_t *emeraldPatch = nullptr; @@ -844,12 +842,7 @@ void podiumData_s::Draw(void) if (rank.specialWon == true) { - UINT8 emeraldNum = 0; - - if (cup != nullptr) - { - emeraldNum = cup->emeraldnum; - } + UINT8 emeraldNum = g_podiumData.emeraldnum; const boolean emeraldBlink = (leveltime & 1); patch_t *emeraldOverlay = nullptr; @@ -1263,6 +1256,60 @@ void K_ResetCeremony(void) return; } + cupheader_t *emeraldcup = NULL; + + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || grandprixinfo.cup->id >= basenumkartcupheaders // custom content + || M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order + { + // Standard order. + emeraldcup = grandprixinfo.cup; + } + else + { + // Determine order from sealedswaps. + for (i = 0; i < GDMAX_SEALEDSWAPS; i++) + { + if (gamedata->sealedswaps[i] == NULL) + { + if (g_podiumData.rank.specialWon == true) + { + // First visit! Mark it off. + gamedata->sealedswaps[i] = grandprixinfo.cup; + } + + break; + } + + if (gamedata->sealedswaps[i] != grandprixinfo.cup) + continue; + + // Repeat visit, grab the same ID. + break; + } + + // If there's pending stars, apply them to the new cup order. + if (i < GDMAX_SEALEDSWAPS) + { + emeraldcup = kartcupheaders; + while (emeraldcup) + { + if (emeraldcup->id >= basenumkartcupheaders) + { + emeraldcup = NULL; + break; + } + + if (emeraldcup->emeraldnum == i+1) + break; + + emeraldcup = emeraldcup->next; + } + + g_podiumData.emeraldnum = i+1; + } + } + // Write grade, position, and emerald-having-ness for later sessions! i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed; @@ -1292,9 +1339,9 @@ void K_ResetCeremony(void) anymerit = true; } - if (g_podiumData.rank.specialWon == true) + if (g_podiumData.rank.specialWon == true && emeraldcup) { - grandprixinfo.cup->windata[i].got_emerald = true; + emeraldcup->windata[i].got_emerald = true; anymerit = true; } diff --git a/src/m_cheat.c b/src/m_cheat.c index 779d6b526..7a0f1e063 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -96,6 +96,7 @@ static UINT8 cheatf_warp(void) if (success) { gamedata->gonerlevel = GDGONER_DONE; + gamedata->sealedswapalerted = true; G_SetUsedCheats(); } @@ -231,6 +232,7 @@ static UINT8 cheatf_devmode(void) } gamedata->gonerlevel = GDGONER_DONE; + gamedata->sealedswapalerted = true; M_ClearMenus(true); diff --git a/src/m_cond.c b/src/m_cond.c index 345641454..4e35471ed 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -666,6 +666,7 @@ void M_ClearStats(void) gamedata->majorkeyskipattempted = false; gamedata->enteredtutorialchallenge = false; gamedata->finishedtutorialchallenge = false; + gamedata->sealedswapalerted = false; gamedata->musicstate = GDMUSIC_NONE; gamedata->importprofilewins = false; @@ -720,6 +721,8 @@ void M_ClearSecrets(void) skincolors[i].cache_spraycan = UINT16_MAX; } + memset(gamedata->sealedswaps, 0, sizeof(gamedata->sealedswaps)); + Z_Free(gamedata->challengegrid); gamedata->challengegrid = NULL; gamedata->challengegridwidth = 0; diff --git a/src/m_cond.h b/src/m_cond.h index 393d046c5..210a09767 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -291,6 +291,7 @@ typedef enum { // This is the largest number of 9s that will fit in UINT32 and UINT16 respectively. #define GDMAX_RINGS 999999999 #define GDMAX_CHAOKEYS 9999 +#define GDMAX_SEALEDSWAPS 7 #define GDCONVERT_ROUNDSTOKEY 14 @@ -371,12 +372,15 @@ struct gamedata_t UINT32 totalrings; UINT32 totaltumbletime; - // Chao Key condition bypass + // CHAO KEYS AND THEIR GENERATION UINT32 pendingkeyrounds; UINT8 pendingkeyroundoffset; UINT16 keyspending; UINT16 chaokeys; + // EMERALD REMAPPING + cupheader_t *sealedswaps[GDMAX_SEALEDSWAPS]; + // SPECIFIC SPECIAL EVENTS boolean everloadedaddon; boolean everfinishedcredits; @@ -387,6 +391,7 @@ struct gamedata_t boolean majorkeyskipattempted; boolean enteredtutorialchallenge; boolean finishedtutorialchallenge; + boolean sealedswapalerted; gdmusic_t musicstate; UINT8 gonerlevel; diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 6fe8ac330..e1df3066b 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -335,6 +335,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) challengesmenu.requestnew = false; challengesmenu.chaokeyadd = false; challengesmenu.keywasadded = false; + challengesmenu.considersealedswapalert = false; challengesmenu.chaokeyhold = 0; challengesmenu.currentunlock = MAXUNLOCKABLES; challengesmenu.unlockcondition = NULL; @@ -668,10 +669,15 @@ void M_ChallengesTick(void) if (challengesmenu.currentunlock < MAXUNLOCKABLES && challengesmenu.unlockanim == UNLOCKTIME) { + unlockable_t *ref = &unlockables[challengesmenu.currentunlock]; + // Unlock animation... also tied directly to the actual unlock! gamedata->unlocked[challengesmenu.currentunlock] = true; M_UpdateUnlockablesAndExtraEmblems(true, true); + if (ref->type == SECRET_SPECIALATTACK) + challengesmenu.considersealedswapalert = true; + // Update shown description just in case..? if (challengesmenu.unlockcondition) Z_Free(challengesmenu.unlockcondition); @@ -682,12 +688,10 @@ void M_ChallengesTick(void) if (challengesmenu.extradata) { - unlockable_t *ref; UINT16 bombcolor; M_UpdateChallengeGridExtraData(challengesmenu.extradata); - ref = &unlockables[challengesmenu.currentunlock]; bombcolor = SKINCOLOR_NONE; if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) @@ -747,7 +751,14 @@ void M_ChallengesTick(void) // Play music the moment control returns. M_PlayMenuJam(); - if (gamedata->chaokeytutorial == false + if (challengesmenu.considersealedswapalert == true + && M_ConsiderSealedSwapAlert() == true) + { + // No keygen tutorial in this case... + // not ideal but at least unlikely to + // get at same time?? :V + } + else if (gamedata->chaokeytutorial == false && challengesmenu.keywasadded == true) { M_ChallengesTutorial(CCTUTORIAL_KEYGEN); diff --git a/src/p_saveg.c b/src/p_saveg.c index e516d809c..8d3b51da8 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6280,6 +6280,14 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) { UINT32 val = READUINT32(save->p); + if (roundqueue.entries[i].rankrestricted && roundqueue.position != i+1) + { + // If this is a Sealed Star that hasn't yet been + // reached, don't be picky about divergance. Just + // use the base game without question. ~toast 010324 + continue; + } + mapnum = roundqueue.entries[i].mapnum; if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL) { diff --git a/src/p_user.c b/src/p_user.c index 815551626..7adfa49ea 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -296,9 +296,29 @@ UINT8 P_GetNextEmerald(void) { cupheader_t *cup = NULL; - if (grandprixinfo.gp == true) + if (grandprixinfo.gp == true && grandprixinfo.cup) { - cup = grandprixinfo.cup; + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || grandprixinfo.cup->id >= basenumkartcupheaders // custom content + || M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order + { + cup = grandprixinfo.cup; + } + else + { + // Determine order from sealedswaps. + UINT8 i; + for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++) + { + if (gamedata->sealedswaps[i] != grandprixinfo.cup) + continue; + + // Repeat visit, grab the same ID. + break; + } + + return i+1; + } } if (cup == NULL)