RingRacers/src/menus/extras-challenges.c
James R 39f46a0f20 Replace music handling
(This commit does not compile. Sound test and tunes
command code needs to be ported after this.)

This is a big one. Here's the rundown:

The old music system was very direct, much of the time
just a proxy to the real sound API in i_sound.h.

You could change the music on command, but there wasn't
a consistent way to prevent some music from playing over
others. P_RestoreMusic is one example of needing to
address this problem. The jingles system was intended as
another solution. Furthermore, sound test (Stereo) has its
own needs.

I am removing all of that. Music handling in general is
now a very deliberate system, kind of similar to jingles.

In the new system, "tunes" are registered. The tune stores
info such as whether it should loop or fade out. Most of
the configuration is intended to be initialized only ONCE.
Tunes can be mapped to an actual music lump. They can be
remapped at any time too.

Tunes are also configured with a priority number. This
determines which tune is heard, if multiple are supposed
to be playing at a time. You can even tell a tune how long
it should play, so it's unnecessary to track this with
bespoke timers.
2023-08-06 17:31:45 -07:00

850 lines
20 KiB
C

/// \file menus/extras-challenges.c
/// \brief Challenges.
#include "../k_menu.h"
#include "../m_cond.h" // Condition Sets
#include "../m_random.h" // And just some randomness for the exits.
#include "../music.h"
#include "../z_zone.h"
#include "../r_skins.h"
#include "../s_sound.h"
//#define CHAOKEYDEBUG
menuitem_t MISC_ChallengesStatsDummyMenu[] =
{
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
};
menu_t MISC_ChallengesDef = {
sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t),
&MainDef,
0,
MISC_ChallengesStatsDummyMenu,
BASEVIDWIDTH/2, 30,
0, 0,
0,
"UNLOCK",
98, 0,
M_DrawChallenges,
M_ChallengesTick,
NULL,
NULL,
M_ChallengesInputs,
};
// This must be defined here so it can take sizeof
// MISC_ChallengesStatsDummyMenu :V
menu_t MISC_StatisticsDef = {
sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t),
&MainDef,
0,
MISC_ChallengesStatsDummyMenu,
280, 185,
0, 0,
0,
"EXTRAS",
98, 0,
M_DrawStatistics,
NULL,
NULL,
NULL,
M_StatisticsInputs,
};
struct challengesmenu_s challengesmenu;
static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
{
UINT16 i;
INT16 work;
if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0
&& (gamedata->chaokeys < GDMAX_CHAOKEYS))
challengesmenu.chaokeyadd = true;
if (fresh && unlockid >= MAXUNLOCKABLES)
{
UINT16 selection[MAXUNLOCKABLES];
UINT16 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;
if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition);
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
challengesmenu.unlockanim = (challengesmenu.pending && !challengesmenu.chaokeyadd ? 0 : MAXUNLOCKTIME);
if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL)
return;
for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++)
{
if (gamedata->challengegrid[i] != unlockid)
{
// Not what we're looking for.
continue;
}
if (challengesmenu.extradata[i].flags & CHE_CONNECTEDLEFT)
{
// no need to check for CHE_CONNECTEDUP in linear iteration
continue;
}
// Helper calculation for non-fresh scrolling.
work = (challengesmenu.col + challengesmenu.focusx);
challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT;
challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT;
// Begin animation
if (challengesmenu.pending)
{
challengesmenu.extradata[i].flip = (TILEFLIP_MAX/2);
}
if (fresh)
{
// We're just entering the menu. Immediately jump to the desired position...
challengesmenu.focusx = 0;
// ...and since the menu is even-width, randomly select whether it's left or right of center.
if (!unlockables[unlockid].majorunlock
&& M_RandomChance(FRACUNIT/2))
challengesmenu.focusx--;
}
else
{
// We're jumping between multiple unlocks in sequence. Get the difference (looped from -range/2 < work <= range/2).
work -= challengesmenu.col;
if (work <= -gamedata->challengegridwidth/2)
work += gamedata->challengegridwidth;
else if (work >= gamedata->challengegridwidth/2)
work -= gamedata->challengegridwidth;
if (work > 0)
{
// We only need to scroll as far as the rightward edge.
if (unlockables[unlockid].majorunlock)
{
work--;
challengesmenu.col++;
if (challengesmenu.col >= gamedata->challengegridwidth)
challengesmenu.col = 0;
}
// Offset right, scroll left?
if (work > LEFTUNLOCKSCROLL)
{
work -= LEFTUNLOCKSCROLL;
challengesmenu.focusx = LEFTUNLOCKSCROLL;
}
else
{
challengesmenu.focusx = work;
work = 0;
}
}
else if (work < 0)
{
// Offset left, scroll right?
if (work < -RIGHTUNLOCKSCROLL)
{
challengesmenu.focusx = -RIGHTUNLOCKSCROLL;
work += RIGHTUNLOCKSCROLL;
}
else
{
challengesmenu.focusx = work;
work = 0;
}
}
else
{
// We're right where we want to be.
challengesmenu.focusx = 0;
}
// And put the pixel-based scrolling in play, too.
challengesmenu.offset = -work*16;
}
break;
}
}
menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
{
UINT16 i, newunlock;
if (Playing())
return desiredmenu;
M_UpdateUnlockablesAndExtraEmblems(false, true);
newunlock = M_GetNextAchievedUnlock(true);
if ((challengesmenu.pending = (newunlock != MAXUNLOCKABLES)))
{
Music_StopAll();
MISC_ChallengesDef.prevMenu = desiredmenu;
}
if (challengesmenu.pending || desiredmenu == NULL)
{
challengesmenu.ticker = 0;
challengesmenu.requestflip = false;
challengesmenu.requestnew = false;
challengesmenu.chaokeyadd = false;
challengesmenu.chaokeyhold = 0;
challengesmenu.currentunlock = MAXUNLOCKABLES;
challengesmenu.unlockcondition = NULL;
M_PopulateChallengeGrid();
if (gamedata->challengegrid)
{
challengesmenu.extradata = Z_Calloc(
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(challengegridextradata_t)),
PU_STATIC, NULL);
M_UpdateChallengeGridExtraData(challengesmenu.extradata);
}
memset(setup_explosions, 0, sizeof(setup_explosions));
memset(&challengesmenu.unlockcount, 0, sizeof(challengesmenu.unlockcount));
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (!unlockables[i].conditionset)
{
continue;
}
challengesmenu.unlockcount[CC_TOTAL]++;
if (!gamedata->unlocked[i])
{
continue;
}
challengesmenu.unlockcount[CC_UNLOCKED]++;
}
if (challengesmenu.pending)
M_ChallengesAutoFocus(newunlock, true);
else if (newunlock >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0
&& (gamedata->chaokeys < GDMAX_CHAOKEYS))
challengesmenu.chaokeyadd = true;
return &MISC_ChallengesDef;
}
return desiredmenu;
}
void M_Challenges(INT32 choice)
{
(void)choice;
M_InterruptMenuWithChallenges(NULL);
MISC_ChallengesDef.prevMenu = currentMenu;
if (gamedata->challengegrid != NULL && !challengesmenu.pending)
{
M_ChallengesAutoFocus(UINT16_MAX, true);
}
M_SetupNextMenu(&MISC_ChallengesDef, false);
}
static boolean M_CanKeyHiliTile(boolean devskip)
{
// No keys to do it with?
if (gamedata->chaokeys == 0 && !devskip)
return false;
// No tile data?
if (challengesmenu.extradata == NULL)
return false;
// No selected tile?
if (challengesmenu.currentunlock >= MAXUNLOCKABLES)
return false;
// Already unlocked?
if (gamedata->unlocked[challengesmenu.currentunlock] == true)
return false;
// Marked as unskippable?
if (unlockables[challengesmenu.currentunlock].majorunlock == true && !devskip)
return false;
UINT16 i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy;
// Not a hinted tile OR a fresh board.
if (!(challengesmenu.extradata[i].flags & CHE_HINT)
&& (challengesmenu.unlockcount[CC_UNLOCKED] + challengesmenu.unlockcount[CC_TALLY] > 0)
&& !devskip)
return false;
// All good!
return true;
}
void M_ChallengesTick(void)
{
const UINT8 pid = 0;
UINT16 i;
UINT16 newunlock = MAXUNLOCKABLES;
// Ticking
challengesmenu.ticker++;
challengesmenu.offset /= 2;
for (i = 0; i < CSEXPLOSIONS; i++)
{
if (setup_explosions[i].tics > 0)
setup_explosions[i].tics--;
}
for (i = CC_ANIM; i < CC_MAX; i++)
{
if (challengesmenu.unlockcount[i] > 0)
challengesmenu.unlockcount[i]--;
}
M_CupSelectTick();
// Update tile flip state.
if (challengesmenu.extradata != NULL)
{
UINT16 id = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy;
boolean seeeveryone = challengesmenu.requestflip;
boolean allthewaythrough;
UINT8 maxflip;
for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++)
{
allthewaythrough = (!seeeveryone && !challengesmenu.pending && i != id);
maxflip = ((seeeveryone || !allthewaythrough) ? (TILEFLIP_MAX/2) : TILEFLIP_MAX);
if ((seeeveryone || (i == id) || (challengesmenu.extradata[i].flip > 0))
&& (challengesmenu.extradata[i].flip != maxflip))
{
challengesmenu.extradata[i].flip++;
if (challengesmenu.extradata[i].flip >= TILEFLIP_MAX)
{
challengesmenu.extradata[i].flip = 0;
}
}
}
}
if (challengesmenu.chaokeyhold)
{
boolean devskip = false;
#ifdef DEVELOP
devskip = M_MenuButtonHeld(pid, MBT_Z);
#endif
// A little messy, but don't freak out, this is just so devs don't crash the game on non-tiles
if ((devskip || M_MenuExtraHeld(pid)) && M_CanKeyHiliTile(devskip))
{
// Not pressed just this frame?
if (!M_MenuExtraPressed(pid))
{
challengesmenu.chaokeyhold++;
if (challengesmenu.chaokeyhold > CHAOHOLD_MAX)
{
#ifndef CHAOKEYDEBUG
gamedata->chaokeys--;
#endif
challengesmenu.chaokeyhold = 0;
challengesmenu.unlockcount[CC_CHAOANIM]++;
S_StartSound(NULL, sfx_chchng);
challengesmenu.pending = true;
//M_ChallengesAutoFocus(challengesmenu.currentunlock, false);
challengesmenu.unlockanim = UNLOCKTIME-1;
}
}
}
else
{
challengesmenu.chaokeyhold = 0;
challengesmenu.unlockcount[CC_CHAONOPE] = 6;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kb2
}
}
if ((challengesmenu.pending || challengesmenu.chaokeyhold) && challengesmenu.fade < 5)
{
// Fade increase.
challengesmenu.fade++;
}
else if (challengesmenu.chaokeyadd == true)
{
if (challengesmenu.ticker <= 5)
; // recreate the slight delay the unlock fades provide
else if (gamedata->pendingkeyrounds == 0)
{
gamedata->keyspending = 0;
gamedata->pendingkeyroundoffset %= GDCONVERT_ROUNDSTOKEY;
challengesmenu.chaokeyadd = false;
challengesmenu.requestnew = true;
}
else if (gamedata->chaokeys >= GDMAX_CHAOKEYS)
{
// The above condition will run on the next tic because of this set
gamedata->pendingkeyrounds = 0;
gamedata->pendingkeyroundoffset = 0;
}
else
{
UINT32 keyexchange = gamedata->keyspending;
if (keyexchange > gamedata->pendingkeyrounds)
{
keyexchange = 1;
}
else if (keyexchange >= GDCONVERT_ROUNDSTOKEY/2)
{
keyexchange = GDCONVERT_ROUNDSTOKEY/2;
}
keyexchange |= 1; // guarantee an odd delta for the sake of the sound
gamedata->pendingkeyrounds -= keyexchange;
gamedata->pendingkeyroundoffset += keyexchange;
if (!(gamedata->pendingkeyrounds & 1))
{
S_StartSound(NULL, sfx_ptally);
}
if (gamedata->pendingkeyroundoffset >= GDCONVERT_ROUNDSTOKEY)
{
gamedata->pendingkeyroundoffset %= GDCONVERT_ROUNDSTOKEY;
if (gamedata->keyspending > 0)
{
S_StartSound(NULL, sfx_achiev);
gamedata->keyspending--;
gamedata->chaokeys++;
challengesmenu.unlockcount[CC_CHAOANIM]++;
if (gamedata->musicstate < GDMUSIC_KEYG)
gamedata->musicstate = GDMUSIC_KEYG;
}
}
}
}
else if (challengesmenu.requestnew)
{
// The menu apparatus is requesting a new unlock.
challengesmenu.requestnew = false;
if ((newunlock = M_GetNextAchievedUnlock(false)) != MAXUNLOCKABLES)
{
// We got one!
M_ChallengesAutoFocus(newunlock, false);
}
else if (gamedata->pendingkeyrounds > 0
&& (gamedata->chaokeys < GDMAX_CHAOKEYS))
{
// Get ready to finish with pending chao key round tallying.
challengesmenu.chaokeyadd = true;
}
else
{
// All done! Let's save the unlocks we've busted open.
challengesmenu.pending = challengesmenu.chaokeyadd = false;
G_SaveGameData();
}
}
else if (challengesmenu.pending)
{
tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME;
if (++challengesmenu.unlockanim >= nexttime)
{
challengesmenu.requestnew = true;
}
if (challengesmenu.currentunlock < MAXUNLOCKABLES
&& challengesmenu.unlockanim == UNLOCKTIME)
{
// Unlock animation... also tied directly to the actual unlock!
gamedata->unlocked[challengesmenu.currentunlock] = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
// Update shown description just in case..?
if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition);
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
challengesmenu.unlockcount[CC_TALLY]++;
challengesmenu.unlockcount[CC_ANIM]++;
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)
{
bombcolor = ref->color;
}
else switch (ref->type)
{
case SECRET_SKIN:
{
INT32 skin = M_UnlockableSkinNum(ref);
if (skin != -1)
{
bombcolor = skins[skin].prefcolor;
}
break;
}
case SECRET_FOLLOWER:
{
INT32 fskin = M_UnlockableFollowerNum(ref);
if (fskin != -1)
{
INT32 psk = R_SkinAvailable(cv_skin[0].string);
if (psk == -1)
psk = 0;
bombcolor = K_GetEffectiveFollowerColor(followers[fskin].defaultcolor, &followers[fskin], cv_playercolor[0].value, &skins[psk]);
}
break;
}
default:
break;
}
if (bombcolor == SKINCOLOR_NONE)
{
bombcolor = cv_playercolor[0].value;
if (bombcolor == SKINCOLOR_NONE)
{
INT32 psk = R_SkinAvailable(cv_skin[0].string);
if (psk == -1)
psk = 0;
bombcolor = skins[psk].prefcolor;
}
}
i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0;
M_SetupReadyExplosions(false, challengesmenu.hilix, challengesmenu.hiliy+i, bombcolor);
if (ref->majorunlock)
{
M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor);
}
S_StartSound(NULL, sfx_s3k4e);
}
}
}
else if (!challengesmenu.chaokeyhold)
{
// Tick down the tally. (currently not visible)
/*if ((challengesmenu.ticker & 1)
&& challengesmenu.unlockcount[CC_TALLY] > 0)
{
challengesmenu.unlockcount[CC_TALLY]--;
challengesmenu.unlockcount[CC_UNLOCKED]++;
}*/
if (challengesmenu.fade > 0)
{
// Fade decrease.
if (--challengesmenu.fade == 0)
{
// Play music the moment control returns.
M_PlayMenuJam();
}
}
}
}
boolean M_ChallengesInputs(INT32 ch)
{
const UINT8 pid = 0;
UINT16 i;
const boolean start = M_MenuButtonPressed(pid, MBT_START);
const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0);
(void) ch;
if (challengesmenu.fade || challengesmenu.chaokeyadd || challengesmenu.chaokeyhold)
{
;
}
else if (M_MenuExtraPressed(pid))
{
if (M_CanKeyHiliTile(false))
{
challengesmenu.chaokeyhold = 1;
}
else
{
challengesmenu.unlockcount[CC_CHAONOPE] = 6;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kb2
#ifdef CHAOKEYDEBUG
if (challengesmenu.currentunlock < MAXUNLOCKABLES && challengesmenu.unlockanim >= UNLOCKTIME && gamedata->unlocked[challengesmenu.currentunlock] == true)
{
gamedata->unlocked[challengesmenu.currentunlock] = gamedata->unlockpending[challengesmenu.currentunlock] = false;
if (challengesmenu.unlockcount[CC_TALLY] > 0)
challengesmenu.unlockcount[CC_TALLY]--;
else
challengesmenu.unlockcount[CC_UNLOCKED]--;
}
#endif
}
return true;
}
#ifdef DEVELOP
else if (M_MenuButtonPressed(pid, MBT_Z))
{
challengesmenu.chaokeyhold = 1;
return true;
}
#endif
else
{
if (M_MenuBackPressed(pid) || start)
{
currentMenu->prevMenu = M_SpecificMenuRestore(currentMenu->prevMenu);
M_GoBack(0);
M_SetMenuDelay(pid);
Z_Free(challengesmenu.extradata);
challengesmenu.extradata = NULL;
challengesmenu.unlockcondition = NULL;
return true;
}
if (M_MenuButtonPressed(pid, MBT_R))
{
challengesmenu.requestflip ^= true;
return true;
}
if (challengesmenu.extradata != NULL && move)
{
challengesmenu.requestflip = false;
// Determine movement around the grid
// For right/down movement, we can pre-determine the number of steps based on extradata.
// For left/up movement, we can't - we have to be ready to iterate twice, and break early if we don't run into a large tile.
if (menucmd[pid].dpad_ud > 0)
{
i = 2;
while (i > 0)
{
if (challengesmenu.row < CHALLENGEGRIDHEIGHT-1)
{
challengesmenu.row++;
}
else
{
challengesmenu.row = 0;
}
if (!(challengesmenu.extradata[
(challengesmenu.col * CHALLENGEGRIDHEIGHT)
+ challengesmenu.row
].flags & CHE_CONNECTEDUP))
{
break;
}
i--;
}
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
else if (menucmd[pid].dpad_ud < 0)
{
i = (challengesmenu.extradata[
(challengesmenu.col * CHALLENGEGRIDHEIGHT)
+ challengesmenu.row
].flags & CHE_CONNECTEDUP) ? 2 : 1;
while (i > 0)
{
if (challengesmenu.row > 0)
{
challengesmenu.row--;
}
else
{
challengesmenu.row = CHALLENGEGRIDHEIGHT-1;
}
i--;
}
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
if (menucmd[pid].dpad_lr > 0)
{
i = 2;
while (i > 0)
{
// Slide the focus counter to movement, if we can.
if (challengesmenu.focusx > -RIGHTUNLOCKSCROLL)
{
challengesmenu.focusx--;
}
// Step the actual column right.
if (challengesmenu.col < gamedata->challengegridwidth-1)
{
challengesmenu.col++;
}
else
{
challengesmenu.col = 0;
}
if (!(challengesmenu.extradata[
(challengesmenu.col * CHALLENGEGRIDHEIGHT)
+ challengesmenu.row
].flags & CHE_CONNECTEDLEFT))
{
break;
}
i--;
}
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
else if (menucmd[pid].dpad_lr < 0)
{
i = (challengesmenu.extradata[
(challengesmenu.col * CHALLENGEGRIDHEIGHT)
+ challengesmenu.row
].flags & CHE_CONNECTEDLEFT) ? 2 : 1;
while (i > 0)
{
// Slide the focus counter to movement, if we can.
if (challengesmenu.focusx < LEFTUNLOCKSCROLL)
{
challengesmenu.focusx++;
}
// Step the actual column left.
if (challengesmenu.col > 0)
{
challengesmenu.col--;
}
else
{
challengesmenu.col = gamedata->challengegridwidth-1;
}
i--;
}
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
// After movement has been determined, figure out the current selection.
i = (challengesmenu.col * CHALLENGEGRIDHEIGHT) + challengesmenu.row;
challengesmenu.currentunlock = (gamedata->challengegrid[i]);
if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition);
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
challengesmenu.hilix = challengesmenu.col;
challengesmenu.hiliy = challengesmenu.row;
if (challengesmenu.currentunlock < MAXUNLOCKABLES
&& unlockables[challengesmenu.currentunlock].majorunlock)
{
// Adjust highlight coordinates up/to the left for large tiles.
if (challengesmenu.hiliy > 0 && (challengesmenu.extradata[i].flags & CHE_CONNECTEDUP))
{
challengesmenu.hiliy--;
}
if ((challengesmenu.extradata[i].flags & CHE_CONNECTEDLEFT))
{
if (challengesmenu.hilix > 0)
{
challengesmenu.hilix--;
}
else
{
challengesmenu.hilix = gamedata->challengegridwidth-1;
}
}
//i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy;
}
return true;
}
if (M_MenuConfirmPressed(pid)
&& challengesmenu.currentunlock < MAXUNLOCKABLES
&& gamedata->unlocked[challengesmenu.currentunlock])
{
switch (unlockables[challengesmenu.currentunlock].type)
{
case SECRET_ALTTITLE:
CV_AddValue(&cv_alttitle, 1);
S_StartSound(NULL, sfx_s3kc3s);
M_SetMenuDelay(pid);
break;
default:
break;
}
return true;
}
}
return true;
}