Merge branch 'more-ui' into 'master'

More UI

See merge request KartKrew/Kart!1280
This commit is contained in:
Oni 2023-06-13 06:33:42 +00:00
commit b6e2609873
23 changed files with 965 additions and 272 deletions

View file

@ -249,12 +249,6 @@ void D_ProcessEvents(void)
HandleGamepadDeviceEvents(ev);
if (gameaction == ga_nothing && gamestate == GS_TITLESCREEN)
{
if (cht_Responder(ev))
continue;
}
if (demo.savemode == DSM_TITLEENTRY)
{
if (G_DemoTitleResponder(ev))
@ -295,14 +289,6 @@ void D_ProcessEvents(void)
if (eaten)
continue; // menu ate the event
// Demo input:
/*
if (demo.playback)
if (M_DemoResponder(ev))
continue; // demo ate the event
*/
G_Responder(ev);
}

View file

@ -2585,6 +2585,13 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
//PARAMCHECK(1);
ty = UC_ADDON + offset;
}
else if (fastcmp(params[0], "PASSWORD"))
{
PARAMCHECK(1);
ty = UC_PASSWORD;
stringvar = Z_StrDup(params[1]);
re = -1;
}
else if ((offset=0) || fastcmp(params[0], "AND")
|| (++offset && fastcmp(params[0], "COMMA")))
{

View file

@ -4974,6 +4974,8 @@ void G_LoadGameData(void)
gamedata->challengegrid[i] = READUINT16(save.p);
}
}
M_SanitiseChallengeGrid();
}
else
{

View file

@ -4849,7 +4849,7 @@ K_drawMiniPing (void)
void K_drawButton(fixed_t x, fixed_t y, INT32 flags, patch_t *button[2], boolean pressed)
{
V_DrawFixedPatch(x, y, FRACUNIT, flags, button[pressed], NULL);
V_DrawFixedPatch(x, y, FRACUNIT, flags, button[(pressed == true) ? 1 : 0], NULL);
}
void K_drawButtonAnim(INT32 x, INT32 y, INT32 flags, patch_t *button[2], tic_t animtic)

View file

@ -131,8 +131,9 @@ void M_HandlePauseMenuGametype(INT32 choice);
typedef enum
{
MBF_UD_LR_FLIPPED = 1, // flip up-down and left-right axes
MBF_SOUNDLESS = 2, // do not play base menu sounds
MBF_UD_LR_FLIPPED = 1, // flip up-down and left-right axes
MBF_SOUNDLESS = 1<<1, // do not play base menu sounds
MBF_NOLOOPENTRIES = 1<<2, // do not loop M_NextOpt/M_PrevOpt
} menubehaviourflags_t;
struct menuitem_t
@ -424,6 +425,8 @@ extern menuitem_t MISC_ChallengesStatsDummyMenu[];
extern menu_t MISC_ChallengesDef;
extern menu_t MISC_StatisticsDef;
extern menu_t MISC_WrongWarpDef;
extern menuitem_t MISC_SoundTest[];
extern menu_t MISC_SoundTestDef;
@ -1035,6 +1038,7 @@ typedef enum
extras_statistics,
extras_eggtv,
extras_stereo,
extras_password,
} extras_e;
void M_InitExtras(INT32 choice); // init for the struct
@ -1098,6 +1102,7 @@ void M_AddonsRefresh(void);
void M_HandleAddons(INT32 choice);
char *M_AddonsHeaderPath(void);
extern consvar_t cv_dummyaddonsearch;
extern consvar_t cv_dummyextraspassword;
void M_Manual(INT32 choice);
void M_HandleImageDef(INT32 choice);
@ -1176,6 +1181,10 @@ void M_DrawAddons(void);
#define TILEFLIP_MAX 16
#define CHAOHOLD_MAX (3*TICRATE/2)
#define CHAOHOLD_BEGIN 7
#define CHAOHOLD_END 3
extern struct timeattackmenu_s {
tic_t ticker; // How long the menu's been open for
@ -1201,7 +1210,9 @@ extern struct challengesmenu_s {
boolean pending;
boolean requestnew;
boolean chaokeyadd;
UINT8 chaokeyhold;
boolean requestflip;
@ -1228,6 +1239,23 @@ void M_Statistics(INT32 choice);
void M_DrawStatistics(void);
boolean M_StatisticsInputs(INT32 ch);
#define MAXWRONGPLAYER MAXSPLITSCREENPLAYERS
#define WRONGPLAYEROFFSCREEN 48
extern struct wrongwarp_s {
INT32 ticker;
tic_t delaytowrongplayer;
struct wrongplayer_s
{
UINT8 skin;
INT16 across;
boolean spinout;
} wrongplayers[MAXWRONGPLAYER];
} wrongwarp;
void M_WrongWarp(INT32 choice);
void M_DrawWrongWarp(void);
typedef enum
{
stereospecial_none = 0,

View file

@ -393,13 +393,13 @@ static void M_DrawMenuTyping(void)
x = (BASEVIDWIDTH - boxwidth)/2;
y = 80;
if (menutyping.menutypingfade < 9)
y += (9-menutyping.menutypingfade)*10;
y += floor(pow(2, (double)(9 - menutyping.menutypingfade)));
else
y += (9-menutyping.menutypingfade);
if (currentMenu->menuitems[itemOn].text)
{
V_DrawThinString(x + 5, y - 2, highlightflags|V_ALLOWLOWERCASE, currentMenu->menuitems[itemOn].text);
V_DrawThinString(x + 5, y - 2, highlightflags|V_6WIDTHSPACE|V_ALLOWLOWERCASE, currentMenu->menuitems[itemOn].text);
}
M_DrawMenuTooltips();
@ -414,8 +414,12 @@ static void M_DrawMenuTyping(void)
V_DrawFill(x + 5 + boxwidth - 8, y + 4 + 5, 1, 8+6, 121);
V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
if (skullAnimCounter < 4)
if (skullAnimCounter < 4
&& menutyping.menutypingclose == false
&& menutyping.menutypingfade == (menutyping.keyboardtyping ? 9 : 18))
{
V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12 + 1, '_' | 0x80, false);
}
const INT32 buttonwidth = ((boxwidth + 1)/NUMVIRTUALKEYSINROW);
#define BUTTONHEIGHT (11)
@ -426,7 +430,12 @@ static void M_DrawMenuTyping(void)
if (menutyping.menutypingfade > 9)
{
y += 36 + 80 + (9-menutyping.menutypingfade)*10; // double yoffs for animation
y += 26;
if (menutyping.menutypingfade < 18)
{
y += floor(pow(2, (double)(18 - menutyping.menutypingfade))); // double yoffs for animation
}
INT32 tempkeyboardx = menutyping.keyboardx;
@ -575,14 +584,21 @@ static void M_DrawMenuTyping(void)
#undef BUTTONHEIGHT
y = 175;
if (menutyping.menutypingfade < 9)
{
y += 3 * (9 - menutyping.menutypingfade);
}
// Some contextual stuff
if (menutyping.keyboardtyping)
{
V_DrawThinString(returnx, 175, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_GRAYMAP, "Type using your keyboard. Press Enter to confirm & exit.\nUse your controller or any directional input to use the Virtual Keyboard.\n");
V_DrawThinString(returnx, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_GRAYMAP, "Type using your keyboard. Press Enter to confirm & exit.\nUse your controller or any directional input to use the Virtual Keyboard.\n");
}
else
{
V_DrawThinString(x, 175, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_GRAYMAP, "Type using the Virtual Keyboard. Use the \'OK\' button to confirm & exit.\nPress any keyboard key not bound to a control to use it.");
V_DrawThinString(returnx, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_GRAYMAP, "Type using the Virtual Keyboard. Use the \'OK\' button to confirm & exit.\nPress any keyboard key not bound to a control to use it.");
}
}
@ -1390,16 +1406,14 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
}
// returns false if the character couldn't be rendered
static boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, boolean charflip, boolean animate, INT32 addflags, UINT8 *colormap)
static boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, UINT8 spr2, UINT8 rotation, UINT32 frame, INT32 addflags, UINT8 *colormap)
{
UINT8 spr;
spritedef_t *sprdef;
spriteframe_t *sprframe;
patch_t *sprpatch;
UINT8 rotation = (charflip ? 1 : 7);
UINT32 frame = animate ? setup_animcounter : 0;
spr = P_GetSkinSprite2(&skins[skin], SPR2_STIN, NULL);
spr = P_GetSkinSprite2(&skins[skin], spr2, NULL);
sprdef = &skins[skin].sprites[spr];
if (!sprdef->numframes) // No frames ??
@ -1511,7 +1525,7 @@ static void M_DrawCharSelectSprite(UINT8 num, INT16 x, INT16 y, boolean charflip
color = p->color;
colormap = R_GetTranslationColormap(p->skin, color, GTC_MENUCACHE);
M_DrawCharacterSprite(x, y, p->skin, charflip, (p->mdepth == CSSTEP_READY), 0, colormap);
M_DrawCharacterSprite(x, y, p->skin, SPR2_STIN, (charflip ? 1 : 7), ((p->mdepth == CSSTEP_READY) ? setup_animcounter : 0), 0, colormap);
}
static void M_DrawCharSelectPreview(UINT8 num)
@ -1880,7 +1894,7 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
if (skinnum >= 0)
{
if (M_DrawCharacterSprite(x-22, y+119, skinnum, false, false, 0, colormap))
if (M_DrawCharacterSprite(x-22, y+119, skinnum, SPR2_STIN, 7, 0, 0, colormap))
V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], colormap);
}
@ -1912,7 +1926,7 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
(K_FollowerUsable(fln) ? TC_DEFAULT : TC_BLINK),
col, GTC_MENUCACHE);
if (M_DrawCharacterSprite(x-22, y+119, skinnum, false, false, 0, ccolormap))
if (M_DrawCharacterSprite(x-22, y+119, skinnum, SPR2_STIN, 7, 0, 0, ccolormap))
{
V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], ccolormap);
}
@ -2711,16 +2725,10 @@ void M_DrawTimeAttack(void)
// SPB Attack control hint + menu overlay
if (levellist.newgametype == GT_RACE && levellist.levelsearch.timeattack == true && M_SecretUnlocked(SECRET_SPBATTACK, true))
{
const UINT8 anim_duration = 16;
const UINT8 anim = (timeattackmenu.ticker % (anim_duration * 2)) < anim_duration;
INT32 buttonx = 162 + t;
INT32 buttony = timeheight;
if (anim)
V_DrawScaledPatch(buttonx + 35, buttony - 3, V_SNAPTOLEFT, W_CachePatchName("TLB_I", PU_CACHE));
else
V_DrawScaledPatch(buttonx + 35, buttony - 3, V_SNAPTOLEFT, W_CachePatchName("TLB_IB", PU_CACHE));
K_drawButtonAnim(buttonx + 35, buttony - 3, V_SNAPTOLEFT, kp_button_r, timeattackmenu.ticker);
if ((timeattackmenu.spbflicker == 0 || timeattackmenu.ticker % 2) == (cv_dummyspbattack.value == 1))
{
@ -4378,13 +4386,13 @@ void M_DrawPause(void)
if (smallroundpatch != NULL)
{
V_DrawMappedPatch(
24, 152,
24, 152 + offset/2,
0,
smallroundpatch,
NULL);
}
Y_RoundQueueDrawer(&standings, false, false);
Y_RoundQueueDrawer(&standings, offset/2, false, false);
}
}
@ -5007,7 +5015,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
if (skin != -1)
{
colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
M_DrawCharacterSprite(x, y, skin, false, false, 0, colormap);
M_DrawCharacterSprite(x, y, skin, SPR2_STIN, 7, 0, 0, colormap);
for (i = 0; i < skin; i++)
{
@ -5065,7 +5073,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
if (skin == -1)
skin = 0;
colormap = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_MENUCACHE);
M_DrawCharacterSprite(x, y, skin, false, false, 0, colormap);
M_DrawCharacterSprite(x, y, skin, SPR2_STIN, 7, 0, 0, colormap);
// Draw follower next to them
if (fskin != -1)
@ -5267,8 +5275,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
{
x = 8;
y = BASEVIDHEIGHT-16;
V_DrawGamemodeString(x, y - 32, V_ALLOWLOWERCASE, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_MENUCACHE), cv_alttitle.string);
V_DrawThinString(x, y, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Press (A)");
V_DrawGamemodeString(x, y - 33, V_ALLOWLOWERCASE, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_MENUCACHE), cv_alttitle.string);
K_drawButtonAnim(x, y, 0, kp_button_a[1], challengesmenu.ticker);
x += SHORT(kp_button_a[1][0]->width);
V_DrawThinString(x, y + 1, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Toggle");
break;
}
default:
@ -5333,7 +5346,9 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
void M_DrawChallenges(void)
{
INT32 x = currentMenu->x, explodex, selectx;
const UINT8 pid = 0;
INT32 x = currentMenu->x, explodex, selectx = 0, selecty = 0;
INT32 y;
INT16 i, j;
const char *str;
@ -5410,6 +5425,7 @@ void M_DrawChallenges(void)
}
selectx = explodex + (challengesmenu.hilix*challengesgridstep);
selecty = currentMenu->y + (challengesmenu.hiliy*challengesgridstep);
while (i >= 0 && x >= -(challengesgridstep*2))
{
@ -5447,49 +5463,12 @@ void M_DrawChallenges(void)
challengesmenu.hilix,
challengesmenu.hiliy,
selectx,
currentMenu->y + (challengesmenu.hiliy*challengesgridstep),
selecty,
true);
M_DrawCharSelectExplosions(false, explodex, currentMenu->y);
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;
if (gamedata->chaokeys > 9)
{
offs -= 6;
if (gamedata->chaokeys > 99)
offs -= 2; // as far as we can go
}
V_DrawFixedPatch((8+offs)*FRACUNIT, 5*FRACUNIT, FRACUNIT, 0, key, NULL);
V_DrawTimerString((27+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, va("%u", gamedata->chaokeys));
offs = challengekeybarwidth;
if (gamedata->chaokeys < 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",
challengesmenu.unlockcount[CC_UNLOCKED] + challengesmenu.unlockcount[CC_TALLY],
challengesmenu.unlockcount[CC_TOTAL]
);
V_DrawRightAlignedTimerString(BASEVIDWIDTH-7, 9-challengesmenu.unlockcount[CC_ANIM], 0, str);
}
// Name bar
{
y = 120;
@ -5511,6 +5490,94 @@ challengedesc:
V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str);
}
// Tally
{
str = va("%d/%d",
challengesmenu.unlockcount[CC_UNLOCKED] + challengesmenu.unlockcount[CC_TALLY],
challengesmenu.unlockcount[CC_TOTAL]
);
V_DrawRightAlignedTimerString(BASEVIDWIDTH-7, 9-challengesmenu.unlockcount[CC_ANIM], 0, str);
}
// Chao Keys
{
patch_t *key = W_CachePatchName("UN_CHA00", PU_CACHE);
INT32 offs = challengesmenu.unlockcount[CC_CHAONOPE];
if (offs & 1)
offs = -offs;
offs /= 2;
if (gamedata->chaokeys > 9)
{
offs -= 6;
if (gamedata->chaokeys > 99)
offs -= 2; // as far as we can go
}
fixed_t keyx = (8+offs)*FRACUNIT, keyy = 5*FRACUNIT;
const char *timerstr = va("%u", gamedata->chaokeys);
V_DrawTimerString((27+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, timerstr);
K_drawButton(
(27 + offs + V_TimerStringWidth(timerstr, 0) + 2) << FRACBITS,
11 << FRACBITS,
0, kp_button_c[1],
M_MenuExtraHeld(pid)
);
offs = challengekeybarwidth;
if (gamedata->chaokeys < 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);
if (challengesmenu.chaokeyhold)
{
fixed_t keyholdrotation = 0, radius = challengesgridstep;
if (challengesmenu.chaokeyhold < CHAOHOLD_BEGIN)
{
radius = (challengesmenu.chaokeyhold*radius)*(FRACUNIT/CHAOHOLD_BEGIN);
keyx += challengesmenu.chaokeyhold*((selectx*FRACUNIT) - keyx)/CHAOHOLD_BEGIN;
keyy += challengesmenu.chaokeyhold*((selecty*FRACUNIT) - keyy)/CHAOHOLD_BEGIN;
}
else
{
if (challengesmenu.chaokeyhold < CHAOHOLD_MAX - CHAOHOLD_END)
{
radius <<= FRACBITS;
keyholdrotation = 360 * ((challengesmenu.chaokeyhold - CHAOHOLD_BEGIN))
* (FRACUNIT/(CHAOHOLD_MAX - (CHAOHOLD_BEGIN + CHAOHOLD_END)));
}
else
{
radius = ((CHAOHOLD_MAX - challengesmenu.chaokeyhold)*radius)*(FRACUNIT/CHAOHOLD_END);
}
keyx = selectx*FRACUNIT;
keyy = selecty*FRACUNIT;
}
if (radius)
{
angle_t ang = (FixedAngle(
keyholdrotation
) >> ANGLETOFINESHIFT) & FINEMASK;
keyx += FixedMul(radius, FINESINE(ang));
keyy -= FixedMul(radius, FINECOSINE(ang));
}
}
V_DrawFixedPatch(keyx, keyy, FRACUNIT, 0, key, NULL);
}
// Derived from M_DrawCharSelectPreview
x = 40;
y = BASEVIDHEIGHT-16;
@ -5854,9 +5921,150 @@ void M_DrawStatistics(void)
#undef STATSSTEP
static INT32 M_WrongWarpFallingHelper(INT32 y, INT32 falltime)
{
if (wrongwarp.ticker < falltime)
{
return y;
}
if (wrongwarp.ticker > falltime + 2*TICRATE)
{
return INT32_MAX;
}
if (wrongwarp.ticker < falltime + TICRATE)
{
y += + ((wrongwarp.ticker - falltime) & 1 ? 1 : -1);
return y;
}
y += floor(pow(1.5, (double)(wrongwarp.ticker - (falltime + TICRATE))));
return y;
}
static void M_DrawWrongPlayer(UINT8 i)
{
#define wrongpl wrongwarp.wrongplayers[i]
if (wrongpl.skin >= numskins)
return;
UINT8 *colormap = R_GetTranslationColormap(wrongpl.skin, skins[wrongpl.skin].prefcolor, GTC_MENUCACHE);
M_DrawCharacterSprite(
wrongpl.across,
160 - ((i & 1) ? 0 : 32),
wrongpl.skin,
wrongpl.spinout ? SPR2_SPIN : SPR2_SLWN,
wrongpl.spinout ? ((wrongpl.across/8) & 7) : 6,
(wrongwarp.ticker+i),
0, colormap
);
#undef wrongpl
}
void M_DrawWrongWarp(void)
{
INT32 titleoffset = 0, titlewidth, x, y;
const char *titletext = "WRONG GAME? WRONG GAME! ";
if (gamestate == GS_MENU)
{
patch_t *pat, *pat2;
INT32 animtimer, anim2 = 0;
pat = W_CachePatchName("TITLEBG1", PU_CACHE);
pat2 = W_CachePatchName("TITLEBG2", PU_CACHE);
animtimer = ((wrongwarp.ticker*5)/16) % SHORT(pat->width);
anim2 = SHORT(pat2->width) - (((wrongwarp.ticker*5)/16) % SHORT(pat2->width));
// SRB2Kart: F_DrawPatchCol is over-engineered; recoded to be less shitty and error-prone
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0);
x = -((INT32)animtimer);
y = 0;
while (x < BASEVIDWIDTH)
{
V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, pat, NULL);
x += SHORT(pat->width);
}
x = -anim2;
y = BASEVIDHEIGHT - SHORT(pat2->height);
while (x < BASEVIDWIDTH)
{
V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, pat2, NULL);
x += SHORT(pat2->width);
}
}
{
patch_t *ttcheckers = W_CachePatchName("TTCHECK", PU_CACHE);
y = FixedMul(40<<FRACBITS, FixedDiv(wrongwarp.ticker%70, 70));
V_DrawSciencePatch(0, -y, 0, ttcheckers, FRACUNIT);
V_DrawSciencePatch(280<<FRACBITS, -(40<<FRACBITS) + y, 0, ttcheckers, FRACUNIT);
y = M_WrongWarpFallingHelper(36, 7*TICRATE/4);
if (y != INT32_MAX)
{
patch_t *ttbanner = W_CachePatchName("TTKBANNR", PU_CACHE);
V_DrawSmallScaledPatch(84, y, 0, ttbanner);
}
y = M_WrongWarpFallingHelper(87, 4*TICRATE/3);
if (y != INT32_MAX)
{
patch_t *ttkart = W_CachePatchName("TTKART", PU_CACHE);
V_DrawSmallScaledPatch(84, y, 0, ttkart);
}
}
if (wrongwarp.ticker < 2*TICRATE/3)
return;
V_DrawFadeScreen(31, min((wrongwarp.ticker - 2*TICRATE/3), 5));
// SMK title screen recreation!?
if (wrongwarp.ticker >= 2*TICRATE)
{
// Done as four calls and not a loop for the sake of render order
M_DrawWrongPlayer(0);
M_DrawWrongPlayer(2);
M_DrawWrongPlayer(1);
M_DrawWrongPlayer(3);
}
y = 20;
x = BASEVIDWIDTH - 8;
if (wrongwarp.ticker < TICRATE)
{
INT32 adjust = floor(pow(2, (double)(TICRATE - wrongwarp.ticker)));
x += adjust/2;
y += adjust;
}
titlewidth = V_LSTitleHighStringWidth(titletext, 0);
titleoffset = (-wrongwarp.ticker) % titlewidth;
while (titleoffset < BASEVIDWIDTH)
{
V_DrawLSTitleHighString(titleoffset, y, 0, titletext);
titleoffset += titlewidth;
}
patch_t *bumper = W_CachePatchName((cv_alttitle.value ? "MTSJUMPR1" : "MTSBUMPR1"), PU_CACHE);
V_DrawScaledPatch(x-(SHORT(bumper->width)), (BASEVIDHEIGHT-8)-(SHORT(bumper->height)), 0, bumper);
}
void M_DrawSoundTest(void)
{
UINT8 pid = 0; // todo: Add ability for any splitscreen player to bring up the menu.
const UINT8 pid = 0;
INT32 x, y, i, cursorx = 0;
INT32 titleoffset = 0, titlewidth;

View file

@ -159,10 +159,11 @@ boolean M_NextOpt(void)
if (itemOn + 1 > currentMenu->numitems - 1)
{
// Prevent looparound here
// If you're going to add any extra exceptions, DON'T.
// Add a "don't loop" flag to the menu_t struct instead.
if (currentMenu == &MISC_AddonsDef)
if (currentMenu->behaviourflags & MBF_NOLOOPENTRIES)
{
itemOn = oldItemOn;
return false;
}
itemOn = 0;
}
else
@ -186,10 +187,11 @@ boolean M_PrevOpt(void)
if (!itemOn)
{
// Prevent looparound here
// If you're going to add any extra exceptions, DON'T.
// Add a "don't loop" flag to the menu_t struct instead.
if (currentMenu == &MISC_AddonsDef)
if (currentMenu->behaviourflags & MBF_NOLOOPENTRIES)
{
itemOn = oldItemOn;
return false;
}
itemOn = currentMenu->numitems - 1;
}
else
@ -370,18 +372,19 @@ boolean M_Responder(event_t *ev)
void M_PlayMenuJam(void)
{
menu_t *refMenu = (menuactive ? currentMenu : restoreMenu);
static boolean loserclubpermitted = false;
boolean loserclub = (loserclubpermitted && (gamedata->musicflags & GDMUSIC_LOSERCLUB));
static boolean musicstatepermitted = false;
if (challengesmenu.pending)
{
S_StopMusic();
S_StopMusicCredit();
loserclubpermitted = true;
musicstatepermitted = true;
return;
}
gdmusic_t override = musicstatepermitted ? gamedata->musicstate : 0;
if (Playing() || soundtest.playing)
return;
@ -393,7 +396,7 @@ void M_PlayMenuJam(void)
S_StopMusicCredit();
return;
}
else if (!loserclub)
else if (override == 0)
{
if (NotCurrentlyPlaying(refMenu->music))
{
@ -404,12 +407,21 @@ void M_PlayMenuJam(void)
}
}
if (loserclub)
if (override != 0)
{
if (refMenu != NULL && NotCurrentlyPlaying("LOSERC"))
// See also gdmusic_t
const char* overridetotrack[GDMUSIC_MAX-1] = {
"KEYGEN",
"LOSERC",
};
if (refMenu != NULL && NotCurrentlyPlaying(overridetotrack[override - 1]))
{
S_ChangeMusicInternal("LOSERC", true);
S_ChangeMusicInternal(overridetotrack[override - 1], true);
S_ShowMusicCredit();
if (override < GDMUSIC_KEEPONMENU)
gamedata->musicstate = GDMUSIC_NONE;
}
return;
@ -487,6 +499,11 @@ menu_t *M_SpecificMenuRestore(menu_t *torestore)
// Ticker init
M_MPOptSelectInit(-1);
}
else if (torestore == &EXTRAS_MainDef)
{
// Disable or enable certain options
M_InitExtras(-1);
}
// One last catch.
M_SetupPlayMenu(-1);
@ -790,12 +807,12 @@ boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt)
return false;
}
return (menucmd[pid].buttons & bt);
return !!(menucmd[pid].buttons & bt);
}
boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt)
{
return (menucmd[pid].buttons & bt);
return !!(menucmd[pid].buttons & bt);
}
// Returns true if we press the confirmation button
@ -1190,5 +1207,7 @@ void M_Init(void)
CV_RegisterVar(&cv_dummyaddonsearch);
CV_RegisterVar(&cv_dummyextraspassword);
M_UpdateMenuBGImage(true);
}

