From 8b437d5a326c39feaec3c553c8a1fefe28094ffe Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 9 Mar 2023 22:31:34 +0000 Subject: [PATCH] Minimum viable product of Chao Keys condition bypass - Start with 3, per Sakurai's prior art. - Earn them per certain number of rounds - DEVELOP builds: once every 4 rounds - Release builds: once every 50 rounds - Has an internal cap based on the maximum number of unlockables supported. - Possible future work could adjust this to restrict based on the maximum number of unlockables unlocks.pk3 actually has set. - Use on the Challenges screen to bust open small tiles with hints (or the very first tile, if you haven't unlocked anything yet). - Will do a funny shake if you try anything else. - Interrupts menu flow just like getting an unlock. - The matches you've played will tick upwards, giving you keys as they loop over. --- src/g_game.c | 20 +++- src/k_menu.h | 6 +- src/k_menudraw.c | 22 ++++ src/m_cond.c | 30 ++++- src/m_cond.h | 19 ++- src/menus/extras-challenges.c | 218 +++++++++++++++++++++++----------- 6 files changed, 237 insertions(+), 78 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 23714313a..c06ad9c61 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4141,6 +4141,7 @@ static void G_DoCompleted(void) roundtype = GDGT_SPECIAL; gamedata->roundsplayed[roundtype]++; + gamedata->pendingkeyrounds++; // Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve M_UpdateUnlockablesAndExtraEmblems(true); @@ -4562,6 +4563,12 @@ void G_LoadGameData(void) gamedata->roundsplayed[i] = READUINT32(save.p); } + gamedata->pendingkeyrounds = READUINT32(save.p); + gamedata->pendingkeyroundoffset = READUINT8(save.p); + gamedata->keyspending = READUINT8(save.p); + gamedata->chaokeys = READUINT8(save.p); + gamedata->usedkeys = READUINT8(save.p); + gamedata->crashflags = READUINT8(save.p); if (gamedata->crashflags & GDCRASH_LAST) gamedata->crashflags |= GDCRASH_ANY; @@ -4740,7 +4747,12 @@ void G_SaveGameData(boolean dirty) return; } - length = (4+1+4+4+(4*GDGT_MAX)+1+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); + length = (4+1+4+4+ + (4*GDGT_MAX)+ + 4+1+1+1+1+ + 1+1+4+ + (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ + 4+4+2); if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; @@ -4765,6 +4777,12 @@ void G_SaveGameData(boolean dirty) WRITEUINT32(save.p, gamedata->roundsplayed[i]); } + WRITEUINT32(save.p, gamedata->pendingkeyrounds); // 4 + WRITEUINT8(save.p, gamedata->pendingkeyroundoffset); // 1 + WRITEUINT8(save.p, gamedata->keyspending); // 1 + WRITEUINT8(save.p, gamedata->chaokeys); // 1 + WRITEUINT8(save.p, gamedata->usedkeys); // 1 + { UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY); if (dirty) diff --git a/src/k_menu.h b/src/k_menu.h index 75bd392e2..cb7e79a10 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1144,7 +1144,9 @@ void M_DrawAddons(void); #define CC_UNLOCKED 1 #define CC_TALLY 2 #define CC_ANIM 3 -#define CC_MAX 4 +#define CC_CHAOANIM 4 +#define CC_CHAONOPE 5 +#define CC_MAX 6 #define TILEFLIP_MAX 16 @@ -1155,7 +1157,6 @@ extern struct timeattackmenu_s { } timeattackmenu; - // Keep track of some pause menu data for visual goodness. extern struct challengesmenu_s { @@ -1174,6 +1175,7 @@ extern struct challengesmenu_s { boolean pending; boolean requestnew; + boolean chaokeyadd; boolean requestflip; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index ebe9a6b17..ba48c2a30 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5170,6 +5170,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) #define challengetransparentstrength 8 #define challengesgridstep 22 +#define challengekeybarwidth 50 void M_DrawChallenges(void) { @@ -5293,6 +5294,26 @@ void M_DrawChallenges(void) challengedesc: + // Chao Keys + { + patch_t *key = W_CachePatchName("UN_CHA00", PU_CACHE); + INT32 offs = challengesmenu.unlockcount[CC_CHAONOPE]; + if (offs & 1) + offs = -offs; + offs /= 2; + V_DrawFixedPatch((6+offs)*FRACUNIT, 5*FRACUNIT, FRACUNIT, 0, key, NULL); + V_DrawKartString((25+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, va("%u", gamedata->chaokeys)); + + offs = challengekeybarwidth; + if ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS) + offs = ((gamedata->pendingkeyroundoffset * challengekeybarwidth)/GDCONVERT_ROUNDSTOKEY); + + if (offs > 0) + V_DrawFill(1, 25, offs, 2, 0); + if (offs < challengekeybarwidth) + V_DrawFadeFill(1+offs, 25, challengekeybarwidth-offs, 2, 0, 31, challengetransparentstrength); + } + // Tally { str = va("%d/%d", @@ -5347,6 +5368,7 @@ challengedesc: #undef challengetransparentstrength #undef challengesgridstep +#undef challengekeybarwidth // Statistics menu diff --git a/src/m_cond.c b/src/m_cond.c index 0f19f9d8f..36f575317 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -548,6 +548,12 @@ void M_ClearSecrets(void) Z_Free(gamedata->challengegrid); gamedata->challengegrid = NULL; gamedata->challengegridwidth = 0; + + gamedata->pendingkeyrounds = 0; + gamedata->pendingkeyroundoffset = 0; + gamedata->keyspending = 0; + gamedata->chaokeys = 3; // Start with 3 !! + gamedata->usedkeys = 0; } // ---------------------- @@ -1336,8 +1342,7 @@ static boolean M_CheckUnlockConditions(player_t *player) boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) { - INT32 i; - UINT8 response = 0; + UINT16 i = 0, response = 0, newkeys = 0; if (!gamedata) { @@ -1355,6 +1360,14 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) response = M_CheckUnlockConditions(NULL); + while ((gamedata->keyspending + gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS + && ((gamedata->pendingkeyrounds + gamedata->pendingkeyroundoffset)/GDCONVERT_ROUNDSTOKEY) > gamedata->keyspending) + { + gamedata->keyspending++; + newkeys++; + response |= true; + } + if (!demo.playback && Playing() && (gamestate == GS_LEVEL)) { for (i = 0; i <= splitscreen; i++) @@ -1367,7 +1380,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) } } - if (!response && loud) + if (loud && response == 0) { return false; } @@ -1397,8 +1410,10 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) response++; } + response += newkeys; + // Announce - if (response) + if (response != 0) { if (loud) { @@ -1409,7 +1424,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) return false; } -UINT8 M_GetNextAchievedUnlock(void) +UINT16 M_GetNextAchievedUnlock(void) { UINT8 i; @@ -1434,6 +1449,11 @@ UINT8 M_GetNextAchievedUnlock(void) return i; } + if (gamedata->keyspending > 0) + { + return PENDING_CHAOKEYS; + } + return MAXUNLOCKABLES; } diff --git a/src/m_cond.h b/src/m_cond.h index 6c1f131d4..83d64ac9b 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -207,6 +207,13 @@ typedef enum // This is the largest number of 9s that will fit in UINT32. #define GDMAX_RINGS 999999999 +#define GDMAX_CHAOKEYS MAXUNLOCKABLES + +#ifdef DEVELOP +#define GDCONVERT_ROUNDSTOKEY 4 +#else +#define GDCONVERT_ROUNDSTOKEY 50 +#endif typedef enum { GDGT_RACE, @@ -247,6 +254,13 @@ struct gamedata_t UINT32 roundsplayed[GDGT_MAX]; UINT32 totalrings; + // Chao Key condition bypass + UINT32 pendingkeyrounds; + UINT8 pendingkeyroundoffset; + UINT8 keyspending; + UINT8 chaokeys; + UINT8 usedkeys; + // SPECIFIC SPECIAL EVENTS boolean everloadedaddon; UINT8 crashflags; @@ -297,7 +311,10 @@ void M_ClearStats(void); // Updating conditions and unlockables boolean M_CheckCondition(condition_t *cn, player_t *player); boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud); -UINT8 M_GetNextAchievedUnlock(void); + +#define PENDING_CHAOKEYS (UINT16_MAX-1) +UINT16 M_GetNextAchievedUnlock(void); + UINT8 M_CheckLevelEmblems(void); UINT8 M_CompletionEmblems(void); diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 9caf144bd..30229499d 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -49,17 +49,59 @@ menu_t MISC_StatisticsDef = { struct challengesmenu_s challengesmenu; -static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) +static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh) { UINT8 i; SINT8 work; + if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0 + && ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS)) + challengesmenu.chaokeyadd = true; + + if (fresh && unlockid >= MAXUNLOCKABLES) + { + UINT8 selection[MAXUNLOCKABLES]; + UINT8 numunlocks = 0; + + // Get a random available unlockable. + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (!unlockables[i].conditionset) + { + continue; + } + + if (!gamedata->unlocked[i]) + { + continue; + } + + selection[numunlocks++] = i; + } + + if (!numunlocks) + { + // ...OK, get a random unlockable. + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (!unlockables[i].conditionset) + { + continue; + } + + selection[numunlocks++] = i; + } + } + + unlockid = selection[M_RandomKey(numunlocks)]; + } + if (unlockid >= MAXUNLOCKABLES) return; challengesmenu.currentunlock = unlockid; challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); - challengesmenu.unlockanim = 0; + challengesmenu.unlockanim = (challengesmenu.pending && !challengesmenu.chaokeyadd ? 0 : MAXUNLOCKTIME); if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL) return; @@ -161,12 +203,13 @@ static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) { - UINT8 i; - UINT16 newunlock = M_GetNextAchievedUnlock(); + UINT16 i, newunlock; M_UpdateUnlockablesAndExtraEmblems(false); - if ((challengesmenu.pending = (newunlock < MAXUNLOCKABLES))) + newunlock = M_GetNextAchievedUnlock(); + + if ((challengesmenu.pending = (newunlock != MAXUNLOCKABLES))) { S_StopMusic(); MISC_ChallengesDef.prevMenu = desiredmenu; @@ -177,6 +220,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) challengesmenu.ticker = 0; challengesmenu.requestflip = false; challengesmenu.requestnew = false; + challengesmenu.chaokeyadd = false; challengesmenu.currentunlock = MAXUNLOCKABLES; challengesmenu.unlockcondition = NULL; @@ -210,6 +254,9 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) if (challengesmenu.pending) M_ChallengesAutoFocus(newunlock, true); + else if (newunlock >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0 + && ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS)) + challengesmenu.chaokeyadd = true; return &MISC_ChallengesDef; } @@ -219,7 +266,6 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) void M_Challenges(INT32 choice) { - UINT8 i; (void)choice; M_InterruptMenuWithChallenges(NULL); @@ -227,40 +273,7 @@ void M_Challenges(INT32 choice) if (gamedata->challengegrid != NULL && !challengesmenu.pending) { - UINT8 selection[MAXUNLOCKABLES]; - UINT8 numunlocks = 0; - - // Get a random available unlockable. - for (i = 0; i < MAXUNLOCKABLES; i++) - { - if (!unlockables[i].conditionset) - { - continue; - } - - if (!gamedata->unlocked[i]) - { - continue; - } - - selection[numunlocks++] = i; - } - - if (!numunlocks) - { - // ...OK, get a random unlockable. - for (i = 0; i < MAXUNLOCKABLES; i++) - { - if (!unlockables[i].conditionset) - { - continue; - } - - selection[numunlocks++] = i; - } - } - - M_ChallengesAutoFocus(selection[M_RandomKey(numunlocks)], true); + M_ChallengesAutoFocus(UINT16_MAX, true); } M_SetupNextMenu(&MISC_ChallengesDef, false); @@ -270,7 +283,7 @@ void M_ChallengesTick(void) { const UINT8 pid = 0; UINT16 i; - UINT8 newunlock = MAXUNLOCKABLES; + UINT16 newunlock = MAXUNLOCKABLES; // Ticking challengesmenu.ticker++; @@ -280,8 +293,11 @@ void M_ChallengesTick(void) if (setup_explosions[i].tics > 0) setup_explosions[i].tics--; } - if (challengesmenu.unlockcount[CC_ANIM] > 0) - challengesmenu.unlockcount[CC_ANIM]--; + for (i = CC_ANIM; i < CC_MAX; i++) + { + if (challengesmenu.unlockcount[i] > 0) + challengesmenu.unlockcount[i]--; + } M_CupSelectTick(); // Update tile flip state. @@ -307,27 +323,61 @@ void M_ChallengesTick(void) } } - if (challengesmenu.pending) + if (challengesmenu.chaokeyadd == true) { - // Pending mode. - - if (challengesmenu.requestnew) + if (challengesmenu.ticker <= 5) + ; // recreate the slight delay the unlock fades provide + else if (gamedata->pendingkeyrounds == 0) { - // The menu apparatus is requesting a new unlock. - challengesmenu.requestnew = false; - if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES) + challengesmenu.chaokeyadd = false; + challengesmenu.requestnew = true; + } + else if ((gamedata->chaokeys + gamedata->usedkeys) >= GDMAX_CHAOKEYS) + { + gamedata->keyspending = 0; + gamedata->pendingkeyrounds = 0; + } + else + { + if (!(--gamedata->pendingkeyrounds & 1)) { - // We got one! - M_ChallengesAutoFocus(newunlock, false); + S_StartSound(NULL, sfx_ptally); } - else + + if (++gamedata->pendingkeyroundoffset >= GDCONVERT_ROUNDSTOKEY) { - // All done! Let's save the unlocks we've busted open. - challengesmenu.pending = false; - G_SaveGameData(true); + gamedata->pendingkeyroundoffset %= GDCONVERT_ROUNDSTOKEY; + + if (gamedata->keyspending > 0) + { + S_StartSound(NULL, sfx_achiev); + gamedata->keyspending--; + gamedata->chaokeys++; + challengesmenu.unlockcount[CC_CHAOANIM]++; + } } } - else if (challengesmenu.fade < 5) + } + else if (challengesmenu.requestnew) + { + // The menu apparatus is requesting a new unlock. + challengesmenu.requestnew = false; + if ((newunlock = M_GetNextAchievedUnlock()) != MAXUNLOCKABLES) + { + // We got one! + M_ChallengesAutoFocus(newunlock, false); + } + else + { + // All done! Let's save the unlocks we've busted open. + challengesmenu.pending = challengesmenu.chaokeyadd = false; + G_SaveGameData(true); + } + } + else if (challengesmenu.pending) + { + // Pending mode. + if (challengesmenu.fade < 5) { // Fade increase. challengesmenu.fade++; @@ -441,28 +491,58 @@ boolean M_ChallengesInputs(INT32 ch) const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0); (void) ch; - if (challengesmenu.fade) + if (challengesmenu.fade || challengesmenu.chaokeyadd) { ; } -#ifdef DEVELOP - else if (M_MenuExtraPressed(pid) && challengesmenu.extradata) // debugging + else if (M_MenuExtraPressed(pid) + && challengesmenu.extradata) { - if (challengesmenu.currentunlock < MAXUNLOCKABLES) - { - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = NULL; - gamedata->challengegridwidth = 0; - M_PopulateChallengeGrid(); - M_UpdateChallengeGridExtraData(challengesmenu.extradata); + i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy; - M_ChallengesAutoFocus(challengesmenu.currentunlock, true); + if (challengesmenu.currentunlock < MAXUNLOCKABLES + && !gamedata->unlocked[challengesmenu.currentunlock] + && !unlockables[challengesmenu.currentunlock].majorunlock + && ((challengesmenu.extradata[i].flags & CHE_HINT) + || (challengesmenu.unlockcount[CC_UNLOCKED] + challengesmenu.unlockcount[CC_TALLY] == 0)) + && gamedata->chaokeys > 0) + { + gamedata->chaokeys--; + gamedata->usedkeys++; + challengesmenu.unlockcount[CC_CHAOANIM]++; + + S_StartSound(NULL, sfx_chchng); challengesmenu.pending = true; + M_ChallengesAutoFocus(challengesmenu.currentunlock, false); + } + else + { + challengesmenu.unlockcount[CC_CHAONOPE] = 6; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kb2 +#if 0 // debugging + if (challengesmenu.currentunlock < MAXUNLOCKABLES) + { + if (gamedata->unlocked[challengesmenu.currentunlock] && challengesmenu.unlockanim >= UNLOCKTIME) + { + if (challengesmenu.unlockcount[CC_TALLY] > 0) + challengesmenu.unlockcount[CC_TALLY]--; + else + challengesmenu.unlockcount[CC_UNLOCKED]--; + } + + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + gamedata->challengegridwidth = 0; + M_PopulateChallengeGrid(); + M_UpdateChallengeGridExtraData(challengesmenu.extradata); + challengesmenu.pending = true; + M_ChallengesAutoFocus(challengesmenu.currentunlock, true); + } +#endif } return true; } -#endif else { if (M_MenuBackPressed(pid) || start)