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.
This commit is contained in:
toaster 2023-03-09 22:31:34 +00:00
parent 328ab0059a
commit 8b437d5a32
6 changed files with 237 additions and 78 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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

View file

@ -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;
}

View file

@ -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);

View file

@ -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)