View file

@ -68,9 +68,6 @@ static UINT8 cheatf_warp(void)
/*if (modifiedgame)
return 0;*/
if (menuactive && currentMenu != &MainDef)
return 0; // Only on the main menu!
// Unlock EVERYTHING.
for (i = 0; i < MAXUNLOCKABLES; i++)
{
@ -86,12 +83,40 @@ static UINT8 cheatf_warp(void)
if (success)
{
G_SetUsedCheats();
M_ClearMenus(true);
S_StartSound(0, sfx_kc42);
M_StartMessage(M_GetText(
"TOURNAMENT MODE\n\
\nAll challenges temporarily unlocked.\n\
Saving is disabled - the game will\n\
return to normal on next launch.\n\
\n\
Press (B)\n"
), NULL, MM_NOTHING);
}
else
{
S_StartSound(0, sfx_s3k7b);
M_StartMessage(M_GetText(
"TOURNAMENT MODE\n\
\nThis is the correct password, but.\n\
you already have every challenge\n\
unlocked, so saving is still allowed!\n\
\n\
Press (B)\n"
), NULL, MM_NOTHING);
}
// Refresh secrets menu existing.
M_ClearMenus(true);
M_StartControlPanel();
return 1;
}
static UINT8 cheatf_wrongwarp(void)
{
// Tee hee.
M_WrongWarp(0);
return 1;
}
@ -103,50 +128,51 @@ static UINT8 cheatf_devmode(void)
if (modifiedgame)
return 0;
if (menuactive && currentMenu != &MainDef)
return 0; // Only on the main menu!
S_StartSound(0, sfx_itemup);
// Just unlock all the things and turn on -debug and console devmode.
G_SetUsedCheats();
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (!unlockables[i].conditionset)
continue;
gamedata->unlocked[i] = true;
}
G_SetUsedCheats();
S_StartSound(0, sfx_kc42);
devparm = true;
cht_debug |= 0x8000;
// Refresh secrets menu existing.
M_ClearMenus(true);
M_StartControlPanel();
return 1;
}
#endif
static cheatseq_t cheat_warp = {
0, cheatf_warp,
//{ SCRAMBLE('r'), SCRAMBLE('e'), SCRAMBLE('d'), SCRAMBLE('x'), SCRAMBLE('v'), SCRAMBLE('i'), 0xff }
(UINT8[]){ SCRAMBLE('b'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), 0xff }
NULL, cheatf_warp,
(UINT8[]){ SCRAMBLE('p'), SCRAMBLE('l'), SCRAMBLE('a'), SCRAMBLE('c'), SCRAMBLE('e'), SCRAMBLE('h'), SCRAMBLE('o'), SCRAMBLE('l'), SCRAMBLE('d'), SCRAMBLE('e'), SCRAMBLE('r'), 0xff }
};
static cheatseq_t cheat_warp_joy = {
0, cheatf_warp,
/*{ SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_UPARROW),
SCRAMBLE(KEY_RIGHTARROW), SCRAMBLE(KEY_RIGHTARROW), SCRAMBLE(KEY_UPARROW),
SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_UPARROW),
SCRAMBLE(KEY_ENTER), 0xff }*/
(UINT8[]){ SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_UPARROW), SCRAMBLE(KEY_RIGHTARROW),
SCRAMBLE(KEY_RIGHTARROW), SCRAMBLE(KEY_UPARROW), SCRAMBLE(KEY_LEFTARROW),
SCRAMBLE(KEY_DOWNARROW), SCRAMBLE(KEY_RIGHTARROW),
SCRAMBLE(KEY_ENTER), 0xff }
static cheatseq_t cheat_wrongwarp = {
NULL, cheatf_wrongwarp,
(UINT8[]){ SCRAMBLE('b'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), 0xff }
};
#ifdef DEVELOP
static cheatseq_t cheat_devmode = {
0, cheatf_devmode,
NULL, cheatf_devmode,
(UINT8[]){ SCRAMBLE('d'), SCRAMBLE('e'), SCRAMBLE('v'), SCRAMBLE('m'), SCRAMBLE('o'), SCRAMBLE('d'), SCRAMBLE('e'), 0xff }
};
#endif
cheatseq_t *cheatseqlist[] =
{
&cheat_warp,
&cheat_wrongwarp,
#ifdef DEVELOP
&cheat_devmode,
#endif
NULL
};
// ==========================================================================
// CHEAT SEQUENCE PACKAGE
// ==========================================================================
@ -168,73 +194,64 @@ void cht_Init(void)
// Called in st_stuff module, which handles the input.
// Returns a 1 if the cheat was successful, 0 if failed.
//
static UINT8 cht_CheckCheat(cheatseq_t *cht, char key)
static UINT8 cht_CheckCheat(cheatseq_t *cht, char key, boolean shouldend)
{
UINT8 rc = 0;
UINT8 rc = 0; // end of sequence character
if (!cht->p)
cht->p = cht->sequence; // initialize if first time
if (cht->p == NULL || *cht->p == 0xff)
return rc;
if (*cht->p == 0)
*(cht->p++) = key;
else if (cheat_xlate_table[(UINT8)key] == *cht->p)
cht->p++;
else
cht->p = cht->sequence;
{
cht->p = NULL;
return rc;
}
if (*cht->p == 1)
cht->p++;
else if (*cht->p == 0xff) // end of sequence character
{
cht->p = cht->sequence;
if (shouldend && *cht->p == 0xff) // end of sequence character
rc = cht->func();
}
return rc;
}
boolean cht_Responder(event_t *ev)
boolean cht_Interpret(const char *password)
{
UINT8 ret = 0, ch = 0;
if (ev->type != ev_keydown)
if (!password)
return false;
if (ev->data1 > 0xFF)
{
// map some fake (joy) inputs into keys
// map joy inputs into keys
switch (ev->data1)
{
case KEY_JOY1:
case KEY_JOY1 + 2:
ch = KEY_ENTER;
break;
case KEY_HAT1:
ch = KEY_UPARROW;
break;
case KEY_HAT1 + 1:
ch = KEY_DOWNARROW;
break;
case KEY_HAT1 + 2:
ch = KEY_LEFTARROW;
break;
case KEY_HAT1 + 3:
ch = KEY_RIGHTARROW;
break;
default:
// no mapping
return false;
}
}
else
ch = (UINT8)ev->data1;
UINT8 ret = 0;
ret += cht_CheckCheat(&cheat_warp, (char)ch);
ret += cht_CheckCheat(&cheat_warp_joy, (char)ch);
#ifdef DEVELOP
ret += cht_CheckCheat(&cheat_devmode, (char)ch);
#endif
return (ret != 0);
size_t cheatseqid = 0;
const char *endofpassword = password;
while (*endofpassword && *(endofpassword+1))
endofpassword++;
cheatseqid = 0;
while (cheatseqlist[cheatseqid])
{
cheatseqlist[cheatseqid]->p = cheatseqlist[cheatseqid]->sequence;
cheatseqid++;
}
while (*password)
{
cheatseqid = 0;
while (cheatseqlist[cheatseqid])
{
ret += cht_CheckCheat(cheatseqlist[cheatseqid], *password, (password == endofpassword));
cheatseqid++;
}
password++;
}
return (ret > 0);
}
// Console cheat commands rely on these a lot...

View file

@ -46,7 +46,7 @@ typedef enum {
//
// Cheat sequences
//
boolean cht_Responder(event_t *ev);
boolean cht_Interpret(const char *password);
void cht_Init(void);
//

View file

@ -312,6 +312,88 @@ quickcheckagain:
}
}
void M_SanitiseChallengeGrid(void)
{
UINT8 seen[MAXUNLOCKABLES];
UINT16 empty[MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)];
UINT16 i, j, numempty = 0;
if (gamedata->challengegrid == NULL)
return;
memset(seen, 0, sizeof(seen));
// Go through all spots to identify duplicates and absences.
for (j = 0; j < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; j++)
{
i = gamedata->challengegrid[j];
if (i >= MAXUNLOCKABLES || !unlockables[i].conditionset)
{
empty[numempty++] = j;
continue;
}
if (seen[i] != 5) // Arbitrary cap greater than 4
{
seen[i]++;
if (seen[i] == 1 || unlockables[i].majorunlock)
{
continue;
}
}
empty[numempty++] = j;
}
// Go through unlockables to identify if any haven't been seen.
for (i = 0; i < MAXUNLOCKABLES; ++i)
{
if (!unlockables[i].conditionset)
{
continue;
}
if (unlockables[i].majorunlock && seen[i] != 4)
{
// Probably not enough spots to retrofit.
goto badgrid;
}
if (seen[i] != 0)
{
// Present on the challenge grid.
continue;
}
if (numempty != 0)
{
// Small ones can be slotted in easy.
j = empty[--numempty];
gamedata->challengegrid[j] = i;
}
// Nothing we can do to recover.
goto badgrid;
}
// Fill the remaining spots with empties.
while (numempty != 0)
{
j = empty[--numempty];
gamedata->challengegrid[j] = MAXUNLOCKABLES;
}
return;
badgrid:
// Just remove everything and let it get regenerated.
Z_Free(gamedata->challengegrid);
gamedata->challengegrid = NULL;
gamedata->challengegridwidth = 0;
}
void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata)
{
UINT16 i, j, num, id, tempid, work;
@ -531,7 +613,7 @@ void M_ClearStats(void)
gamedata->eversavedreplay = false;
gamedata->everseenspecial = false;
gamedata->evercrashed = false;
gamedata->musicflags = 0;
gamedata->musicstate = GDMUSIC_NONE;
gamedata->importprofilewins = false;
}
@ -758,10 +840,13 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
case UC_CRASH:
if (gamedata->evercrashed)
{
gamedata->musicflags |= GDMUSIC_LOSERCLUB;
if (gamedata->musicstate < GDMUSIC_LOSERCLUB)
gamedata->musicstate = GDMUSIC_LOSERCLUB;
return true;
}
return false;
case UC_PASSWORD:
return (cn->stringvar == NULL);
// Just for string building
case UC_AND:
@ -1081,7 +1166,7 @@ static const char *M_GetConditionString(condition_t *cn)
case UC_ALLSUPER:
case UC_ALLEMERALDS:
{
const char *chaostext, *speedtext = "", *orbetter = "";
const char *chaostext, *speedtext = "";
if (!gamedata->everseenspecial)
return NULL;
@ -1093,17 +1178,14 @@ static const char *M_GetConditionString(condition_t *cn)
else
chaostext = "14";
if (cn->requirement == KARTSPEED_NORMAL)
/*if (cn->requirement == KARTSPEED_NORMAL) -- Emeralds can not be collected on Easy
{
speedtext = " on Normal difficulty";
//if (M_SecretUnlocked(SECRET_HARDSPEED, true))
orbetter = " or better";
}
else if (cn->requirement == KARTSPEED_HARD)
else*/
if (cn->requirement == KARTSPEED_HARD)
{
speedtext = " on Hard difficulty";
if (M_SecretUnlocked(SECRET_MASTERMODE, true))
orbetter = " or better";
}
else if (cn->requirement == KARTGP_MASTER)
{
@ -1113,7 +1195,7 @@ static const char *M_GetConditionString(condition_t *cn)
speedtext = " on ???";
}
return va("collect all %s Emeralds%s%s", chaostext, speedtext, orbetter);
return va("GRAND PRIX: collect all %s Emeralds%s", chaostext, speedtext);
}
case UC_TOTALMEDALS: // Requires number of emblems >= x
@ -1213,6 +1295,8 @@ static const char *M_GetConditionString(condition_t *cn)
if (gamedata->evercrashed)
return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash";
return NULL;
case UC_PASSWORD:
return "enter a secret password";
case UC_AND:
return "&";
@ -1265,7 +1349,7 @@ static const char *M_GetConditionString(condition_t *cn)
if (cn->requirement == KARTSPEED_NORMAL)
{
speedtext = "on Normal difficulty or better";
speedtext = "on Normal difficulty";
}
else if (cn->requirement == KARTSPEED_HARD)
{
@ -1322,7 +1406,10 @@ static const char *M_GetConditionString(condition_t *cn)
{
if (cup->id != cn->requirement)
continue;
return va("%s%s %s CUP", completetype, orbetter, cup->name);
return va("%s%s %s CUP",
completetype, orbetter,
(M_CupLocked(cup) ? "???" : cup->name)
);
}
return va("INVALID CUP CONDITION \"%d:%d\"", cn->type, cn->requirement);
}
@ -1528,7 +1615,7 @@ char *M_BuildConditionSetString(UINT16 unlockid)
static boolean M_CheckUnlockConditions(player_t *player)
{
INT32 i;
UINT32 i;
conditionset_t *c;
boolean ret;
@ -1547,6 +1634,43 @@ static boolean M_CheckUnlockConditions(player_t *player)
return ret;
}
boolean M_ConditionInterpret(const char *password)
{
UINT32 i, j;
conditionset_t *c;
condition_t *cn;
for (i = 0; i < MAXCONDITIONSETS; ++i)
{
c = &conditionSets[i];
if (!c->numconditions || gamedata->achieved[i])
continue;
for (j = 0; j < c->numconditions; ++j)
{
cn = &c->condition[j];
if (cn->type != UC_PASSWORD)
continue;
if (cn->stringvar == NULL)
continue;
if (stricmp(cn->stringvar, password))
continue;
// Remove the password for this session.
Z_Free(cn->stringvar);
cn->stringvar = NULL;
return true;
}
}
return false;
}
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall)
{
UINT16 i = 0, response = 0, newkeys = 0;
@ -1650,32 +1774,57 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall)
return false;
}
UINT16 M_GetNextAchievedUnlock(void)
UINT16 M_GetNextAchievedUnlock(boolean canskipchaokeys)
{
UINT16 i;
// Go through unlockables
for (i = 0; i < MAXUNLOCKABLES; ++i)
{
if (gamedata->unlocked[i] || !unlockables[i].conditionset)
if (!unlockables[i].conditionset)
{
// Not worthy of consideration
continue;
}
if (gamedata->unlocked[i] == true)
{
// Already unlocked, no need to engage
continue;
}
if (gamedata->unlockpending[i] == false)
{
// Not unlocked AND not pending, which means chao keys can be used on something
canskipchaokeys = false;
continue;
}
return i;
}
if (gamedata->keyspending != 0)
if (canskipchaokeys == true)
{
// Okay, we're skipping chao keys - let's just insta-digest them.
if (gamedata->chaokeys + gamedata->keyspending < GDMAX_CHAOKEYS)
{
gamedata->chaokeys += gamedata->keyspending;
gamedata->pendingkeyroundoffset =
(gamedata->pendingkeyroundoffset + gamedata->pendingkeyrounds)
% GDCONVERT_ROUNDSTOKEY;
}
else
{
gamedata->chaokeys = GDMAX_CHAOKEYS;
gamedata->pendingkeyroundoffset = 0;
}
gamedata->keyspending = 0;
gamedata->pendingkeyrounds = 0;
}
else if (gamedata->keyspending != 0)
{
return PENDING_CHAOKEYS;
}

View file

@ -55,6 +55,8 @@ typedef enum
UC_REPLAY, // Save a replay
UC_CRASH, // Hee ho !
UC_PASSWORD, // Type in something funny
// Just for string building
UC_AND,
UC_COMMA,
@ -218,7 +220,14 @@ typedef enum
#endif
#define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH)
#define GDMUSIC_LOSERCLUB 0x01
// See also M_PlayMenuJam
typedef enum {
GDMUSIC_NONE = 0,
GDMUSIC_KEYG,
GDMUSIC_KEEPONMENU, // Minimum to keep after leaving the Challenge Grid
GDMUSIC_LOSERCLUB = GDMUSIC_KEEPONMENU,
GDMUSIC_MAX
} gdmusic_t;
// This is the largest number of 9s that will fit in UINT32 and UINT16 respectively.
#define GDMAX_RINGS 999999999
@ -281,7 +290,7 @@ struct gamedata_t
boolean eversavedreplay;
boolean everseenspecial;
boolean evercrashed;
UINT8 musicflags;
gdmusic_t musicstate;
// BACKWARDS COMPAT ASSIST
boolean importprofilewins;
@ -302,6 +311,7 @@ void M_NewGameDataStruct(void);
// Challenges menu stuff
void M_PopulateChallengeGrid(void);
void M_SanitiseChallengeGrid(void);
struct challengegridextradata_t
{
@ -332,11 +342,12 @@ void M_ClearStats(void);
boolean M_NotFreePlay(player_t *player);
// Updating conditions and unlockables
boolean M_ConditionInterpret(const char *password);
boolean M_CheckCondition(condition_t *cn, player_t *player);
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall);
#define PENDING_CHAOKEYS (UINT16_MAX-1)
UINT16 M_GetNextAchievedUnlock(void);
UINT16 M_GetNextAchievedUnlock(boolean canskipchaokeys);
UINT16 M_CheckLevelEmblems(void);
UINT16 M_CompletionEmblems(void);

View file

@ -4,6 +4,7 @@ target_sources(SRB2SDL2 PRIVATE
extras-challenges.c
extras-egg-tv.cpp
extras-statistics.c
extras-wrong.c
main-1.c
main-profile-select.c
options-1.c

View file

@ -3,6 +3,7 @@
#include "../k_menu.h"
#include "../m_cond.h"
#include "../m_cheat.h"
#include "../s_sound.h"
menuitem_t EXTRAS_Main[] =
@ -14,7 +15,7 @@ menuitem_t EXTRAS_Main[] =
{IT_STRING | IT_CALL, NULL, NULL,
NULL, {.routine = M_Addons}, 0, 0},
{IT_STRING | IT_CALL, "Tutorial", "Help Dr. Eggman and Tails test out their new Ring Racers.",
{IT_STRING | IT_CALL, "Tutorial", "Help Dr. Robotnik and Tails test out their new Ring Racers.",
NULL, {.routine = M_LevelSelectInit}, 0, GT_TUTORIAL},
{IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!",
@ -28,6 +29,9 @@ menuitem_t EXTRAS_Main[] =
{IT_STRING | IT_CALL, NULL, NULL,
NULL, {.routine = M_SoundTest}, 0, 0},
{IT_STRING | IT_CVAR | IT_CV_STRING, "Password", "If you don't know any passwords, come back later!",
NULL, {.cvar = &cv_dummyextraspassword}, 0, 0},
};
// the extras menu essentially reuses the options menu stuff
@ -53,18 +57,10 @@ menu_t EXTRAS_MainDef = {
struct extrasmenu_s extrasmenu;
consvar_t cv_dummyextraspassword = CVAR_INIT ("dummyextraspassword", "", CV_HIDDEN, NULL, NULL);
void M_InitExtras(INT32 choice)
{
(void)choice;
extrasmenu.ticker = 0;
extrasmenu.offset = 0;
extrasmenu.extx = 0;
extrasmenu.exty = 0;
extrasmenu.textx = 0;
extrasmenu.texty = 0;
// Addons
if (M_SecretUnlocked(SECRET_ADDONS, true))
{
@ -127,6 +123,17 @@ void M_InitExtras(INT32 choice)
EXTRAS_Main[extras_stereo].text = EXTRAS_Main[extras_stereo].tooltip = "???";
}
if (choice == -1)
return;
extrasmenu.ticker = 0;
extrasmenu.offset = 0;
extrasmenu.extx = 0;
extrasmenu.exty = 0;
extrasmenu.textx = 0;
extrasmenu.texty = 0;
M_SetupNextMenu(&EXTRAS_MainDef, false);
}
@ -163,6 +170,23 @@ void M_ExtrasTick(void)
extrasmenu.textx = 160;
extrasmenu.texty = 50;
}
if (menutyping.active == false && cv_dummyextraspassword.string[0] != '\0')
{
if (M_ConditionInterpret(cv_dummyextraspassword.string) == true)
{
if (M_UpdateUnlockablesAndExtraEmblems(true, true))
{
M_Challenges(0);
}
}
else if (cht_Interpret(cv_dummyextraspassword.string) == true && menuactive == true)
{
M_InitExtras(-1);
}
CV_StealthSet(&cv_dummyextraspassword, "");
}
}
boolean M_ExtrasInputs(INT32 ch)

View file

@ -23,7 +23,7 @@ menu_t MISC_AddonsDef = {
MISC_AddonsMenu,
50, 28,
0, 0,
0,
MBF_NOLOOPENTRIES,
"EXTRAS",
0, 0,
M_DrawAddons,
@ -341,6 +341,9 @@ void M_HandleAddons(INT32 choice)
// Secret menu!
//MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
// I could guard it, but let's just always do this.
M_InitExtras(-1);
if (currentMenu->prevMenu)
M_SetupNextMenu(M_InterruptMenuWithChallenges(currentMenu->prevMenu), false);
else

View file

@ -8,6 +8,8 @@
#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},
@ -212,7 +214,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
M_UpdateUnlockablesAndExtraEmblems(false, true);
newunlock = M_GetNextAchievedUnlock();
newunlock = M_GetNextAchievedUnlock(true);
if ((challengesmenu.pending = (newunlock != MAXUNLOCKABLES)))
{
@ -226,6 +228,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
challengesmenu.requestflip = false;
challengesmenu.requestnew = false;
challengesmenu.chaokeyadd = false;
challengesmenu.chaokeyhold = 0;
challengesmenu.currentunlock = MAXUNLOCKABLES;
challengesmenu.unlockcondition = NULL;
@ -284,6 +287,39 @@ void M_Challenges(INT32 choice)
M_SetupNextMenu(&MISC_ChallengesDef, false);
}
static boolean M_CanKeyHiliTile(void)
{
// No keys to do it with?
if (gamedata->chaokeys == 0)
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)
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))
return false;
// All good!
return true;
}
void M_ChallengesTick(void)
{
const UINT8 pid = 0;
@ -328,7 +364,40 @@ void M_ChallengesTick(void)
}
}
if (challengesmenu.pending && challengesmenu.fade < 5)
if (challengesmenu.chaokeyhold)
{
if (M_MenuExtraHeld(pid) && M_CanKeyHiliTile())
{
// 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++;
@ -384,6 +453,9 @@ void M_ChallengesTick(void)
gamedata->keyspending--;
gamedata->chaokeys++;
challengesmenu.unlockcount[CC_CHAOANIM]++;
if (gamedata->musicstate < GDMUSIC_KEYG)
gamedata->musicstate = GDMUSIC_KEYG;
}
}
}
@ -392,7 +464,7 @@ void M_ChallengesTick(void)
{
// The menu apparatus is requesting a new unlock.
challengesmenu.requestnew = false;
if ((newunlock = M_GetNextAchievedUnlock()) != MAXUNLOCKABLES)
if ((newunlock = M_GetNextAchievedUnlock(false)) != MAXUNLOCKABLES)
{
// We got one!
M_ChallengesAutoFocus(newunlock, false);
@ -486,7 +558,7 @@ void M_ChallengesTick(void)
}
}
}
else
else if (!challengesmenu.chaokeyhold)
{
// Tick down the tally. (currently not visible)
@ -517,52 +589,30 @@ boolean M_ChallengesInputs(INT32 ch)
const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0);
(void) ch;
if (challengesmenu.fade || challengesmenu.chaokeyadd)
if (challengesmenu.fade || challengesmenu.chaokeyadd || challengesmenu.chaokeyhold)
{
;
}
else if (M_MenuExtraPressed(pid)
&& challengesmenu.extradata)
else if (M_MenuExtraPressed(pid))
{
i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy;
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)
if (M_CanKeyHiliTile())
{
gamedata->chaokeys--;
challengesmenu.unlockcount[CC_CHAOANIM]++;
S_StartSound(NULL, sfx_chchng);
challengesmenu.pending = true;
M_ChallengesAutoFocus(challengesmenu.currentunlock, false);
challengesmenu.chaokeyhold = 1;
}
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);
#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
}

153
src/menus/extras-wrong.c Normal file
View file

@ -0,0 +1,153 @@
/// \file menus/extras-wrong.c
/// \brief Wrongwarp
#include "../k_menu.h"
#include "../s_sound.h"
#include "../m_random.h"
#include "../r_skins.h"
struct wrongwarp_s wrongwarp;
static menuitem_t MISC_WrongWarpMenu[] =
{
{IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0},
};
void M_WrongWarp(INT32 choice)
{
(void)choice;
wrongwarp.ticker = 0;
M_SetupNextMenu(&MISC_WrongWarpDef, false);
// Done here to avoid immediate music credit
S_ChangeMusicInternal("YEAWAY", true);
}
static void M_WrongWarpTick(void)
{
static boolean firsteggman = true;
static boolean antitailgate = false;
UINT8 i, j;
wrongwarp.ticker++;
if (wrongwarp.ticker < 2*TICRATE)
return;
if (wrongwarp.ticker == 2*TICRATE)
{
S_ShowMusicCredit();
for (i = 0; i < MAXWRONGPLAYER; i++)
wrongwarp.wrongplayers[i].skin = MAXSKINS;
firsteggman = true;
}
// SMK title screen recreation!?
for (i = 0; i < MAXWRONGPLAYER; i++)
{
if (wrongwarp.wrongplayers[i].skin == MAXSKINS)
continue;
wrongwarp.wrongplayers[i].across += 5;
if (wrongwarp.wrongplayers[i].across < BASEVIDWIDTH + WRONGPLAYEROFFSCREEN)
continue;
wrongwarp.wrongplayers[i].skin = MAXSKINS;
}
if (wrongwarp.delaytowrongplayer)
{
wrongwarp.delaytowrongplayer--;
return;
}
wrongwarp.delaytowrongplayer = M_RandomRange(TICRATE/3, 2*TICRATE/3);
if (wrongwarp.ticker == 2*TICRATE)
return;
UINT32 rskin = 0;
if (firsteggman == true)
{
// Eggman always leads the pack. It's not Sonic's game anymore...
firsteggman = false;
i = 0;
wrongwarp.wrongplayers[i].spinout = false;
}
else
{
rskin = R_GetLocalRandomSkin();
for (i = 0; i < MAXWRONGPLAYER; i++)
{
// Already in use.
if (wrongwarp.wrongplayers[i].skin == rskin)
return;
// Prevent tailgating.
if (antitailgate == !!(i & 1))
continue;
// Slot isn't free.
if (wrongwarp.wrongplayers[i].skin != MAXSKINS)
continue;
break;
}
// No free slots.
if (i == MAXWRONGPLAYER)
return;
// Check to see if any later entry uses the skin too
for (j = i+1; j < MAXWRONGPLAYER; j++)
{
if (wrongwarp.wrongplayers[j].skin != rskin)
continue;
return;
}
wrongwarp.wrongplayers[i].spinout = M_RandomChance(FRACUNIT/11);
}
// Add the new character!
wrongwarp.wrongplayers[i].skin = rskin;
wrongwarp.wrongplayers[i].across = -WRONGPLAYEROFFSCREEN;
antitailgate = !!(i & 1);
}
static boolean M_WrongWarpInputs(INT32 ch)
{
(void)ch;
if (wrongwarp.ticker < TICRATE/2)
return true;
return false;
}
menu_t MISC_WrongWarpDef = {
sizeof (MISC_WrongWarpMenu)/sizeof (menuitem_t),
NULL,
0,
MISC_WrongWarpMenu,
0, 0,
0, 0,
MBF_SOUNDLESS,
".",
98, 0,
M_DrawWrongWarp,
M_WrongWarpTick,
NULL,
NULL,
M_WrongWarpInputs,
};

View file

@ -347,7 +347,7 @@ boolean M_LevelListFromGametype(INT16 gt)
templevelsearch.cup = &dummy_lostandfound;
templevelsearch.checklocked = true;
if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) != NEXTMAP_INVALID)
if (M_GetFirstLevelInList(&temp, &templevelsearch) != NEXTMAP_INVALID)
{
foundany = true;
GRID_INSERTCUP;

View file

@ -7813,7 +7813,7 @@ static void P_InitGametype(void)
// Started a game? Move on to the next jam when you go back to the title screen
CV_SetValue(&cv_menujam_update, 1);
gamedata->musicflags = 0;
gamedata->musicstate = GDMUSIC_NONE;
}
struct minimapinfo minimapinfo;

View file

@ -363,8 +363,12 @@ void S_StopSoundByID(void *origin, sfxenum_t sfx_id)
// Sounds without origin can have multiple sources, they shouldn't
// be stopped by new sounds.
// (The above comment predates this codebase using git and cannot be BLAME'd)
// ...yeah, but if it's being stopped by ID, it's clearly an intentful effect. ~toast 090623
#if 0
if (!origin)
return;
#endif
#ifdef HW3SOUND
if (hws_mode != HWS_DEFAULT_MODE)
{

View file

@ -1102,6 +1102,7 @@ sfxinfo_t S_sfx[NUMSFX] =
{"typri2", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SA2 final boss-type typewriting
{"eggspr", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Sonic Unleashed Trap Spring
{"achiev", false, 204, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Achievement"},
{"gpmetr", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // End of a "Tutorial Teleport"
{"endwrp", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // End of a "Tutorial Teleport"
// SRB2Kart - Drop target sounds

View file

@ -1169,6 +1169,7 @@ typedef enum
sfx_typri2,
sfx_eggspr,
sfx_achiev,
sfx_gpmetr,
sfx_endwrp,
// SRB2Kart - Drop target sounds

View file

@ -449,7 +449,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
patch_t *resbar = W_CachePatchName("R_RESBAR", PU_PATCH); // Results bars for players
if (drawping || data.rankingsmode != 0)
if (drawping || standings->rankingsmode != 0)
{
inwardshim = 8;
}
@ -604,15 +604,15 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
);
}
}
else if (data.rankingsmode != 0)
else if (standings->rankingsmode != 0)
{
char *increasenum = NULL;
if (data.increase[pnum] != INT16_MIN)
if (standings->increase[pnum] != INT16_MIN)
{
increasenum = va(
"(%d)",
data.increase[pnum]
standings->increase[pnum]
);
}
@ -660,9 +660,9 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
// Y_RoundQueueDrawer
//
// Handles drawing the bottom-of-screen progression.
// Currently requires intermission y_data to be active, but abstraction is feasible.
// Currently requires intermission y_data for animation only.
//
void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean widescreen)
void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen)
{
if (roundqueue.size == 0)
{
@ -766,7 +766,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
INT32 widthofroundqueue = 24*(workingqueuesize - 1);
INT32 x = (BASEVIDWIDTH - widthofroundqueue) / 2;
INT32 y;
INT32 y, basey = 167 + offset;
INT32 spacetospecial = 0;
@ -829,13 +829,13 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
if (upwa == true)
{
xiter -= 24;
V_DrawMappedPatch(xiter, 167, baseflags, queuebg_upwa, greymap);
V_DrawMappedPatch(xiter, basey, baseflags, queuebg_upwa, greymap);
}
while (xiter > -bufferspace)
{
xiter -= 24;
V_DrawMappedPatch(xiter, 167, baseflags, queuebg_flat, greymap);
V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap);
}
for (i = 0; i < workingqueuesize; i++)
@ -846,9 +846,9 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
upwa ^= true;
if (upwa == false)
{
y = 171;
y = basey + 4;
V_DrawMappedPatch(x, 167, baseflags, queuebg_down, greymap);
V_DrawMappedPatch(x, basey, baseflags, queuebg_down, greymap);
if (i+1 != workingqueuesize) // no more line?
{
@ -857,17 +857,17 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
}
else
{
y = 179;
y = basey + 12;
if (i+1 != workingqueuesize) // no more line?
{
V_DrawMappedPatch(x, 167, baseflags, queuebg_upwa, greymap);
V_DrawMappedPatch(x, basey, baseflags, queuebg_upwa, greymap);
choose_line = line_upwa;
}
else
{
V_DrawMappedPatch(x, 167, baseflags, queuebg_flat, greymap);
V_DrawMappedPatch(x, basey, baseflags, queuebg_flat, greymap);
}
}
@ -936,7 +936,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
// Draw the line to the right of the dot
V_DrawMappedPatch(
x - 1, 178,
x - 1, basey + 11,
baseflags,
choose_line[BPP_SHADOW],
NULL
@ -969,7 +969,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
else
{
V_DrawMappedPatch(
x - 1, 179,
x - 1, basey + 12,
baseflags,
choose_line[BPP_DONE],
colormap
@ -988,7 +988,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
}
V_DrawMappedPatch(
x - 1, 179,
x - 1, basey + 12,
baseflags,
choose_line[lineisfull ? BPP_DONE : BPP_AHEAD],
lineisfull ? colormap : NULL
@ -1006,7 +1006,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
while (xiter < BASEVIDWIDTH + bufferspace)
{
xiter += 24;
V_DrawMappedPatch(xiter, 167, baseflags, queuebg_flat, greymap);
V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap);
}
// Handle special entry on the end
@ -1068,7 +1068,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
}
// Special background bump
V_DrawMappedPatch(x2 - 13, 167, baseflags, queuebg_prize, greymap);
V_DrawMappedPatch(x2 - 13, basey, baseflags, queuebg_prize, greymap);
// Draw the final line
const fixed_t barstart = x + 6;
@ -1095,14 +1095,14 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
while (xiter < fillend)
{
V_DrawMappedPatch(
xiter - 1, 177,
xiter - 1, basey + 10,
baseflags,
line_flat[BPP_SHADOW],
NULL
);
V_DrawMappedPatch(
xiter - 1, 179,
xiter - 1, basey + 12,
baseflags,
line_flat[BPP_DONE],
colormap
@ -1128,14 +1128,14 @@ void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean wides
while (xiter < barend)
{
V_DrawMappedPatch(
xiter - 1, 177,
xiter - 1, basey + 10,
baseflags,
line_flat[BPP_SHADOW],
NULL
);
V_DrawMappedPatch(
xiter - 1, 179,
xiter - 1, basey + 12,
baseflags,
line_flat[lineisfull ? BPP_DONE : BPP_AHEAD],
lineisfull ? colormap : NULL
@ -1391,7 +1391,7 @@ skiptallydrawer:
goto finalcounter;
// Returns early if there's no roundqueue entries to draw
Y_RoundQueueDrawer(&data, true, false);
Y_RoundQueueDrawer(&data, 0, true, false);
if (netgame)
{
@ -1498,6 +1498,35 @@ void Y_Ticker(void)
return;
}
// Animation sounds for roundqueue, see Y_RoundQueueDrawer
if (roundqueue.size != 0
&& roundqueue.position != 0
&& (timer - 1) <= 2*TICRATE)
{
const INT32 through = ((2*TICRATE) - (timer - 1));
if (data.showrank == true
&& roundqueue.position == roundqueue.size-1)
{
// Handle special entry on the end
if (through == data.linemeter && timer > 2)
{
S_StopSoundByID(NULL, sfx_gpmetr);
S_StartSound(NULL, sfx_kc50);
}
else if (through == 0)
{
S_StartSound(NULL, sfx_gpmetr);
}
}
else if (through == 9
&& roundqueue.position < roundqueue.size)
{
// Impactful landing
S_StartSound(NULL, sfx_kc50);
}
}
if (intertic < TICRATE || endtic != -1)
{
return;

View file

@ -49,7 +49,7 @@ void Y_Ticker(void);
// Specific sub-drawers
void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset);
void Y_RoundQueueDrawer(y_data_t *standings, boolean doanimations, boolean widescreen);
void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen);
void Y_StartIntermission(void);
void Y_EndIntermission(void);