RingRacers/src/k_menudraw.c
toaster 966ce7a256 Challenges Menu: Highlight overlay for newly unlocked tiles that haven't yet been focused on
Game design solution to the problem of multiple Challenges being unlocked at once not being clear enough.

Uses the `unlockpending` array for gamedata save purposes, which I was always intending to use for this purpose but never quite got around to.
2024-03-19 19:30:16 +00:00

8800 lines
214 KiB
C

/// \file k_menudraw.c
/// \brief SRB2Kart's menu drawer functions
#ifdef __GNUC__
#include <unistd.h>
#endif
#include "k_menu.h"
#include "doomdef.h"
#include "d_main.h"
#include "d_netcmd.h"
#include "console.h"
#include "r_local.h"
#include "hu_stuff.h"
#include "g_game.h"
#include "g_input.h"
#include "m_argv.h"
// Data.
#include "sounds.h"
#include "s_sound.h"
#include "i_system.h"
// Addfile
#include "filesrch.h"
#include "v_video.h"
#include "i_video.h"
#include "keys.h"
#include "z_zone.h"
#include "w_wad.h"
#include "p_local.h"
#include "p_setup.h"
#include "f_finale.h"
#ifdef HWRENDER
#include "hardware/hw_main.h"
#endif
#include "d_net.h"
#include "mserv.h"
#include "m_misc.h"
#include "m_anigif.h"
#include "byteptr.h"
#include "st_stuff.h"
#include "i_sound.h"
#include "k_kart.h"
#include "k_hud.h"
#include "k_follower.h"
#include "d_player.h" // KITEM_ constants
#include "doomstat.h" // MAXSPLITSCREENPLAYERS
#include "k_grandprix.h" // K_CanChangeRules
#include "k_rank.h" // K_GetGradeColor
#include "k_zvote.h" // K_GetMidVoteLabel
#include "k_boss.h"
#include "y_inter.h" // Y_RoundQueueDrawer
#include "i_joy.h" // for joystick menu controls
// Condition Sets
#include "m_cond.h"
// Sound Test
#include "music.h"
// And just some randomness for the exits.
#include "m_random.h"
#include "i_time.h"
#include "m_easing.h"
#include "sanitize.h"
#ifdef PC_DOS
#include <stdio.h> // for snprintf
int snprintf(char *str, size_t n, const char *fmt, ...);
//int vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
#endif
#ifdef HAVE_DISCORDRPC
#include "discord.h"
#endif
fixed_t M_TimeFrac(tic_t tics, tic_t duration)
{
return tics < duration ? (tics * FRACUNIT + rendertimefrac_unpaused) / duration : FRACUNIT;
}
fixed_t M_ReverseTimeFrac(tic_t tics, tic_t duration)
{
return FRACUNIT - M_TimeFrac(duration - tics, duration);
}
fixed_t M_DueFrac(tic_t start, tic_t duration)
{
tic_t t = I_GetTime();
tic_t n = t - start;
return M_TimeFrac(min(n, duration), duration);
}
#define SKULLXOFF -32
#define LINEHEIGHT 13
#define STRINGHEIGHT 9
#define FONTBHEIGHT 20
#define SMALLLINEHEIGHT 9
#define SLIDER_RANGE 10
#define SLIDER_WIDTH (8*SLIDER_RANGE+6)
#define SERVERS_PER_PAGE 11
// horizontally centered text
static void M_CentreText(INT32 xoffs, INT32 y, const char *string)
{
INT32 x;
//added : 02-02-98 : centre on 320, because V_DrawString centers on vid.width...
x = ((BASEVIDWIDTH - V_MenuStringWidth(string, 0))>>1) + xoffs;
V_DrawMenuString(x,y,0,string);
}
static INT32 M_SliderX(INT32 range)
{
if (range < 0)
range = 0;
if (range > 100)
range = 100;
return -4 + (((SLIDER_RANGE)*8 + 4)*range)/100;
}
// A smaller 'Thermo', with range given as percents (0-100)
static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop)
{
x = BASEVIDWIDTH - x - SLIDER_WIDTH;
V_DrawFill(x - 5, y + 3, SLIDER_WIDTH + 3, 5, 31);
V_DrawFill(x - 4, y + 4, SLIDER_WIDTH, 2, orangemap[0]);
if (ontop)
{
V_DrawMenuString(x - 16 - (skullAnimCounter/5), y,
highlightflags, "\x1C"); // left arrow
V_DrawMenuString(x+(SLIDER_RANGE*8) + 8 + (skullAnimCounter/5), y,
highlightflags, "\x1D"); // right arrow
}
INT32 range = cv->PossibleValue[1].value - cv->PossibleValue[0].value;
INT32 val = atoi(cv->defaultvalue);
val = (val - cv->PossibleValue[0].value) * 100 / range;
// draw the default tick
V_DrawFill(x + M_SliderX(val), y + 2, 3, 4, 31);
val = (cv->value - cv->PossibleValue[0].value) * 100 / range;
INT32 px = x + M_SliderX(val);
// draw the slider cursor
V_DrawFill(px - 1, y - 1, 5, 11, 31);
V_DrawFill(px, y, 2, 8, aquamap[0]);
}
void M_DrawCursorHand(INT32 x, INT32 y)
{
V_DrawScaledPatch(x - 24 - (I_GetTime() % 16 < 8), y, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
}
void M_DrawUnderline(INT32 left, INT32 right, INT32 y)
{
if (menutransition.tics == menutransition.dest)
V_DrawFill(left - 1, y + 5, (right - left) + 11, 2, 31);
}
static patch_t *addonsp[NUM_EXT+5];
static INT16 bgMapID = NEXTMAP_INVALID;
void M_PickMenuBGMap(void)
{
UINT16 *allowedMaps;
size_t allowedMapsCount = 0;
UINT16 ret = 0;
INT32 i;
allowedMaps = Z_Malloc(nummapheaders * sizeof(UINT16), PU_STATIC, NULL);
for (i = 0; i < nummapheaders; i++)
{
if (mapheaderinfo[i] == NULL || mapheaderinfo[i]->lumpnum == LUMPERROR)
{
// Doesn't exist?
continue;
}
if (mapheaderinfo[i]->thumbnailPic == NULL)
{
// No image...
continue;
}
if ((mapheaderinfo[i]->typeoflevel & (TOL_SPECIAL|TOL_VERSUS)) != 0)
{
// Don't spoil Special or Versus.
continue;
}
if ((mapheaderinfo[i]->menuflags & LF2_HIDEINMENU) == LF2_HIDEINMENU)
{
// "Hide in menu"... geddit?!
continue;
}
if (!(mapheaderinfo[i]->menuflags & LF2_NOVISITNEEDED)
&& !(mapheaderinfo[i]->records.mapvisited & MV_VISITED)
&& !(
mapheaderinfo[i]->cup
&& mapheaderinfo[i]->cup->cachedlevels[0] == i
))
{
// Not visited OR head of cup
continue;
}
if (M_MapLocked(i + 1) == true)
{
// We haven't earned this one.
continue;
}
// Got past the gauntlet, so we can allow this one.
allowedMaps[ allowedMapsCount++ ] = i;
}
if (allowedMapsCount > 0)
{
ret = allowedMaps[ M_RandomKey(allowedMapsCount) ];
}
Z_Free(allowedMaps);
bgMapID = ret;
}
static fixed_t bgText1Scroll = 0;
static fixed_t bgText2Scroll = 0;
static fixed_t bgImageScroll = 0;
static char bgImageName[9];
#define MENUBG_TEXTSCROLL 6
#define MENUBG_IMAGESCROLL 36
void M_UpdateMenuBGImage(boolean forceReset)
{
char oldName[9];
memcpy(oldName, bgImageName, 9);
if (currentMenu->menuitems[itemOn].patch)
{
sprintf(bgImageName, "%s", currentMenu->menuitems[itemOn].patch);
}
else
{
sprintf(bgImageName, "MENUI000");
}
if (forceReset == false && strcmp(bgImageName, oldName))
{
bgImageScroll = (3 * BASEVIDWIDTH) * (FRACUNIT / 4);
}
if (forceReset == true)
{
M_PickMenuBGMap();
}
}
void M_DrawMenuBackground(void)
{
patch_t *text1 = W_CachePatchName("MENUBGT1", PU_CACHE);
patch_t *text2 = W_CachePatchName("MENUBGT2", PU_CACHE);
fixed_t text1loop = SHORT(text1->height)*FRACUNIT;
fixed_t text2loop = SHORT(text2->width)*FRACUNIT;
if (bgMapID >= nummapheaders)
{
M_PickMenuBGMap();
}
patch_t *bgMapImage = mapheaderinfo[bgMapID]->thumbnailPic;
if (bgMapImage == NULL)
{
bgMapImage = W_CachePatchName("MENUBG4", PU_CACHE);
}
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bgMapImage, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SLATE, GTC_MENUCACHE));
V_DrawFixedPatch(0, 0, FRACUNIT, V_ADD, W_CachePatchName("MENUCUTD", PU_CACHE), NULL);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUCUT", PU_CACHE), NULL);
V_DrawFixedPatch(-bgImageScroll, 0, FRACUNIT, 0, W_CachePatchName("MENUBG1", PU_CACHE), NULL);
V_DrawFixedPatch(-bgImageScroll, 0, FRACUNIT, 0, W_CachePatchName(bgImageName, PU_CACHE), NULL);
V_DrawFixedPatch(0, (BASEVIDHEIGHT + 16) * FRACUNIT, FRACUNIT, V_SUBTRACT, W_CachePatchName("MENUBG2", PU_CACHE), NULL);
V_DrawFixedPatch(8 * FRACUNIT, -bgText1Scroll,
FRACUNIT, V_SUBTRACT, text1, NULL);
V_DrawFixedPatch(8 * FRACUNIT, -bgText1Scroll + text1loop,
FRACUNIT, V_SUBTRACT, text1, NULL);
V_DrawFixedPatch(-bgText2Scroll, (BASEVIDHEIGHT-8) * FRACUNIT,
FRACUNIT, V_ADD, text2, NULL);
V_DrawFixedPatch(-bgText2Scroll + text2loop, (BASEVIDHEIGHT-8) * FRACUNIT,
FRACUNIT, V_ADD, text2, NULL);
if (renderdeltatics > 2*FRACUNIT)
return; // wipe hitch...
bgText1Scroll += (MENUBG_TEXTSCROLL*renderdeltatics);
while (bgText1Scroll > text1loop)
bgText1Scroll -= text1loop;
bgText2Scroll += (MENUBG_TEXTSCROLL*renderdeltatics);
while (bgText2Scroll > text2loop)
bgText2Scroll -= text2loop;
if (bgImageScroll > 0)
{
bgImageScroll -= (MENUBG_IMAGESCROLL*renderdeltatics);
if (bgImageScroll < 0)
{
bgImageScroll = 0;
}
}
}
void M_DrawExtrasBack(void)
{
patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL);
}
UINT16 M_GetCvPlayerColor(UINT8 pnum)
{
if (pnum >= MAXSPLITSCREENPLAYERS)
return SKINCOLOR_NONE;
UINT16 color = cv_playercolor[pnum].value;
if (color != SKINCOLOR_NONE)
return color;
INT32 skin = R_SkinAvailableEx(cv_skin[pnum].string, false);
if (skin == -1)
return SKINCOLOR_NONE;
return skins[skin].prefcolor;
}
static void M_DrawMenuParty(void)
{
const INT32 PLATTER_WIDTH = 19;
const INT32 PLATTER_STAGGER = 6;
const INT32 PLATTER_OFFSET = (PLATTER_WIDTH - PLATTER_STAGGER);
patch_t *small = W_CachePatchName("MENUPLRA", PU_CACHE);
patch_t *large = W_CachePatchName("MENUPLRB", PU_CACHE);
INT32 x, y;
INT32 skin;
UINT16 color;
UINT8 *colormap;
if (setup_numplayers == 0 || currentMenu == &PLAY_CharSelectDef || currentMenu == &MISC_ChallengesDef)
{
return;
}
x = 2;
y = BASEVIDHEIGHT - small->height - 2;
// Despite the work put into it, can't use M_GetCvPlayerColor directly - we need to reference skin always.
#define grab_skin_and_colormap(pnum) \
{ \
skin = R_SkinAvailableEx(cv_skin[pnum].string, false); \
color = cv_playercolor[pnum].value; \
if (skin == -1) \
skin = 0; \
if (color == SKINCOLOR_NONE) \
color = skins[skin].prefcolor; \
colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); \
}
switch (setup_numplayers)
{
case 1:
{
x -= 8;
V_DrawScaledPatch(x, y, 0, small);
grab_skin_and_colormap(0);
V_DrawMappedPatch(x + 22, y + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap);
break;
}
case 2:
{
x -= 8;
V_DrawScaledPatch(x, y, 0, small);
V_DrawScaledPatch(x + PLATTER_OFFSET, y - PLATTER_STAGGER, 0, small);
grab_skin_and_colormap(1);
V_DrawMappedPatch(x + PLATTER_OFFSET + 22, y - PLATTER_STAGGER + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap);
grab_skin_and_colormap(0);
V_DrawMappedPatch(x + 22, y + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap);
break;
}
case 3:
{
V_DrawScaledPatch(x, y, 0, large);
V_DrawScaledPatch(x + PLATTER_OFFSET, y - PLATTER_STAGGER, 0, small);
grab_skin_and_colormap(1);
V_DrawMappedPatch(x + PLATTER_OFFSET + 22, y - PLATTER_STAGGER + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap);
grab_skin_and_colormap(0);
V_DrawMappedPatch(x + 12, y - 2, 0, faceprefix[skin][FACE_MINIMAP], colormap);
grab_skin_and_colormap(2);
V_DrawMappedPatch(x + 22, y + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap);
break;
}
case 4:
{
V_DrawScaledPatch(x, y, 0, large);
V_DrawScaledPatch(x + PLATTER_OFFSET, y - PLATTER_STAGGER, 0, large);
grab_skin_and_colormap(1);
V_DrawMappedPatch(x + PLATTER_OFFSET + 12, y - PLATTER_STAGGER - 2, 0, faceprefix[skin][FACE_MINIMAP], colormap);
grab_skin_and_colormap(0);
V_DrawMappedPatch(x + 12, y - 2, 0, faceprefix[skin][FACE_MINIMAP], colormap);
grab_skin_and_colormap(3);
V_DrawMappedPatch(x + PLATTER_OFFSET + 22, y - PLATTER_STAGGER + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap);
grab_skin_and_colormap(2);
V_DrawMappedPatch(x + 22, y + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap);
break;
}
default:
{
return;
}
}
#undef grab_skin_and_color
x += PLATTER_WIDTH;
y += small->height;
V_DrawScaledPatch(x + 16, y - 12, 0, W_CachePatchName(va("OPPRNK0%d", setup_numplayers % 10), PU_CACHE));
}
void M_DrawMenuForeground(void)
{
if (gamestate == GS_MENU)
{
M_DrawMenuParty();
}
// draw non-green resolution border
if ((!menuactive || currentMenu != &PAUSE_PlaybackMenuDef) && // this obscures replay menu and I want to put in minimal effort to fix that
((vid.width % BASEVIDWIDTH != 0) || (vid.height % BASEVIDHEIGHT != 0)))
{
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("WEIRDRES", PU_CACHE), NULL);
}
}
//
// M_DrawMenuTooltips
//
// Draw a banner across the top of the screen, with a description of the current option displayed
//
static void M_DrawMenuTooltips(void)
{
if (currentMenu->menuitems[itemOn].tooltip != NULL)
{
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL);
V_DrawCenteredThinString(BASEVIDWIDTH/2, 12, 0, currentMenu->menuitems[itemOn].tooltip);
}
}
static const char *M_MenuTypingCroppedString(void)
{
static char buf[36];
const char *p = menutyping.cache;
size_t n = strlen(p);
if (n > sizeof buf)
{
p += n - sizeof buf;
n = sizeof buf;
}
memcpy(buf, p, n);
buf[n] = '\0';
return buf;
}
// Draws the typing submenu
static void M_DrawMenuTyping(void)
{
const UINT8 pid = 0;
INT32 i, j;
INT32 x, y;
char buf[8]; // We write there to use drawstring for convenience.
V_DrawFadeScreen(31, (menutyping.menutypingfade+1)/2);
// Draw the string we're editing at the top.
const INT32 boxwidth = (8*(MAXSTRINGLENGTH + 1)) + 7;
x = (BASEVIDWIDTH - boxwidth)/2;
y = 80;
if (menutyping.menutypingfade < 9)
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, currentMenu->menuitems[itemOn].text);
}
M_DrawMenuTooltips();
//M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
V_DrawFill(x + 5, y + 4 + 5, boxwidth - 8, 8+6, 159);
V_DrawFill(x + 4, y + 4 + 4, boxwidth - 6, 1, 121);
V_DrawFill(x + 4, y + 4 + 5 + 8 + 6, boxwidth - 6, 1, 121);
V_DrawFill(x + 4, y + 4 + 5, 1, 8+6, 121);
V_DrawFill(x + 5 + boxwidth - 8, y + 4 + 5, 1, 8+6, 121);
INT32 textwidth = M_DrawCaretString(x + 8, y + 12, M_MenuTypingCroppedString(), true);
if (skullAnimCounter < 4
&& menutyping.menutypingclose == false
&& menutyping.menutypingfade == (menutyping.keyboardtyping ? 9 : 18))
{
V_DrawCharacter(x + 8 + textwidth, y + 12 + 1, '_', false);
}
const INT32 buttonwidth = ((boxwidth + 1)/NUMVIRTUALKEYSINROW);
#define BUTTONHEIGHT (11)
// Now the keyboard itself
x += 5;
INT32 returnx = x;
if (menutyping.menutypingfade > 9)
{
y += 26;
if (menutyping.menutypingfade < 18)
{
y += floor(pow(2, (double)(18 - menutyping.menutypingfade))); // double yoffs for animation
}
INT32 tempkeyboardx = menutyping.keyboardx;
while (virtualKeyboard[menutyping.keyboardy][tempkeyboardx] == 1
&& tempkeyboardx > 0)
tempkeyboardx--;
for (i = 0; i < 5; i++)
{
j = 0;
while (j < NUMVIRTUALKEYSINROW)
{
INT32 mflag = 0;
INT16 c = virtualKeyboard[i][j];
INT32 buttonspacing = 1;
UINT8 col = 27;
INT32 arrowoffset = 0;
while (j + buttonspacing < NUMVIRTUALKEYSINROW
&& virtualKeyboard[i][j + buttonspacing] == 1)
{
buttonspacing++;
}
if (menutyping.keyboardshift ^ menutyping.keyboardcapslock)
c = shift_virtualKeyboard[i][j];
if (i < 4 && j < NUMVIRTUALKEYSINROW-2)
{
col = 25;
}
boolean canmodifycol = (menutyping.menutypingfade == 18);
if (c == KEY_BACKSPACE)
{
arrowoffset = 1;
buf[0] = '\x1C'; // left arrow
buf[1] = '\0';
if (canmodifycol && M_MenuBackHeld(pid))
{
col -= 4;
canmodifycol = false;
}
}
else if (c == KEY_RSHIFT)
{
arrowoffset = 2;
buf[0] = '\x1A'; // up arrow
buf[1] = '\0';
if (menutyping.keyboardcapslock || menutyping.keyboardshift)
{
col = 22;
}
if (canmodifycol && M_MenuExtraHeld(pid))
{
col -= 4;
canmodifycol = false;
}
}
else if (c == KEY_ENTER)
{
strcpy(buf, "OK");
if (menutyping.menutypingclose)
{
col -= 4;
canmodifycol = false;
}
}
else if (c == KEY_SPACE)
{
strcpy(buf, "Space");
}
else
{
buf[0] = c;
buf[1] = '\0';
}
INT32 width = (buttonwidth * buttonspacing) - 1;
// highlight:
/*if (menutyping.keyboardtyping)
{
mflag |= V_TRANSLUCENT; // grey it out if we can't use it.
}
else*/
{
if (tempkeyboardx == j && menutyping.keyboardy == i)
{
if (canmodifycol && M_MenuConfirmHeld(pid))
{
col -= 4;
canmodifycol = false;
}
V_DrawFill(x + 1, y + 1, width - 2, BUTTONHEIGHT - 2, col - 3);
V_DrawFill(x, y, width, 1, 121);
V_DrawFill(x, y + BUTTONHEIGHT - 1, width, 1, 121);
V_DrawFill(x, y + 1, 1, BUTTONHEIGHT - 2, 121);
V_DrawFill(x + width - 1, y + 1, 1, BUTTONHEIGHT - 2, 121);
mflag |= highlightflags;
}
else
{
V_DrawFill(x, y, width, BUTTONHEIGHT, col);
}
}
if (arrowoffset != 0)
{
if (c == KEY_RSHIFT)
{
V_DrawFill(x + width - 5, y + 1, 4, 4, 31);
if (menutyping.keyboardcapslock)
{
V_DrawFill(x + width - 4, y + 2, 2, 2, 121);
}
}
V_DrawCenteredString(x + (width/2), y + 1 + arrowoffset, mflag, buf);
}
else
{
V_DrawCenteredThinString(x + (width/2), y + 1, mflag, buf);
}
x += width + 1;
j += buttonspacing;
}
x = returnx;
y += BUTTONHEIGHT + 1;
}
}
#undef BUTTONHEIGHT
y = 187;
if (menutyping.menutypingfade < 9)
{
y += 3 * (9 - menutyping.menutypingfade);
}
// Some contextual stuff
if (menutyping.keyboardtyping)
{
V_DrawThinString(returnx, y, V_GRAYMAP,
"Type using your keyboard. Press Enter to confirm & exit."
//"\nPress any button on your controller to use the Virtual Keyboard."
);
}
else
{
V_DrawThinString(returnx, y, V_GRAYMAP,
"Type using the Virtual Keyboard. Use the \'OK\' button to confirm & exit."
//"\nPress any keyboard key to type normally."
);
}
}
static void M_DrawMediocreKeyboardKey(const char *text, INT32 *workx, INT32 worky, boolean push, boolean rightaligned)
{
INT32 buttonwidth = V_StringWidth(text, 0) + 2;
if (rightaligned)
{
(*workx) -= buttonwidth;
}
if (push)
{
worky += 2;
}
else
{
V_DrawFill((*workx)-1, worky+10, buttonwidth, 2, 24);
}
V_DrawFill((*workx)-1, worky, buttonwidth, 10, 16);
V_DrawString(
(*workx), worky + 1,
0, text
);
}
// Draw the message popup submenu
void M_DrawMenuMessage(void)
{
if (!menumessage.active)
return;
INT32 x = (BASEVIDWIDTH - menumessage.x)/2;
INT32 y = (BASEVIDHEIGHT - menumessage.y)/2 + floor(pow(2, (double)(9 - menumessage.fadetimer)));
size_t i, start = 0;
char string[MAXMENUMESSAGE];
const char *msg = menumessage.message;
V_DrawFadeScreen(31, menumessage.fadetimer);
V_DrawFill(0, y, BASEVIDWIDTH, menumessage.y, 159);
if (menumessage.header != NULL)
{
V_DrawThinString(x, y - 10, highlightflags, menumessage.header);
}
if (menumessage.defaultstr)
{
INT32 workx = x + menumessage.x;
INT32 worky = y + menumessage.y;
boolean standardbuttons = (
cv_currprofile.value != -1 || G_GetNumAvailableGamepads()
);
boolean push;
if (menumessage.closing)
push = (menumessage.answer != MA_YES);
else
{
const UINT8 anim_duration = 16;
push = ((menumessage.timer % (anim_duration * 2)) < anim_duration);
}
workx -= V_ThinStringWidth(menumessage.defaultstr, 0);
V_DrawThinString(
workx, worky + 1,
((push && (menumessage.closing & MENUMESSAGECLOSE))
? highlightflags : 0),
menumessage.defaultstr
);
workx -= 2;
if (standardbuttons)
{
workx -= SHORT(kp_button_x[1][0]->width);
K_drawButton(
workx * FRACUNIT, worky * FRACUNIT,
0, kp_button_x[1],
push
);
workx -= SHORT(kp_button_b[1][0]->width);
K_drawButton(
workx * FRACUNIT, worky * FRACUNIT,
0, kp_button_b[1],
push
);
}
else
{
M_DrawMediocreKeyboardKey("ESC", &workx, worky, push, true);
}
if (menumessage.confirmstr)
{
workx -= 12;
if (menumessage.closing)
push = !push;
workx -= V_ThinStringWidth(menumessage.confirmstr, 0);
V_DrawThinString(
workx, worky + 1,
((push && (menumessage.closing & MENUMESSAGECLOSE))
? highlightflags : 0),
menumessage.confirmstr
);
workx -= 2;
}
if (standardbuttons)
{
workx -= SHORT(kp_button_a[1][0]->width);
K_drawButton(
workx * FRACUNIT, worky * FRACUNIT,
0, kp_button_a[1],
push
);
}
else
{
M_DrawMediocreKeyboardKey("ENTER", &workx, worky, push, true);
}
}
x -= 4;
y += 4;
while (*(msg+start))
{
size_t len = strlen(msg+start);
for (i = 0; i < len; i++)
{
if (*(msg+start+i) == '\n')
{
memset(string, 0, MAXMENUMESSAGE);
if (i >= MAXMENUMESSAGE)
{
CONS_Printf("M_DrawMenuMessage: too long segment in %s\n", msg);
return;
}
else
{
strncpy(string,msg+start, i);
string[i] = '\0';
start += i;
i = (size_t)-1; //added : 07-02-98 : damned!
start++;
}
break;
}
}
if (i == strlen(msg+start))
{
if (i >= MAXMENUMESSAGE)
{
CONS_Printf("M_DrawMenuMessage: too long segment in %s\n", msg);
return;
}
else
{
strcpy(string, msg + start);
start += i;
}
}
V_DrawString((BASEVIDWIDTH - V_StringWidth(string, 0))/2, y, 0, string);
y += 8;
}
}
// PAUSE
static void M_DrawPausedText(INT32 x)
{
patch_t *pausebg = W_CachePatchName("M_STRIPU", PU_CACHE);
patch_t *pausetext = W_CachePatchName("M_PAUSET", PU_CACHE);
INT32 snapFlags = menuactive ? 0 : (V_SNAPTOLEFT|V_SNAPTOTOP);
V_DrawFixedPatch(x, -5*FRACUNIT, FRACUNIT, snapFlags|V_ADD, pausebg, NULL);
V_DrawFixedPatch(x, -5*FRACUNIT, FRACUNIT, snapFlags, pausetext, NULL);
}
//
// M_Drawer
// Called after the view has been rendered,
// but before it has been blitted.
//
void M_Drawer(void)
{
if (menuwipe)
F_WipeStartScreen();
// background layer
if (menuactive)
{
boolean drawbgroutine = false;
boolean trulystarted = M_GameTrulyStarted();
if (gamestate == GS_MENU && trulystarted)
{
if (currentMenu->bgroutine)
drawbgroutine = true;
else
M_DrawMenuBackground();
}
else
{
if (currentMenu->bgroutine
&& (currentMenu->behaviourflags & MBF_DRAWBGWHILEPLAYING))
drawbgroutine = true;
if (!Playing() && !trulystarted)
{
M_DrawGonerBack();
}
else if (!WipeInAction && currentMenu != &PAUSE_PlaybackMenuDef)
{
V_DrawFadeScreen(122, 3);
}
}
if (drawbgroutine)
currentMenu->bgroutine();
}
// draw pause pic
if (paused && !demo.playback && (menuactive || cv_showhud.value))
{
M_DrawPausedText(0);
}
// foreground layer
if (menuactive)
{
if (currentMenu->drawroutine)
currentMenu->drawroutine(); // call current menu Draw routine
M_DrawMenuForeground();
// Draw version down in corner
// ... but only in the MAIN MENU. I'm a picky bastard.
if (currentMenu == &MainDef)
{
F_VersionDrawer();
}
// Draw typing overlay when needed, above all other menu elements.
if (menutyping.active)
M_DrawMenuTyping();
// Draw message overlay when needed
M_DrawMenuMessage();
}
if (menuwipe)
{
F_WipeEndScreen();
F_RunWipe(wipe_menu_final, wipedefs[wipe_menu_final], false, "FADEMAP0", true, false);
menuwipe = false;
}
if (netgame && Playing())
{
boolean mainpause_open = menuactive && currentMenu == &PAUSE_MainDef;
ST_DrawServerSplash(!mainpause_open);
}
// focus lost notification goes on top of everything, even the former everything
if (window_notinfocus && cv_showfocuslost.value)
{
M_DrawTextBox((BASEVIDWIDTH/2) - (60), (BASEVIDHEIGHT/2) - (16), 13, 2);
V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2) - (4), highlightflags|V_FORCEUPPERCASE, "Focus Lost");
}
}
// ==========================================================================
// GENERIC MENUS
// ==========================================================================
// Converts a string into question marks.
// Used for the secrets menu, to hide yet-to-be-unlocked stuff.
static const char *M_CreateSecretMenuOption(const char *str)
{
#if 1
(void)str;
return "???";
#else
static char qbuf[64];
int i;
for (i = 0; i < 63; ++i)
{
if (!str[i])
{
qbuf[i] = '\0';
return qbuf;
}
else if (str[i] != ' ')
qbuf[i] = '?';
else
qbuf[i] = ' ';
}
qbuf[63] = '\0';
return qbuf;
#endif
}
//
// M_DrawGenericMenu
//
// Default, generic text-based list menu, used for Options
//
void M_DrawGenericMenu(void)
{
INT32 x = currentMenu->x, y = currentMenu->y, w, i, cursory = 0;
M_DrawMenuTooltips();
for (i = 0; i < currentMenu->numitems; i++)
{
if (i == itemOn)
cursory = y;
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_PATCH:
if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
{
if (currentMenu->menuitems[i].status & IT_CENTER)
{
patch_t *p;
p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE);
V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p);
}
else
{
V_DrawScaledPatch(x, y, 0,
W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE));
}
}
/* FALLTHRU */
case IT_NOTHING:
case IT_DYBIGSPACE:
y = currentMenu->y + currentMenu->menuitems[i].mvar1;//+= LINEHEIGHT;
break;
#if 0
case IT_BIGSLIDER:
M_DrawThermo(x, y, currentMenu->menuitems[i].itemaction.cvar);
y += LINEHEIGHT;
break;
#endif
case IT_STRING:
if (currentMenu->menuitems[i].mvar1)
y = currentMenu->y+currentMenu->menuitems[i].mvar1;
if (i == itemOn)
cursory = y;
if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING)
V_DrawMenuString(x, y, 0, currentMenu->menuitems[i].text);
else
V_DrawMenuString(x, y, highlightflags, currentMenu->menuitems[i].text);
// Cvar specific handling
switch (currentMenu->menuitems[i].status & IT_TYPE)
case IT_CVAR:
{
consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar;
switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
{
#if 0
case IT_CV_SLIDER:
M_DrawSlider(x, y, cv, (i == itemOn));
case IT_CV_NOPRINT: // color use this
case IT_CV_INVISSLIDER: // monitor toggles use this
break;
#endif
case IT_CV_STRING:
{
M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
INT32 xoffs = 0;
if (itemOn == i)
{
xoffs += 8;
V_DrawMenuString(x + (skullAnimCounter/5) + 6, y + 12, highlightflags, "\x1D");
}
V_DrawString(x + xoffs + 8, y + 12, 0, cv->string);
y += 16;
}
break;
default:
w = V_MenuStringWidth(cv->string, 0);
V_DrawMenuString(BASEVIDWIDTH - x - w, y,
((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string);
if (i == itemOn)
{
V_DrawMenuString(BASEVIDWIDTH - x - 10 - w - (skullAnimCounter/5), y,
highlightflags, "\x1C"); // left arrow
V_DrawMenuString(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
highlightflags, "\x1D"); // right arrow
}
break;
}
break;
}
y += STRINGHEIGHT;
break;
case IT_STRING2:
V_DrawMenuString(x, y, 0, currentMenu->menuitems[i].text);
/* FALLTHRU */
case IT_DYLITLSPACE:
y += SMALLLINEHEIGHT;
break;
case IT_GRAYPATCH:
if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
V_DrawMappedPatch(x, y, 0,
W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap);
y += LINEHEIGHT;
break;
case IT_TRANSTEXT:
if (currentMenu->menuitems[i].mvar1)
y = currentMenu->y+currentMenu->menuitems[i].mvar1;
/* FALLTHRU */
case IT_TRANSTEXT2:
V_DrawMenuString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);
y += SMALLLINEHEIGHT;
break;
case IT_QUESTIONMARKS:
if (currentMenu->menuitems[i].mvar1)
y = currentMenu->y+currentMenu->menuitems[i].mvar1;
V_DrawMenuString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
y += SMALLLINEHEIGHT;
break;
case IT_HEADERTEXT: // draws 16 pixels to the left, in yellow text
if (currentMenu->menuitems[i].mvar1)
y = currentMenu->y+currentMenu->menuitems[i].mvar1;
V_DrawMenuString(x-16, y, highlightflags, currentMenu->menuitems[i].text);
y += SMALLLINEHEIGHT;
break;
}
}
if ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == 0)
cursory = 300; // Put the cursor off screen if we can't even display that option and we're on it, it makes no sense otherwise...
// DRAW THE SKULL CURSOR
if (((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_PATCH)
|| ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING))
{
V_DrawScaledPatch(currentMenu->x + SKULLXOFF, cursory - 5, 0,
W_CachePatchName("M_CURSOR", PU_CACHE));
}
else
{
V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
W_CachePatchName("M_CURSOR", PU_CACHE));
V_DrawMenuString(currentMenu->x, cursory, highlightflags, currentMenu->menuitems[itemOn].text);
}
}
#define GM_STARTX 128
#define GM_STARTY 80
#define GM_XOFFSET 17
#define GM_YOFFSET 34
#define GM_FLIPTIME 5
static tic_t gm_flipStart;
static INT32 M_DrawRejoinIP(INT32 x, INT32 y, INT32 tx)
{
extern consvar_t cv_dummyipselect;
char (*ip)[MAX_LOGIP] = joinedIPlist[cv_dummyipselect.value];
if (!*ip[0])
return 0;
INT16 shift = 20;
x -= shift;
INT16 j = 0;
for (j=0; j <= (GM_YOFFSET + 10) / 2; j++)
{
// Draw rectangles that look like the current selected item starting from the top of the actual selection graphic and going up to where it's supposed to go.
// With colour 169 (that's the index of the shade of black the plague colourization gives us. ...No I don't like using a magic number either.
V_DrawFill((x-1) + j, y + (2*j), 226, 2, 169);
}
x += GM_XOFFSET + 14;
y += GM_YOFFSET;
const char *text = ip[0];
INT32 w = V_ThinStringWidth(text, 0);
INT32 f = highlightflags;
V_DrawMenuString(x - 10 - (skullAnimCounter/5), y, f, "\x1C"); // left arrow
V_DrawMenuString(x + w + 2+ (skullAnimCounter/5), y, f, "\x1D"); // right arrow
V_DrawThinString(x, y, f, text);
V_DrawRightAlignedThinString(BASEVIDWIDTH + 4 + tx, y, V_ORANGEMAP, "\xAC Rejoin");
return shift;
}
//
// M_DrawKartGamemodeMenu
//
// Huge gamemode-selection list for main menu
//
void M_DrawKartGamemodeMenu(void)
{
UINT8 n = 0;
INT32 i, x, y;
INT32 tx = M_EaseWithTransition(Easing_Linear, 5 * 48);
for (i = 0; i < currentMenu->numitems; i++)
{
if (currentMenu->menuitems[i].status == IT_DISABLED)
{
continue;
}
n++;
}
n--;
x = GM_STARTX - ((GM_XOFFSET / 2) * (n-1)) + tx;
y = GM_STARTY - ((GM_YOFFSET / 2) * (n-1));
M_DrawMenuTooltips();
for (i = 0; i < currentMenu->numitems; i++)
{
INT32 type;
if (currentMenu->menuitems[i].status == IT_DISABLED)
{
continue;
}
if (i >= currentMenu->numitems-1)
{
x = GM_STARTX + (GM_XOFFSET * 5 / 2) + tx;
y = GM_STARTY + (GM_YOFFSET * 5 / 2);
}
INT32 cx = x;
boolean selected = (i == itemOn && menutransition.tics == menutransition.dest);
if (selected)
{
fixed_t f = M_DueFrac(gm_flipStart, GM_FLIPTIME);
cx -= Easing_OutSine(f, 0, (GM_XOFFSET / 2));
// Direct Join
if (currentMenu == &PLAY_MP_OptSelectDef && i == mp_directjoin)
{
INT32 shift = M_DrawRejoinIP(cx, y, cx - x);
cx -= Easing_OutSine(f, 0, shift);
}
}
type = (currentMenu->menuitems[i].status & IT_DISPLAY);
switch (type)
{
case IT_STRING:
case IT_TRANSTEXT2:
{
UINT8 *colormap = NULL;
if (selected)
{
colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE);
}
V_DrawFixedPatch(cx*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, W_CachePatchName("MENUPLTR", PU_CACHE), colormap);
V_DrawGamemodeString(cx + 16, y - 3,
(type == IT_TRANSTEXT2
? V_TRANSLUCENT
: 0
),
colormap,
currentMenu->menuitems[i].text);
}
break;
}
x += GM_XOFFSET;
y += GM_YOFFSET;
}
}
void M_FlipKartGamemodeMenu(boolean slide)
{
gm_flipStart = slide ? I_GetTime() : 0;
}
void M_DrawHorizontalMenu(void)
{
INT32 x, y, i, final = currentMenu->extra2-1, showflags;
const INT32 width = 80;
y = currentMenu->y;
V_DrawFadeFill(0, y-2, BASEVIDWIDTH, 24, 0, 31, 5);
x = (BASEVIDWIDTH - 8*final)/2;
for (i = 0; i < currentMenu->extra2; i++, x += 8)
{
if (i == itemOn)
{
V_DrawFill(x-2, y + 16, 4, 4, 0);
}
else if (i >= currentMenu->numitems)
{
V_DrawFill(x-1, y + 17, 2, 2, 20);
}
else
{
V_DrawFill(x-1, y + 17, 2, 2,
(i == final && skullAnimCounter/5) ? 73 : 10
);
}
}
i = itemOn;
x = BASEVIDWIDTH/2;
do
{
if (i == 0)
break;
i--;
x -= width;
}
while (x > -width/2);
while (x < BASEVIDWIDTH + (width/2))
{
showflags = 0;
if (i == final)
{
showflags |= V_STRINGDANCE;
if (itemOn == i)
showflags |= V_YELLOWMAP;
}
else if (i == itemOn)
{
showflags |= highlightflags;
}
V_DrawCenteredThinString(
x, y,
showflags,
currentMenu->menuitems[i].text
);
if (++i == currentMenu->numitems)
break;
x += width;
}
if (itemOn != 0)
V_DrawMenuString((BASEVIDWIDTH - width)/2 + 3 - (skullAnimCounter/5), y + 1,
highlightflags, "\x1C"); // left arrow
if (itemOn != currentMenu->numitems-1)
V_DrawMenuString((BASEVIDWIDTH + width)/2 - 10 + (skullAnimCounter/5), y + 1,
highlightflags, "\x1D"); // right arrow
}
#define MAXMSGLINELEN 256
//
// Draw a textbox, like Quake does, because sometimes it's difficult
// to read the text with all the stuff in the background...
//
void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
{
// Solid color textbox.
V_DrawFill(x+5, y+5, width*8+6, boxlines*8+6, 159);
//V_DrawFill(x+8, y+8, width*8, boxlines*8, 31);
}
//
// M_DrawMessageMenu
//
// Generic message prompt
//
void M_DrawMessageMenu(void)
{
INT32 y = currentMenu->y;
size_t i, start = 0;
INT16 max;
char string[MAXMENUMESSAGE];
INT32 mlines;
const char *msg = currentMenu->menuitems[0].text;
mlines = currentMenu->lastOn>>8;
max = (INT16)((UINT8)(currentMenu->lastOn & 0xFF)*8);
M_DrawTextBox(currentMenu->x, y - 8, (max+7)>>3, mlines);
while (*(msg+start))
{
size_t len = strlen(msg+start);
for (i = 0; i < len; i++)
{
if (*(msg+start+i) == '\n')
{
memset(string, 0, MAXMENUMESSAGE);
if (i >= MAXMENUMESSAGE)
{
CONS_Printf("M_DrawMessageMenu: too long segment in %s\n", msg);
return;
}
else
{
strncpy(string,msg+start, i);
string[i] = '\0';
start += i;
i = (size_t)-1; //added : 07-02-98 : damned!
start++;
}
break;
}
}
if (i == strlen(msg+start))
{
if (i >= MAXMENUMESSAGE)
{
CONS_Printf("M_DrawMessageMenu: too long segment in %s\n", msg);
return;
}
else
{
strcpy(string, msg + start);
start += i;
}
}
V_DrawMenuString((BASEVIDWIDTH - V_MenuStringWidth(string, 0))/2,y,0,string);
y += 8; //SHORT(hu_font[0]->height);
}
}
// Draw an Image Def. Aka, Help images.
// Defines what image is used in (menuitem_t)->patch.
// You can even put multiple images in one menu!
void M_DrawImageDef(void)
{
patch_t *patch = W_CachePatchName(currentMenu->menuitems[itemOn].text, PU_CACHE);
if (patch->width <= BASEVIDWIDTH)
{
V_DrawScaledPatch(0, 0, 0, patch);
}
else
{
V_DrawSmallScaledPatch(0, 0, 0, patch);
}
if (currentMenu->menuitems[itemOn].mvar1)
{
V_DrawString(2,BASEVIDHEIGHT-10, V_YELLOWMAP, va("%d", (itemOn<<1)-1)); // intentionally not highlightflags, unlike below
V_DrawRightAlignedString(BASEVIDWIDTH-2,BASEVIDHEIGHT-10, V_YELLOWMAP, va("%d", itemOn<<1)); // ditto
}
else
{
INT32 x = BASEVIDWIDTH>>1, y = (BASEVIDHEIGHT>>1) - 4;
x += (itemOn ? 1 : -1)*((BASEVIDWIDTH>>2) + 10);
V_DrawCenteredString(x, y-10, highlightflags, "USE ARROW KEYS");
V_DrawCharacter(x - 10 - (skullAnimCounter/5), y,
'\x1C' | highlightflags, false); // left arrow
V_DrawCharacter(x + 2 + (skullAnimCounter/5), y,
'\x1D' | highlightflags, false); // right arrow
V_DrawCenteredString(x, y+10, highlightflags, "TO LEAF THROUGH");
}
}
//
// PLAY MENUS
//
static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
{
angle_t angamt = ANGLE_MAX;
UINT16 i, numoptions = 0;
INT16 l = 0, r = 0;
INT16 subtractcheck;
switch (p->mdepth)
{
case CSSTEP_ALTS:
numoptions = setup_chargrid[p->gridx][p->gridy].numskins;
break;
case CSSTEP_COLORS:
case CSSTEP_FOLLOWERCOLORS:
numoptions = p->colors.listLen;
break;
case CSSTEP_FOLLOWERCATEGORY:
numoptions = setup_numfollowercategories+1;
break;
case CSSTEP_FOLLOWER:
numoptions = setup_followercategories[p->followercategory][0];
break;
default:
return;
}
if (numoptions == 0)
{
return;
}
subtractcheck = 1 ^ (numoptions & 1);
angamt /= numoptions;
for (i = 0; i < numoptions; i++)
{
fixed_t cx = x << FRACBITS, cy = y << FRACBITS;
boolean subtract = (i & 1) == subtractcheck;
angle_t ang = ((i+1)/2) * angamt;
patch_t *patch = NULL;
UINT8 *colormap = NULL;
fixed_t radius = 28<<FRACBITS;
INT16 n = 0;
switch (p->mdepth)
{
case CSSTEP_ALTS:
{
INT16 skin;
n = (p->clonenum) + numoptions/2;
if (subtract)
n -= ((i+1)/2);
else
n += ((i+1)/2);
n = (n + numoptions) % numoptions;
skin = setup_chargrid[p->gridx][p->gridy].skinlist[n];
patch = faceprefix[skin][FACE_RANK];
colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
radius = 24<<FRACBITS;
cx -= (SHORT(patch->width) << FRACBITS) >> 1;
cy -= (SHORT(patch->height) << FRACBITS) >> 1;
break;
}
case CSSTEP_COLORS:
{
INT16 diff;
if (i == 0)
{
n = l = r = M_GetColorBefore(&p->colors, p->color, (numoptions/2) - (numoptions & 1));
}
else if (subtract)
{
n = l = M_GetColorBefore(&p->colors, l, 1);
}
else
{
n = r = M_GetColorAfter(&p->colors, r, 1);
}
colormap = R_GetTranslationColormap(TC_DEFAULT, (n == SKINCOLOR_NONE) ? skins[p->skin].prefcolor : n, GTC_MENUCACHE);
diff = (numoptions - i) / 2; // only 0 when i == numoptions-1
if (diff == 0)
patch = W_CachePatchName("COLORSP2", PU_CACHE);
else if (abs(diff) < 25)
patch = W_CachePatchName("COLORSP1", PU_CACHE);
else
patch = W_CachePatchName("COLORSP0", PU_CACHE);
cx -= (SHORT(patch->width) << FRACBITS) >> 1;
break;
}
case CSSTEP_FOLLOWERCATEGORY:
{
followercategory_t *fc = NULL;
n = (p->followercategory + 1) + numoptions/2;
if (subtract)
n -= ((i+1)/2);
else
n += ((i+1)/2);
n = (n + numoptions) % numoptions;
if (n == 0)
{
patch = W_CachePatchName("K_NOBLNS", PU_CACHE);
}
else
{
fc = &followercategories[setup_followercategories[n - 1][1]];
patch = W_CachePatchName(fc->icon, PU_CACHE);
}
radius = 24<<FRACBITS;
cx -= (SHORT(patch->width) << FRACBITS) >> 1;
cy -= (SHORT(patch->height) << FRACBITS) >> 1;
break;
}
case CSSTEP_FOLLOWER:
{
follower_t *fl = NULL;
INT16 startfollowern = p->followern;
if (i == 0)
{
n = p->followern;
r = (numoptions+1)/2;
while (r)
{
n--;
if (n < 0)
n = numfollowers-1;
if (n == startfollowern)
break;
if (followers[n].category == setup_followercategories[p->followercategory][1]
&& K_FollowerUsable(n))
r--;
}
l = r = n;
}
else if (subtract)
{
do
{
l--;
if (l < 0)
l = numfollowers-1;
if (l == startfollowern)
break;
}
while (followers[l].category != setup_followercategories[p->followercategory][1]
|| !K_FollowerUsable(l));
n = l;
}
else
{
do
{
r++;
if (r >= numfollowers)
r = 0;
if (r == startfollowern)
break;
}
while (followers[r].category != setup_followercategories[p->followercategory][1]
|| !K_FollowerUsable(r));
n = r;
}
{
fl = &followers[n];
patch = W_CachePatchName(fl->icon, PU_CACHE);
colormap = R_GetTranslationColormap(TC_DEFAULT,
K_GetEffectiveFollowerColor(fl->defaultcolor, fl, p->color, &skins[p->skin]),
GTC_MENUCACHE
);
}
radius = 24<<FRACBITS;
cx -= (SHORT(patch->width) << FRACBITS) >> 1;
cy -= (SHORT(patch->height) << FRACBITS) >> 1;
break;
}
case CSSTEP_FOLLOWERCOLORS:
{
INT16 diff;
UINT16 col;
if (i == 0)
{
n = l = r = M_GetColorBefore(&p->colors, p->followercolor, (numoptions/2) - (numoptions & 1));
}
else if (subtract)
{
n = l = M_GetColorBefore(&p->colors, l, 1);
}
else
{
n = r = M_GetColorAfter(&p->colors, r, 1);
}
col = K_GetEffectiveFollowerColor(n, &followers[p->followern], p->color, &skins[p->skin]);
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
diff = (numoptions - i)/2; // only 0 when i == numoptions-1
if (diff == 0)
patch = W_CachePatchName("COLORSP2", PU_CACHE);
else if (abs(diff) < 25)
patch = W_CachePatchName("COLORSP1", PU_CACHE);
else
patch = W_CachePatchName("COLORSP0", PU_CACHE);
cx -= (SHORT(patch->width) << FRACBITS) >> 1;
break;
}
default:
break;
}
if (subtract)
ang = (signed)(ANGLE_90 - ang);
else
ang = ANGLE_90 + ang;
if (numoptions & 1)
ang = (signed)(ang - (angamt/2));
if (p->rotate)
{
SINT8 rotate = p->rotate;
if ((p->hitlag == true) && (setup_animcounter & 1))
rotate = -rotate;
ang = (signed)(ang + ((angamt / CSROTATETICS) * rotate));
}
cx += FixedMul(radius, FINECOSINE(ang >> ANGLETOFINESHIFT));
cy -= FixedMul(radius, FINESINE(ang >> ANGLETOFINESHIFT)) / 3;
V_DrawFixedPatch(cx, cy, FRACUNIT, 0, patch, colormap);
if (p->mdepth == CSSTEP_ALTS && n != p->clonenum)
V_DrawFixedPatch(cx, cy, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("ICONDARK", PU_CACHE), NULL);
}
}
// returns false if the character couldn't be rendered
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;
spr = P_GetSkinSprite2(&skins[skin], spr2, NULL);
sprdef = &skins[skin].sprites[spr];
if (!sprdef->numframes) // No frames ??
return false; // Can't render!
frame %= sprdef->numframes;
sprframe = &sprdef->spriteframes[frame];
sprpatch = W_CachePatchNum(sprframe->lumppat[rotation], PU_CACHE);
if (sprframe->flip & (1<<rotation)) // Only for first sprite
{
addflags ^= V_FLIP; // This sprite is left/right flipped!
}
if (skins[skin].highresscale != FRACUNIT)
{
V_DrawFixedPatch(x<<FRACBITS,
y<<FRACBITS,
skins[skin].highresscale,
addflags, sprpatch, colormap);
}
else
V_DrawMappedPatch(x, y, addflags, sprpatch, colormap);
return true;
}
// Returns false is the follower shouldn't be rendered.
// 'num' can be used to directly specify the follower number, but doing this will not animate it.
// if a setup_player_t is specified instead, its data will be used to animate the follower sprite.
static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, boolean charflip, INT32 addflags, UINT8 *colormap, setup_player_t *p)
{
spritedef_t *sprdef;
spriteframe_t *sprframe;
patch_t *patch;
INT32 followernum;
state_t *usestate;
UINT32 useframe;
follower_t *fl;
UINT8 rotation = (charflip ? 1 : 7);
if (p != NULL)
followernum = p->followern;
else
followernum = num;
// Don't draw if we're outta bounds.
if (followernum < 0 || followernum >= numfollowers)
return false;
fl = &followers[followernum];
if (p != NULL)
{
usestate = p->follower_state;
useframe = p->follower_frame;
}
else
{
usestate = &states[followers[followernum].followstate];
useframe = usestate->frame & FF_FRAMEMASK;
}
sprdef = &sprites[usestate->sprite];
// draw the follower
if (useframe >= sprdef->numframes)
useframe = 0; // frame doesn't exist, we went beyond it... what?
sprframe = &sprdef->spriteframes[useframe];
patch = W_CachePatchNum(sprframe->lumppat[rotation], PU_CACHE);
if (sprframe->flip & (1<<rotation)) // Only for first sprite
{
addflags ^= V_FLIP; // This sprite is left/right flipped!
}
fixed_t sine = 0;
if (p != NULL)
{
UINT16 color = K_GetEffectiveFollowerColor(
(p->mdepth < CSSTEP_FOLLOWERCOLORS && p->mdepth != CSSTEP_ASKCHANGES) ? fl->defaultcolor : p->followercolor,
fl,
p->color,
&skins[p->skin]
);
sine = FixedMul(fl->bobamp, FINESINE(((FixedMul(4 * M_TAU_FIXED, fl->bobspeed) * p->follower_timer)>>ANGLETOFINESHIFT) & FINEMASK));
colormap = R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE);
}
V_DrawFixedPatch((x*FRACUNIT), ((y-12)*FRACUNIT) + sine, fl->scale, addflags, patch, colormap);
return true;
}
static void M_DrawCharSelectSprite(UINT8 num, INT16 x, INT16 y, boolean charflip)
{
setup_player_t *p = &setup_player[num];
UINT8 color;
UINT8 *colormap;
if (p->skin < 0)
{
return;
}
if (p->mdepth < CSSTEP_COLORS && p->mdepth != CSSTEP_ASKCHANGES)
{
color = skins[p->skin].prefcolor;
}
else
{
color = p->color;
}
if (color == SKINCOLOR_NONE)
{
color = skins[p->skin].prefcolor;
}
colormap = R_GetTranslationColormap(p->skin, color, GTC_MENUCACHE);
M_DrawCharacterSprite(x, y, p->skin, SPR2_STIN, (charflip ? 1 : 7), ((p->mdepth == CSSTEP_READY) ? setup_animcounter : 0),
p->mdepth == CSSTEP_ASKCHANGES ? V_TRANSLUCENT : 0, colormap);
}
static void M_DrawCharSelectPreview(UINT8 num)
{
INT16 x = 11, y = 5;
char letter = 'A' + num;
setup_player_t *p = &setup_player[num];
boolean charflip = !!(num & 1);
if (num & 1)
x += 233;
if (num > 1)
y += 99;
V_DrawScaledPatch(x, y+6, V_TRANSLUCENT, W_CachePatchName("PREVBACK", PU_CACHE));
if (p->mdepth >= CSSTEP_CHARS || p->mdepth == CSSTEP_ASKCHANGES)
{
M_DrawCharSelectSprite(num, x+32, y+75, charflip);
M_DrawCharSelectCircle(p, x+32, y+64);
}
if (p->showextra == false)
{
INT32 backx = x + ((num & 1) ? -1 : 11);
V_DrawScaledPatch(backx, y+2, 0, W_CachePatchName("FILEBACK", PU_CACHE));
V_DrawScaledPatch(x + ((num & 1) ? 44 : 0), y+2, 0, W_CachePatchName(va("CHARSEL%c", letter), PU_CACHE));
profile_t *pr = NULL;
if (p->mdepth > CSSTEP_PROFILE)
{
pr = PR_GetProfile(p->profilen);
}
V_DrawCenteredFileString(backx+26, y+2, 0, pr ? pr->profilename : "PLAYER");
}
if (p->mdepth >= CSSTEP_FOLLOWER || p->mdepth == CSSTEP_ASKCHANGES)
{
M_DrawFollowerSprite(x+32+((charflip ? 1 : -1)*16), y+75, -1, charflip, p->mdepth == CSSTEP_ASKCHANGES ? V_TRANSLUCENT : 0, NULL, p);
}
if ((setup_animcounter/10) & 1)
{
if (p->mdepth == CSSTEP_NONE && num == setup_numplayers && gamestate == GS_MENU)
{
V_DrawScaledPatch(x+1, y+36, 0, W_CachePatchName("4PSTART", PU_CACHE));
}
else if (p->mdepth >= CSSTEP_READY)
{
V_DrawScaledPatch(x+1, y+36, 0, W_CachePatchName("4PREADY", PU_CACHE));
}
}
// Profile selection
if (p->mdepth == CSSTEP_PROFILE)
{
INT16 px = x+12;
INT16 py = y+48 - p->profilen*12 +
Easing_OutSine(
M_DueFrac(p->profilen_slide.start, 5),
p->profilen_slide.dist*12,
0
);
UINT8 maxp = PR_GetNumProfiles();
UINT8 i = 0;
UINT8 j;
V_SetClipRect(0, (y+25)*FRACUNIT, BASEVIDWIDTH*FRACUNIT, (5*12)*FRACUNIT, 0);
for (i = 0; i < maxp; i++)
{
profile_t *pr = PR_GetProfile(i);
INT16 dist = abs(p->profilen - i);
INT32 notSelectable = 0;
SINT8 belongsTo = -1;
if (i != PROFILE_GUEST)
{
for (j = 0; j < setup_numplayers; j++)
{
if (setup_player[j].mdepth > CSSTEP_PROFILE
&& setup_player[j].profilen == i)
{
belongsTo = j;
break;
}
}
}
if (belongsTo != -1 && belongsTo != num)
{
notSelectable |= V_TRANSLUCENT;
}
if (dist > 3)
{
py += 12;
continue;
}
if (dist > 1)
{
V_DrawCenteredFileString(px+26, py, notSelectable, pr->version ? pr->profilename : "NEW");
V_DrawScaledPatch(px, py, V_TRANSLUCENT, W_CachePatchName("FILEBACK", PU_CACHE));
}
else
{
V_DrawScaledPatch(px, py, 0, W_CachePatchName("FILEBACK", PU_CACHE));
if (i != p->profilen || ((setup_animcounter/10) & 1))
{
const char *txt = pr->version ? pr->profilename : "NEW";
fixed_t w = V_StringScaledWidth(
FRACUNIT,
FRACUNIT,
FRACUNIT,
notSelectable,
FILE_FONT,
txt
);
V_DrawStringScaled(
((px+26) * FRACUNIT) - (w/2),
py * FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
notSelectable,
i == p->profilen ? R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SAPPHIRE, GTC_CACHE) : NULL,
FILE_FONT,
txt
);
}
}
py += 12;
}
V_ClearClipRect();
}
// "Changes?"
else if (p->mdepth == CSSTEP_ASKCHANGES)
{
UINT8 i;
char choices[2][9] = {"ALL GOOD", "CHANGE"};
INT32 xpos = x+8;
INT32 ypos = y+38;
V_DrawFileString(xpos, ypos, 0, "READY?");
for (i = 0; i < 2; i++)
{
UINT8 cy = ypos+16 + (i*10);
if (p->changeselect == i)
M_DrawCursorHand(xpos + 20, cy);
V_DrawThinString(xpos+16, cy, (p->changeselect == i ? highlightflags : 0), choices[i]);
}
}
if (p->showextra == true)
{
INT32 randomskin = 0;
switch (p->mdepth)
{
case CSSTEP_ALTS: // Select clone
case CSSTEP_READY:
if (p->clonenum < setup_chargrid[p->gridx][p->gridy].numskins
&& setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum] < numskins)
{
V_DrawThinString(x-3, y+12, 0,
skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].name);
randomskin = (skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].flags & SF_IRONMAN);
}
else
{
V_DrawThinString(x-3, y+12, 0, va("BAD CLONENUM %u", p->clonenum));
}
/* FALLTHRU */
case CSSTEP_CHARS: // Character Select grid
V_DrawThinString(x-3, y+2, 0, va("Class %c (s %c - w %c)",
('A' + R_GetEngineClass(p->gridx+1, p->gridy+1, randomskin)),
(randomskin
? '?' : ('1'+p->gridx)),
(randomskin
? '?' : ('1'+p->gridy))
));
break;
case CSSTEP_COLORS: // Select color
if (p->color < numskincolors)
{
V_DrawThinString(x-3, y+2, 0, skincolors[p->color].name);
}
else
{
V_DrawThinString(x-3, y+2, 0, va("BAD COLOR %u", p->color));
}
break;
case CSSTEP_FOLLOWERCATEGORY:
if (p->followercategory == -1)
{
V_DrawThinString(x-3, y+2, 0, "None");
}
else
{
V_DrawThinString(x-3, y+2, 0,
followercategories[setup_followercategories[p->followercategory][1]].name);
}
break;
case CSSTEP_FOLLOWER:
if (p->followern == -1)
{
V_DrawThinString(x-3, y+2, 0, "None");
}
else
{
V_DrawThinString(x-3, y+2, 0,
followers[p->followern].name);
}
break;
case CSSTEP_FOLLOWERCOLORS:
if (p->followercolor == FOLLOWERCOLOR_MATCH)
{
V_DrawThinString(x-3, y+2, 0, "Match");
}
else if (p->followercolor == FOLLOWERCOLOR_OPPOSITE)
{
V_DrawThinString(x-3, y+2, 0, "Opposite");
}
else if (p->followercolor < numskincolors)
{
V_DrawThinString(x-3, y+2, 0, skincolors[p->followercolor].name);
}
else
{
V_DrawThinString(x-3, y+2, 0, va("BAD FOLLOWERCOLOR %u", p->followercolor));
}
break;
default:
V_DrawThinString(x-3, y+2, 0, "[extrainfo mode]");
break;
}
}
}
static void M_DrawCharSelectExplosions(boolean charsel, INT16 basex, INT16 basey)
{
UINT8 i;
INT16 quadx = 2, quady = 2, mul = 22;
for (i = 0; i < CSEXPLOSIONS; i++)
{
UINT8 *colormap;
UINT8 frame;
if (setup_explosions[i].tics == 0 || setup_explosions[i].tics > 5)
continue;
frame = 6 - setup_explosions[i].tics;
if (charsel)
{
quadx = 4 * (setup_explosions[i].x / 3);
quady = 4 * (setup_explosions[i].y / 3);
mul = 16;
}
colormap = R_GetTranslationColormap(TC_DEFAULT, setup_explosions[i].color, GTC_MENUCACHE);
V_DrawMappedPatch(
basex + (setup_explosions[i].x*mul) + quadx - 6,
basey + (setup_explosions[i].y*mul) + quady - 6,
0, W_CachePatchName(va("CHCNFRM%d", frame), PU_CACHE),
colormap
);
}
}
#define IDLELEN 8
#define SELECTLEN (8 + IDLELEN + 7 + IDLELEN)
static void M_DrawCharSelectCursor(UINT8 num)
{
static const char *idleframes[IDLELEN] = {
"CHHOV1", "CHHOV1", "CHHOV1", "CHHOV2", "CHHOV1", "CHHOV3", "CHHOV1", "CHHOV2"
};
static const char *selectframesa[SELECTLEN] = {
"CHHOV1", "CHPIKA1", "CHHOV2", "CHPIKA2", "CHHOV3", "CHPIKA3", "CHHOV2", "CHPIKA4",
"CHHOV1", "CHHOV1", "CHHOV1", "CHHOV2", "CHHOV1", "CHHOV3", "CHHOV1", "CHHOV2",
"CHPIKA5", "CHHOV2", "CHPIKA6", "CHHOV3", "CHPIKA7", "CHHOV2", "CHPIKA8",
"CHHOV1", "CHHOV1", "CHHOV1", "CHHOV2", "CHHOV1", "CHHOV3", "CHHOV1", "CHHOV2"
};
static const char *selectframesb[SELECTLEN] = {
NULL, "CHPIKB1", NULL, "CHPIKB2", NULL, "CHPIKB3", NULL, "CHPIKB4",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
"CHPIKB5", NULL, "CHPIKB6", NULL, "CHPIKB7", NULL, "CHPIKB8",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
setup_player_t *p = &setup_player[num];
char letter = 'A' + num;
UINT16 color = SKINCOLOR_NONE;
UINT8 *colormap;
INT16 x, y;
INT16 quadx, quady;
if (p->mdepth < CSSTEP_ASKCHANGES)
return;
quadx = 4 * (p->gridx / 3);
quady = 4 * (p->gridy / 3);
x = 82 + (p->gridx*16) + quadx - 13;
y = 22 + (p->gridy*16) + quady - 12;
// profiles skew the graphic to the right slightly
if (optionsmenu.profile)
x += 64;
color = p->color;
if (color == SKINCOLOR_NONE)
{
if (p->skin >= 0)
{
color = skins[p->skin].prefcolor;
}
else
{
color = SKINCOLOR_GREY;
}
}
colormap = R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE);
if (p->mdepth >= CSSTEP_READY)
{
V_DrawMappedPatch(x, y, 0, W_CachePatchName("CHCNFRM0", PU_CACHE), colormap);
}
else if (p->mdepth > CSSTEP_CHARS)
{
V_DrawMappedPatch(x, y, 0, W_CachePatchName(selectframesa[setup_animcounter % SELECTLEN], PU_CACHE), colormap);
if (selectframesb[(setup_animcounter-1) % SELECTLEN] != NULL)
V_DrawMappedPatch(x, y, V_TRANSLUCENT, W_CachePatchName(selectframesb[(setup_animcounter-1) % SELECTLEN], PU_CACHE), colormap);
}
else
{
V_DrawMappedPatch(x, y, 0, W_CachePatchName(idleframes[setup_animcounter % IDLELEN], PU_CACHE), colormap);
}
if (p->mdepth < CSSTEP_READY)
V_DrawMappedPatch(x, y, 0, W_CachePatchName(va("CSELH%c", letter), PU_CACHE), colormap);
}
#undef IDLE
#undef IDLELEN
#undef SELECTLEN
// Draw character profile card.
// Moved here because in the case of profile edition this is drawn in the charsel menu.
void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
{
setup_player_t *sp = &setup_player[0]; // When editing profile character, we'll always be checking for what P1 is doing.
patch_t *card = W_CachePatchName("PR_CARD", PU_CACHE);
patch_t *cardbot = W_CachePatchName("PR_CARDB", PU_CACHE);
patch_t *pwrlv = W_CachePatchName("PR_PWR", PU_CACHE);
UINT16 truecol = SKINCOLOR_BLACK;
UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLACK, GTC_CACHE);
INT32 skinnum = -1;
char pname[PROFILENAMELEN+1] = "NEW";
if (p != NULL && p->version)
{
truecol = p->color;
skinnum = R_SkinAvailableEx(p->skinname, false);
strcpy(pname, p->profilename);
}
if (sp->mdepth >= CSSTEP_CHARS)
{
truecol = sp->color;
skinnum = setup_chargrid[sp->gridx][sp->gridy].skinlist[sp->clonenum];
}
if (truecol == SKINCOLOR_NONE)
{
if (skinnum >= 0)
{
truecol = skins[skinnum].prefcolor;
}
else
{
truecol = SKINCOLOR_RED;
}
}
// Card
{
colormap = R_GetTranslationColormap(TC_DEFAULT, truecol, GTC_CACHE);
V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, greyedout ? V_TRANSLUCENT : 0, card, colormap);
}
if (greyedout)
return; // only used for profiles we can't select.
if (p != NULL)
{
V_DrawFixedPatch((x+30)*FRACUNIT, (y+84)*FRACUNIT, FRACUNIT, 0, pwrlv, colormap);
V_DrawCenteredTimerString(x+30, y+87, 0, va("%d", p->wins));
}
// check what setup_player is doing in priority.
if (sp->mdepth >= CSSTEP_CHARS)
{
if (skinnum >= 0)
{
UINT8 *ccolormap = R_GetTranslationColormap(skinnum, truecol, GTC_MENUCACHE);
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);
}
M_DrawCharSelectCircle(sp, x-22, y+104);
if (sp->mdepth >= CSSTEP_FOLLOWER)
{
if (M_DrawFollowerSprite(x-22 - 16, y+119, 0, false, 0, 0, sp))
{
UINT16 col = K_GetEffectiveFollowerColor(sp->followercolor, &followers[sp->followern], sp->color, &skins[sp->skin]);
patch_t *ico = W_CachePatchName(followers[sp->followern].icon, PU_CACHE);
UINT8 *fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap);
}
}
}
else if (skinnum > -1) // otherwise, read from profile.
{
UINT8 *ccolormap;
INT32 fln = K_FollowerAvailable(p->follower);
if (R_SkinUsable(g_localplayers[0], skinnum, false))
ccolormap = R_GetTranslationColormap(skinnum, truecol, GTC_MENUCACHE);
else
ccolormap = R_GetTranslationColormap(TC_BLINK, truecol, GTC_MENUCACHE);
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);
}
if (fln >= 0)
{
UINT16 fcol = K_GetEffectiveFollowerColor(
p->followercolor,
&followers[fln],
p->color,
&skins[skinnum]
);
UINT8 *fcolormap = R_GetTranslationColormap(
(K_FollowerUsable(fln) ? TC_DEFAULT : TC_BLINK),
fcol, GTC_MENUCACHE);
if (M_DrawFollowerSprite(x-22 - 16, y+119, fln, false, 0, fcolormap, NULL))
{
patch_t *ico = W_CachePatchName(followers[fln].icon, PU_CACHE);
V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap);
}
}
}
V_DrawCenteredGamemodeString(x, y+24, 0, 0, pname);
// Card bottom to overlay the skin preview
V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, cardbot, colormap);
// Profile number & player name
if (p != NULL)
{
V_DrawProfileNum(x + 37 + 10, y + 131, 0, PR_GetProfileNum(p));
V_DrawCenteredThinString(x, y + 141, V_GRAYMAP, p->playername);
V_DrawCenteredThinString(x, y + 151, V_GRAYMAP, GetPrettyRRID(p->public_key, true));
}
}
void M_DrawCharacterSelect(void)
{
const UINT8 pid = 0;
UINT8 i, j, k;
UINT8 priority = 0;
INT16 quadx, quady;
INT16 skin;
INT32 basex = optionsmenu.profile ? (64 + M_EaseWithTransition(Easing_InSine, 5 * 48)) : 0;
boolean forceskin = M_CharacterSelectForceInAction();
if (setup_numplayers > 0)
{
priority = setup_animcounter % setup_numplayers;
}
{
const int kLeft = 76;
const int kTop = 6;
const int kButtonWidth = 16;
INT32 x = basex + kLeft;
if (!optionsmenu.profile) // Does nothing on this screen
{
K_drawButton((x += 22) * FRACUNIT, (kTop - 3) * FRACUNIT, 0, kp_button_r, M_MenuButtonPressed(pid, MBT_R));
V_DrawThinString((x += kButtonWidth), kTop, 0, "Info");
}
K_drawButton((x += 58) * FRACUNIT, (kTop - 1) * FRACUNIT, 0, kp_button_c[1], M_MenuButtonPressed(pid, MBT_C));
V_DrawThinString((x += kButtonWidth), kTop, 0, "Default");
}
// We have to loop twice -- first time to draw the drop shadows, a second time to draw the icons.
if (forceskin == false)
{
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9; j++)
{
skin = setup_chargrid[i][j].skinlist[setup_page];
quadx = 4 * (i / 3);
quady = 4 * (j / 3);
// Here's a quick little cheat to save on drawing time!
// Don't draw a shadow if it'll get covered by another icon
if ((i % 3 < 2) && (j % 3 < 2))
{
if ((setup_chargrid[i+1][j].skinlist[setup_page] != -1)
&& (setup_chargrid[i][j+1].skinlist[setup_page] != -1)
&& (setup_chargrid[i+1][j+1].skinlist[setup_page] != -1))
continue;
}
if (skin != -1)
V_DrawScaledPatch(basex+ 82 + (i*16) + quadx + 1, 22 + (j*16) + quady + 1, 0, W_CachePatchName("ICONBACK", PU_CACHE));
}
}
}
// Draw this inbetween. These drop shadows should be covered by the stat graph, but the icons shouldn't.
V_DrawScaledPatch(basex+ 3, 2, 0, W_CachePatchName((optionsmenu.profile ? "PR_STGRPH" : "STATGRPH"), PU_CACHE));
// Draw the icons now
for (i = 0; i < 9; i++)
{
if ((forceskin == true) && (i != skins[cv_forceskin.value].kartspeed-1))
continue;
for (j = 0; j < 9; j++)
{
if (forceskin == true)
{
if (j != skins[cv_forceskin.value].kartweight-1)
continue;
skin = cv_forceskin.value;
}
else
{
skin = setup_chargrid[i][j].skinlist[setup_page];
}
for (k = 0; k < setup_numplayers; k++)
{
if (setup_player[k].mdepth < CSSTEP_ASKCHANGES)
continue;
if (setup_player[k].gridx != i || setup_player[k].gridy != j)
continue;
break; // k == setup_numplayers means no one has it selected
}
quadx = 4 * (i / 3);
quady = 4 * (j / 3);
if (skin != -1)
{
UINT8 *colormap;
if (k == setup_numplayers)
colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE);
else
colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
V_DrawMappedPatch(basex + 82 + (i*16) + quadx, 22 + (j*16) + quady, 0, faceprefix[skin][FACE_RANK], colormap);
// draw dot if there are more alts behind there!
if (forceskin == false && setup_page+1 < setup_chargrid[i][j].numskins)
V_DrawScaledPatch(basex + 82 + (i*16) + quadx, 22 + (j*16) + quady + 11, 0, W_CachePatchName("ALTSDOT", PU_CACHE));
}
}
}
// Explosions when you've made your final selection
M_DrawCharSelectExplosions(true, 82, 22);
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
// Draw a preview for each player
if (optionsmenu.profile == NULL)
{
M_DrawCharSelectPreview(i);
}
else if (i == 0)
{
M_DrawProfileCard(optionsmenu.optx, optionsmenu.opty, false, optionsmenu.profile);
}
if (i >= setup_numplayers)
continue;
// Draw the cursors
if (i != priority)
M_DrawCharSelectCursor(i);
}
if (setup_numplayers > 0)
{
// Draw the priority player over the other ones
M_DrawCharSelectCursor(priority);
}
}
// DIFFICULTY SELECT
// This is a mix of K_DrawKartGamemodeMenu and the generic menu drawer depending on what we need.
// This is only ever used here (I hope because this is starting to pile up on hacks to look like the old m_menu.c lol...)
void M_DrawRaceDifficulty(void)
{
patch_t *box = W_CachePatchName("M_DBOX", PU_CACHE);
INT32 i;
INT32 tx = M_EaseWithTransition(Easing_Linear, 5 * 48);
INT32 x = 120 + tx;
INT32 y = 48;
M_DrawMenuTooltips();
// Draw the box for difficulty...
V_DrawFixedPatch((111 + tx)*FRACUNIT, 33*FRACUNIT, FRACUNIT, 0, box, NULL);
for (i = 0; i < currentMenu->numitems; i++)
{
if (i >= currentMenu->extra1)
{
x = GM_STARTX + (GM_XOFFSET * 5 / 2);
y = GM_STARTY + (GM_YOFFSET * 5 / 2);
if (i < currentMenu->numitems-1)
{
x -= GM_XOFFSET;
y -= GM_YOFFSET;
}
x += tx;
}
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
// This is HACKY......
case IT_STRING2:
{
INT32 f = (i == itemOn) ? highlightflags : 0;
if (currentMenu->menuitems[i].status & IT_CVAR)
{
// implicitely we'll only take care of normal cvars
INT32 cx = 190 + tx;
consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar;
if (i == itemOn)
{
INT32 w = V_MenuStringWidth(cv->string, 0)/2;
M_DrawUnderline(124, 190 + w, y);
V_DrawMenuString(cx - 10 - w - (skullAnimCounter/5), y, highlightflags, "\x1C"); // left arrow
V_DrawMenuString(cx + w + 2 + (skullAnimCounter/5), y, highlightflags, "\x1D"); // right arrow
}
V_DrawCenteredMenuString(cx, y, f, cv->string);
}
V_DrawMenuString(124 + tx + (i == itemOn ? 1 : 0), y, f, currentMenu->menuitems[i].text);
if (i == itemOn)
{
M_DrawCursorHand(124 + tx, y);
}
y += 14;
break;
}
case IT_STRING:
{
INT32 cx = x;
UINT8 *colormap = NULL;
if (i == itemOn)
{
colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
if (i >= currentMenu->extra1)
{
cx -= Easing_OutSine(M_DueFrac(gm_flipStart, GM_FLIPTIME), 0, GM_XOFFSET / 2);
}
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE);
}
if (currentMenu->menuitems[i].status & (IT_CVAR | IT_ARROWS))
{
INT32 fx = (cx - tx);
INT32 centx = fx + (320-fx)/2 + (tx); // undo the menutransition movement to redo it here otherwise the text won't move at the same speed lole.
const char *val = currentMenu->menuitems[i].text;
if (currentMenu->menuitems[i].status & IT_CVAR)
{
consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar;
val = cv->string;
}
V_DrawFixedPatch(cx*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, W_CachePatchName("MENUSHRT", PU_CACHE), colormap);
V_DrawCenteredGamemodeString(centx, y - 3, 0, colormap, val);
if (i == itemOn)
{
patch_t *arr_r = W_CachePatchName("GM_ARRL", PU_CACHE);
patch_t *arr_l = W_CachePatchName("GM_ARRR", PU_CACHE);
V_DrawFixedPatch((centx-54 - arr_r->width - (skullAnimCounter/5))*FRACUNIT, (y-3)*FRACUNIT, FRACUNIT, 0, arr_r, colormap);
V_DrawFixedPatch((centx+54 + (skullAnimCounter/5))*FRACUNIT, (y-3)*FRACUNIT, FRACUNIT, 0, arr_l, colormap);
}
}
else // not a cvar
{
V_DrawFixedPatch(cx*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, W_CachePatchName("MENUPLTR", PU_CACHE), colormap);
V_DrawGamemodeString(cx + 16, y - 3, 0, colormap, currentMenu->menuitems[i].text);
}
x += GM_XOFFSET;
y += GM_YOFFSET;
if (i < currentMenu->extra1)
{
y += 2; // extra spacing for Match Race options
}
break;
}
case IT_PATCH:
{
extern menu_anim_t g_drace_timer;
const menuitem_t *it = &currentMenu->menuitems[i];
boolean activated = g_drace_timer.dist == i;
boolean flicker = activated && (I_GetTime() - g_drace_timer.start) % 2 < 1;
INT32 cx = it->mvar1 + tx;
INT32 cy = 79;
const char *pat = i == drace_mritems && cv_thunderdome.value ? "RBOXTOGG" : it->patch;
V_DrawMappedPatch(cx, cy, 0, W_CachePatchName(pat, PU_CACHE),
flicker ? R_GetTranslationColormap(TC_HITLAG, 0, GTC_MENUCACHE) : NULL);
if (it->itemaction.cvar && !it->itemaction.cvar->value)
{
V_DrawMappedPatch(cx, cy, 0, W_CachePatchName("OFF_TOGG", PU_CACHE), NULL);
}
patch_t **bt = NULL;
switch (it->mvar2)
{
case MBT_Y:
bt = kp_button_y[1];
break;
case MBT_Z:
bt = kp_button_z[1];
break;
}
if (bt)
{
K_drawButton((cx + 24) * FRACUNIT, (cy + 22) * FRACUNIT, 0, bt, activated);
}
break;
}
}
}
}
// LEVEL SELECT
static void M_DrawCupPreview(INT16 y, levelsearch_t *baselevelsearch)
{
levelsearch_t locklesslevelsearch = *baselevelsearch; // full copy
locklesslevelsearch.checklocked = false;
UINT8 i = 0;
INT16 maxlevels = M_CountLevelsToShowInList(&locklesslevelsearch);
const fixed_t step = (82 * FRACUNIT);
fixed_t previewanimwork = (cupgrid.previewanim * FRACUNIT) + rendertimefrac_unpaused;
fixed_t x = -(previewanimwork % step);
INT16 map, start = M_GetFirstLevelInList(&i, &locklesslevelsearch);
UINT8 starti = i;
patch_t *staticpat = unvisitedlvl[cupgrid.previewanim % 4];
if (baselevelsearch->cup && maxlevels > 0)
{
INT16 add = (previewanimwork / step) % maxlevels;
map = start;
while (add > 0)
{
map = M_GetNextLevelInList(map, &i, &locklesslevelsearch);
if (map >= nummapheaders)
{
break;
}
add--;
}
while (x < BASEVIDWIDTH * FRACUNIT)
{
if (map >= nummapheaders)
{
map = start;
i = starti;
}
if (M_CanShowLevelInList(map, baselevelsearch))
{
K_DrawMapThumbnail(
x + FRACUNIT, (y+2)<<FRACBITS,
80<<FRACBITS,
0,
map,
NULL);
}
else
{
V_DrawFixedPatch(
x + FRACUNIT, (y+2) * FRACUNIT,
FRACUNIT,
0,
staticpat,
NULL);
}
x += step;
map = M_GetNextLevelInList(map, &i, &locklesslevelsearch);
}
}
else
{
while (x < BASEVIDWIDTH * FRACUNIT)
{
V_DrawFixedPatch(x + FRACUNIT, (y+2) * FRACUNIT, FRACUNIT, 0, staticpat, NULL);
x += step;
}
}
}
static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch)
{
UINT8 temp = 0;
V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE));
if (levelsearch->cup == &dummy_lostandfound)
{
V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, "Lost & Found");
}
else if (levelsearch->cup)
{
boolean unlocked = (M_GetFirstLevelInList(&temp, levelsearch) != NEXTMAP_INVALID);
UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE);
patch_t *icon = W_CachePatchName(levelsearch->cup->icon, PU_CACHE);
const char *str = (unlocked ? va("%s Cup", levelsearch->cup->realname) : "???");
INT16 offset = V_LSTitleLowStringWidth(str, 0) / 2;
V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str);
if (unlocked)
{
V_DrawMappedPatch(BASEVIDWIDTH/2 - offset - 24, y+5, 0, icon, colormap);
V_DrawMappedPatch(BASEVIDWIDTH/2 + offset + 3, y+5, 0, icon, colormap);
}
}
else
{
if (currentMenu == &PLAY_LevelSelectDef)
{
UINT8 namedgt = (levellist.guessgt != MAXGAMETYPES) ? levellist.guessgt : levellist.newgametype;
V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", gametypes[namedgt]->name));
}
}
}
fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 difficulty, boolean flash, boolean statsmode)
{
INT32 rankw = 14 + 1 + 12 + 1 + 12;
if (!cup)
return 0;
boolean noemerald = (!gamedata->everseenspecial || difficulty == KARTSPEED_EASY);
if (statsmode)
{
rankw += 10 + 1;
if (noemerald)
{
rankw -= (12 + 1);
rankx += 7; // vibes-based maths, as tyron puts it
}
}
rankx += 19 - (rankw / 2);
cupwindata_t *windata = &(cup->windata[difficulty]);
UINT8 emeraldnum = UINT8_MAX;
if (!noemerald)
{
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order
{
// Standard order.
if (windata->got_emerald == true)
{
emeraldnum = cup->emeraldnum;
}
}
else
{
// Determine order from sealedswaps.
UINT8 i;
for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
if (gamedata->sealedswaps[i] != cup)
continue;
break;
}
// If there's pending stars, get them from the associated cup order.
if (i < GDMAX_SEALEDSWAPS)
{
cupheader_t *emeraldcup = kartcupheaders;
while (emeraldcup)
{
if (emeraldcup->id >= basenumkartcupheaders)
{
emeraldcup = NULL;
break;
}
if (emeraldcup->emeraldnum == i+1)
{
if (emeraldcup->windata[difficulty].got_emerald == true)
emeraldnum = i+1;
break;
}
emeraldcup = emeraldcup->next;
}
}
}
}
if (windata->best_placement == 0)
{
if (statsmode)
{
V_DrawCharacter((10-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
rankx += 10 + 1;
V_DrawCharacter((14-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
rankx += 14 + 1;
V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
}
else
{
rankx += 14 + 1;
}
goto windataemeraldmaybe;
}
UINT8 *colormap = NULL;
if (statsmode)
{
const char monitorletter = (cup->monitor < 10) ? ('0' + cup->monitor) : ('A' + (cup->monitor - 10));
patch_t *monPat = W_CachePatchName(va("CUPMON%c%c", monitorletter, 'C'), PU_CACHE);
UINT16 moncolor = SKINCOLOR_NONE;
switch (windata->best_placement)
{
case 1:
moncolor = SKINCOLOR_GOLD;
break;
case 2:
moncolor = SKINCOLOR_SILVER;
break;
case 3:
moncolor = SKINCOLOR_BRONZE;
break;
default:
moncolor = SKINCOLOR_BEIGE;
break;
}
if (moncolor != SKINCOLOR_NONE)
colormap = R_GetTranslationColormap(TC_RAINBOW, moncolor, GTC_MENUCACHE);
if (monPat)
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, monPat, colormap);
rankx += 10 + 1;
colormap = NULL;
}
const gp_rank_e grade = windata->best_grade; // (cupgrid.previewanim/TICRATE) % (GRADE_S + 1); -- testing
patch_t *gradePat = NULL;
UINT16 gradecolor = K_GetGradeColor(grade);
if (gradecolor != SKINCOLOR_NONE)
colormap = R_GetTranslationColormap(TC_DEFAULT, gradecolor, GTC_MENUCACHE);
switch (grade)
{
case GRADE_E:
gradePat = W_CachePatchName("R_CUPRNE", PU_CACHE);
break;
case GRADE_D:
gradePat = W_CachePatchName("R_CUPRND", PU_CACHE);
break;
case GRADE_C:
gradePat = W_CachePatchName("R_CUPRNC", PU_CACHE);
break;
case GRADE_B:
gradePat = W_CachePatchName("R_CUPRNB", PU_CACHE);
break;
case GRADE_A:
gradePat = W_CachePatchName("R_CUPRNA", PU_CACHE);
break;
case GRADE_S:
gradePat = W_CachePatchName("R_CUPRNS", PU_CACHE);
break;
default:
break;
}
if (gradePat)
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, gradePat, colormap);
rankx += 14 + 1;
patch_t *charPat = NULL;
if ((windata->best_skin.unloaded != NULL)
|| (windata->best_skin.id > numskins))
{
colormap = NULL;
charPat = W_CachePatchName("HUHMAP", PU_CACHE);
}
else
{
UINT8 skin = windata->best_skin.id;
colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
charPat = faceprefix[skin][FACE_MINIMAP];
}
if (charPat)
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, charPat, colormap);
windataemeraldmaybe:
rankx += 12 + 1;
if (noemerald)
;
else if (emeraldnum != UINT8_MAX)
{
if (emeraldnum == 0)
V_DrawCharacter(rankx+2, ranky+2, '+', false);
else
{
colormap = NULL;
if (!flash)
{
UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (emeraldnum-1) % 7;
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
}
const char *emname = va(
"%sMAP%c",
(emeraldnum > 7) ? "SUP" : "EME",
colormap ? '\0' : 'B'
);
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, W_CachePatchName(emname, PU_CACHE), colormap);
}
}
else if (statsmode)
{
V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
}
return rankw;
}
void M_DrawCup(cupheader_t *cup, fixed_t x, fixed_t y, INT32 lockedTic, boolean isTrophy, UINT8 placement)
{
patch_t *patch = NULL;
UINT8 *colormap = NULL;
INT16 icony = 7;
char status = 'A';
char monitor = '0';
if (isTrophy)
{
UINT16 col = SKINCOLOR_NONE;
switch (placement)
{
case 0:
break;
case 1:
col = SKINCOLOR_GOLD;
status = 'B';
break;
case 2:
col = SKINCOLOR_SILVER;
status = 'B';
break;
case 3:
col = SKINCOLOR_BRONZE;
status = 'B';
break;
default:
col = SKINCOLOR_BEIGE;
break;
}
if (col != SKINCOLOR_NONE)
colormap = R_GetTranslationColormap(TC_RAINBOW, col, GTC_MENUCACHE);
else
colormap = NULL;
}
if (cup == &dummy_lostandfound)
{
// No cup? Lost and found!
monitor = '0';
}
else
{
if (cup->monitor < 10)
{
monitor = '0' + cup->monitor;
if (monitor == '2')
{
icony = 5; // by default already 7px down, so this is really 2px further up
}
else if (monitor == '3')
{
icony = 6;
}
}
else
{
monitor = 'A' + (cup->monitor - 10);
if (monitor == 'X')
{
icony = 11;
}
}
}
patch = W_CachePatchName(va("CUPMON%c%c", monitor, status), PU_CACHE);
V_DrawFixedPatch(x, y, FRACUNIT, 0, patch, colormap);
if (cup == &dummy_lostandfound)
{
; // Only ever placed on the list if valid
}
else if (lockedTic != 0)
{
patch_t *st = W_CachePatchName(va("ICONST0%d", (lockedTic % 4) + 1), PU_CACHE);
V_DrawFixedPatch(x + (8 * FRACUNIT), y + (icony * FRACUNIT), FRACUNIT, 0, st, NULL);
}
else
{
V_DrawFixedPatch(x + (8 * FRACUNIT), y + (icony * FRACUNIT), FRACUNIT, 0, W_CachePatchName(cup->icon, PU_CACHE), NULL);
V_DrawFixedPatch(x + (8 * FRACUNIT), y + (icony * FRACUNIT), FRACUNIT, 0, W_CachePatchName("CUPBOX", PU_CACHE), NULL);
}
}
void M_DrawCupSelect(void)
{
UINT8 i, j, temp = 0;
INT16 x, y;
INT16 cy = M_EaseWithTransition(Easing_Linear, 5 * 30);
cupwindata_t *windata = NULL;
levelsearch_t templevelsearch = levellist.levelsearch; // full copy
for (i = 0; i < CUPMENU_COLUMNS; i++)
{
x = 14 + (i*42);
for (j = 0; j < (cupgrid.cache_secondrowlocked ? 1 : CUPMENU_ROWS); j++)
{
size_t id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS));
if (!cupgrid.builtgrid[id])
break;
templevelsearch.cup = cupgrid.builtgrid[id];
y = 20 + (j*44) - cy;
if (cupgrid.cache_secondrowlocked == true)
y += 28;
const boolean isGP = (cupgrid.grandprix && (cv_dummygpdifficulty.value >= 0 && cv_dummygpdifficulty.value < KARTGP_MAX));
if (isGP)
{
windata = &templevelsearch.cup->windata[cv_dummygpdifficulty.value];
}
M_DrawCup(
templevelsearch.cup,
x * FRACUNIT, y * FRACUNIT,
(M_GetFirstLevelInList(&temp, &templevelsearch) == NEXTMAP_INVALID) ? ((cupgrid.previewanim % 4) + 1) : 0,
isGP,
windata ? windata->best_placement : 0
);
if (cupgrid.grandprix == true
&& templevelsearch.cup == cupsavedata.cup
&& id != CUPMENU_CURSORID)
{
V_DrawScaledPatch(x + 32, y + 32, 0, W_CachePatchName("CUPBKUP1", PU_CACHE));
}
// used to be 8 + (j*100) - (30*menutransition.tics)
// but one-row mode means y has to be changed
// this is the difference between y and that
if (j == 0)
{
y -= 12; // (8) - (20)
}
else
{
y += 44; //(8 + 100) - (20 + 44)
}
if (windata)
{
M_DrawCupWinData(
x,
y,
templevelsearch.cup,
cv_dummygpdifficulty.value,
(cupgrid.previewanim & 1),
false
);
}
}
}
{
fixed_t tx = Easing_Linear(M_DueFrac(cupgrid.xslide.start, CUPMENU_SLIDETIME), cupgrid.xslide.dist * FRACUNIT, 0);
fixed_t ty = Easing_Linear(M_DueFrac(cupgrid.yslide.start, CUPMENU_SLIDETIME), cupgrid.yslide.dist * FRACUNIT, 0);
x = 14 + (cupgrid.x*42*FRACUNIT - tx) / FRACUNIT;
y = 20 + (cupgrid.y*44*FRACUNIT - ty) / FRACUNIT - cy;
}
if (cupgrid.cache_secondrowlocked == true)
y += 28;
V_DrawScaledPatch(x - 4, y - 1, 0, W_CachePatchName("CUPCURS", PU_CACHE));
templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID];
if (cupgrid.grandprix == true
&& templevelsearch.cup != NULL
&& templevelsearch.cup == cupsavedata.cup)
{
V_DrawScaledPatch(
14 + (cupgrid.x*42) + 32,
20 + (cupgrid.y*44) + 32
+ ((cupgrid.cache_secondrowlocked == true) ? 28 : 0),
0,
W_CachePatchName("CUPBKUP2", PU_CACHE)
);
}
INT16 ty = M_EaseWithTransition(Easing_Linear, 5 * 24);
V_DrawFill(0, 146 + ty, BASEVIDWIDTH, 54, 31);
M_DrawCupPreview(146 + ty, &templevelsearch);
M_DrawCupTitle(120 - ty, &templevelsearch);
if (cupgrid.numpages > 1)
{
x = 3 - (skullAnimCounter/5);
y = 20 + (44 - 1) - cy;
patch_t *cuparrow = W_CachePatchName("CUPARROW", PU_CACHE);
if (cupgrid.pageno != 0)
V_DrawScaledPatch(x, y, 0, cuparrow);
if (cupgrid.pageno != cupgrid.numpages-1)
V_DrawScaledPatch(BASEVIDWIDTH-x, y, V_FLIP, cuparrow);
}
}
static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map)
{
char word1[22];
char word2[22 + 2]; // actnum
UINT8 word1len = 0;
UINT8 word2len = 0;
INT16 x2 = x;
UINT8 i;
if (!mapheaderinfo[map]
|| (
!mapheaderinfo[map]->menuttl[0]
&& !mapheaderinfo[map]->lvlttl[0]
)
)
return;
if (!mapheaderinfo[map]->menuttl[0]
&& mapheaderinfo[map]->zonttl[0])
{
boolean one = true;
boolean two = true;
for (i = 0; i < 22; i++)
{
if (!one && !two)
break;
if (mapheaderinfo[map]->lvlttl[i] && one)
{
word1[word1len] = mapheaderinfo[map]->lvlttl[i];
word1len++;
}
else
one = false;
if (mapheaderinfo[map]->zonttl[i] && two)
{
word2[word2len] = mapheaderinfo[map]->zonttl[i];
word2len++;
}
else
two = false;
}
}
else
{
char *ttlsource =
mapheaderinfo[map]->menuttl[0]
? mapheaderinfo[map]->menuttl
: mapheaderinfo[map]->lvlttl;
// If there are 2 or more words:
// - Last word goes on word2
// - Everything else on word1
char *p = strrchr(ttlsource, ' ');
if (p)
{
word2len = strlen(p + 1);
memcpy(word2, p + 1, word2len);
}
else
p = ttlsource + strlen(ttlsource);
word1len = p - ttlsource;
memcpy(word1, ttlsource, word1len);
}
if (!mapheaderinfo[map]->menuttl[0] && mapheaderinfo[map]->actnum)
{
word2[word2len] = ' ';
word2len++;
word2[word2len] = '0' + mapheaderinfo[map]->actnum;
word2len++;
}
word1[word1len] = '\0';
word2[word2len] = '\0';
{
char addlen[3];
for (i = 0; i < 2; i++)
{
if (!word1[i])
break;
addlen[i] = word1[i];
}
addlen[i] = '\0';
x2 += V_LSTitleLowStringWidth(addlen, 0);
}
if (word1len)
V_DrawLSTitleHighString(x, y, 0, word1);
if (word2len)
V_DrawLSTitleLowString(x2, y+28, 0, word2);
}
static void M_DrawLevelSelectBlock(INT16 x, INT16 y, UINT16 map, boolean redblink, boolean greyscale)
{
UINT8 *colormap = NULL;
if (greyscale)
colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE);
if (redblink)
V_DrawScaledPatch(3+x, y, 0, W_CachePatchName("LVLSEL2", PU_CACHE));
else
V_DrawScaledPatch(3+x, y, 0, W_CachePatchName("LVLSEL", PU_CACHE));
K_DrawMapThumbnail(
(9+x)<<FRACBITS, (y+6)<<FRACBITS,
80<<FRACBITS,
0,
map,
colormap);
M_DrawHighLowLevelTitle(98+x, y+8, map);
if (levellist.levelsearch.tutorial && !(mapheaderinfo[map]->records.mapvisited & MV_BEATEN))
{
V_DrawScaledPatch(
x + 80 + 3, y + 50, 0,
W_CachePatchName(
va(
"CUPBKUP%c",
(greyscale ? '1' : '2')
),
PU_CACHE
)
);
}
}
void M_DrawLevelSelect(void)
{
INT16 i = 0;
UINT8 j = 0;
INT16 map = M_GetFirstLevelInList(&j, &levellist.levelsearch);
INT16 t = M_EaseWithTransition(Easing_Linear, 5 * 64), tay = 0;
INT16 y = 80 - levellist.y +
Easing_OutSine(
M_DueFrac(levellist.slide.start, 4),
levellist.slide.dist,
0
);
boolean tatransition = ((menutransition.startmenu == &PLAY_TimeAttackDef || menutransition.endmenu == &PLAY_TimeAttackDef) && menutransition.tics != menutransition.dest);
if (tatransition)
{
t = -t;
tay = t/2;
}
while (true)
{
INT16 lvlx = t, lvly = y;
if (map >= nummapheaders)
break;
if (i == levellist.cursor && tatransition)
{
lvlx = 0;
lvly = max(2, y+tay);
}
M_DrawLevelSelectBlock(lvlx, lvly, map,
(i == levellist.cursor && (((skullAnimCounter / 4) & 1) || tatransition)),
(i != levellist.cursor)
);
y += 72;
i++;
map = M_GetNextLevelInList(map, &j, &levellist.levelsearch);
}
M_DrawCupTitle(tay, &levellist.levelsearch);
}
static boolean M_LevelSelectHasBG(menu_t *check)
{
if (check == NULL)
check = currentMenu;
return (check == &PLAY_LevelSelectDef
|| check == &PLAY_CupSelectDef);
}
static boolean M_LevelSelectToTimeAttackTransitionHelper(void)
{
return (M_LevelSelectHasBG(menutransition.startmenu))
!= M_LevelSelectHasBG(menutransition.endmenu);
}
void M_DrawSealedBack(void)
{
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
if (M_LevelSelectHasBG(currentMenu) == false)
return;
INT32 translucencylevel = 7;
if (M_LevelSelectToTimeAttackTransitionHelper())
{
translucencylevel += menutransition.tics/3;
if (translucencylevel >= 10)
return;
}
V_DrawFixedPatch(
0, 0,
FRACUNIT,
translucencylevel << V_ALPHASHIFT,
W_CachePatchName("MENUI008", PU_CACHE),
R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLACK, GTC_CACHE)
);
}
void M_DrawTimeAttack(void)
{
UINT16 map = levellist.choosemap;
INT16 t = M_EaseWithTransition(Easing_Linear, 5 * 48);
INT16 leftedge = 149+t+16;
INT16 rightedge = 149+t+155;
INT16 opty = 140;
INT32 w;
UINT8 i;
V_DrawScaledPatch(149+t, 70, 0, W_CachePatchName("BESTTIME", PU_CACHE));
if (!mapheaderinfo[map])
{
V_DrawRightAlignedMenuString(rightedge-12, opty, 0, "No map!?");
return;
}
{
patch_t *minimap = NULL;
INT32 minimapx = 76, minimapy = 130;
if (M_LevelSelectToTimeAttackTransitionHelper())
minimapx -= t;
if (levellist.newgametype == GT_SPECIAL)
{
// Star Within The Seal
#define SEAL_PULSELEN ((6*TICRATE)/5) // The rate of O_SSTAR3
INT32 crossfade = (timeattackmenu.ticker % (2*SEAL_PULSELEN)) - SEAL_PULSELEN;
if (crossfade < 0)
crossfade = -crossfade;
crossfade = (crossfade * 10)/SEAL_PULSELEN;
#undef SEAL_PULSELEN
if (crossfade != 10)
{
minimap = W_CachePatchName(
"K_FINB05",
PU_CACHE
);
V_DrawScaledPatch(
minimapx, minimapy,
0, minimap
);
}
if (crossfade != 0)
{
minimap = W_CachePatchName(
"K_FINB04",
PU_CACHE
);
V_DrawScaledPatch(
minimapx, minimapy,
(10-crossfade)<<V_ALPHASHIFT,
minimap
);
}
}
else if (levellist.newgametype == GT_VERSUS)
{
const INT32 teaserh = 56;
minimapy -= 1; // tiny adjustment
V_DrawScaledPatch(
minimapx, minimapy - teaserh,
V_TRANSLUCENT, W_CachePatchName("K_TEASR2", PU_CACHE)
);
V_DrawScaledPatch(
minimapx, minimapy + teaserh,
V_TRANSLUCENT, W_CachePatchName("K_TEASR4", PU_CACHE)
);
V_DrawScaledPatch(
minimapx, minimapy,
0, W_CachePatchName("K_TEASR3", PU_CACHE)
);
}
else if ((minimap = mapheaderinfo[map]->minimapPic))
{
V_DrawScaledPatch(
minimapx - (SHORT(minimap->width)/2),
minimapy - (SHORT(minimap->height)/2),
0, minimap
);
}
}
if (currentMenu == &PLAY_TimeAttackDef)
{
recorddata_t *rcp = &mapheaderinfo[map]->records;
recordtimes_t *record = cv_dummyspbattack.value ? &rcp->spbattack : &rcp->timeattack;
tic_t timerec = record->time;
tic_t laprec = record->lap;
UINT32 timeheight = 82;
if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT)
&& (mapheaderinfo[map]->numlaps != 1))
{
V_DrawRightAlignedMenuString(rightedge-12, timeheight, M_ALTCOLOR, "BEST LAP:");
K_drawKartTimestamp(laprec, 162+t, timeheight+6, 0, 2);
timeheight += 30;
}
else
{
timeheight += 15;
}
V_DrawRightAlignedMenuString(rightedge-12, timeheight, M_ALTCOLOR, "BEST TIME:");
K_drawKartTimestamp(timerec, 162+t, timeheight+6, 0, 1);
// SPB Attack control hint + menu overlay
{
INT32 buttonx = 162 + t;
INT32 buttony = timeheight;
if (M_EncoreAttackTogglePermitted())
{
K_drawButtonAnim(buttonx + 35, buttony - 3, 0, kp_button_r, timeattackmenu.ticker);
}
if ((timeattackmenu.spbflicker == 0 || timeattackmenu.ticker % 2) == (cv_dummyspbattack.value == 1))
{
V_DrawMappedPatch(buttonx + 7, buttony - 1, 0, W_CachePatchName("K_SPBATK", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE));
}
}
}
else
opty = 80;
// Done after to overlay material
M_DrawLevelSelectBlock(0, 2, map, true, false);
for (i = 0; i < currentMenu->numitems; i++)
{
UINT32 f = (i == itemOn) ? highlightflags : 0;
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_HEADERTEXT:
V_DrawMenuString(leftedge, opty, M_ALTCOLOR, currentMenu->menuitems[i].text);
opty += 10;
break;
case IT_STRING:
if (i >= currentMenu->numitems-1)
{
V_DrawRightAlignedMenuString(rightedge, opty, f, currentMenu->menuitems[i].text);
if (i == itemOn)
M_DrawCursorHand(rightedge - V_MenuStringWidth(currentMenu->menuitems[i].text, 0), opty);
}
else
{
V_DrawMenuString(leftedge, opty, f, currentMenu->menuitems[i].text);
if (i == itemOn)
M_DrawCursorHand(leftedge, opty);
}
opty += 10;
// Cvar specific handling
{
const char *str = NULL;
INT32 optflags = f;
boolean drawarrows = (i == itemOn);
if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_ARROWS)
{
// Currently assumes M_HandleStaffReplay
if (mapheaderinfo[levellist.choosemap]->ghostCount <= 1)
drawarrows = false;
if (mapheaderinfo[levellist.choosemap] == NULL)
str = "Invalid map";
else if (cv_dummystaff.value > mapheaderinfo[levellist.choosemap]->ghostCount)
str = va("%u - Invalid ID", cv_dummystaff.value+1);
else if (mapheaderinfo[levellist.choosemap]->ghostBrief[cv_dummystaff.value] == NULL)
str = va("%u - Invalid brief", cv_dummystaff.value+1);
else
{
const char *th = "th";
if (cv_dummystaff.value+1 == 1)
th = "st";
else if (cv_dummystaff.value+1 == 2)
th = "nd";
else if (cv_dummystaff.value+1 == 3)
th = "rd";
str = va("%u%s - %s",
cv_dummystaff.value+1,
th,
mapheaderinfo[levellist.choosemap]->ghostBrief[cv_dummystaff.value]->name
);
}
}
else if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR)
{
str = currentMenu->menuitems[i].itemaction.cvar->string;
}
if (str)
{
w = V_MenuStringWidth(str, optflags);
V_DrawMenuString(leftedge+12, opty, optflags, str);
if (drawarrows)
{
V_DrawMenuString(leftedge+12 - 10 - (skullAnimCounter/5), opty, f, "\x1C"); // left arrow
V_DrawMenuString(leftedge+12 + w + 2+ (skullAnimCounter/5), opty, f, "\x1D"); // right arrow
}
opty += 10;
}
}
break;
case IT_SPACE:
opty += 4;
break;
}
}
}
// This draws the options of a given menu in a fashion specific to the multiplayer option select screen (host game / server browser etc)
// First argument is the menu to draw the options from, the 2nd one is a table that contains which option is to be "extendded"
// Format for each option: {extended? (only used by the ticker), max extension (likewise), # of lines to extend}
// NOTE: This is pretty rigid and only intended for use with the multiplayer options menu which has *3* choices.
void M_DrawMasterServerReminder(void)
{
// Did you change the Server Browser address? Have a little reminder.
INT32 mservflags = 0;
if (CV_IsSetToDefault(&cv_masterserver))
mservflags = highlightflags;
else
mservflags = warningflags;
INT32 y = BASEVIDHEIGHT - 10;
V_DrawFadeFill(0, y-1, BASEVIDWIDTH, 10+1, 0, 31, 5);
V_DrawCenteredThinString(BASEVIDWIDTH/2, y,
mservflags, va("List via \"%s\"", cv_masterserver.string));
}
static void M_MPOptDrawer(menu_t *m, INT16 extend[3][3])
{
// This is a copypaste of the generic gamemode menu code with a few changes.
// TODO: Allow specific options to "expand" into smaller ones.
patch_t *buttback = W_CachePatchName("M_PLAT2", PU_CACHE);
INT32 i, x = 132, y = 32; // Dirty magic numbers for now but they work out.
for (i = 0; i < m->numitems; i++)
{
switch (m->menuitems[i].status & IT_DISPLAY)
{
case IT_STRING:
{
UINT8 *colormap = NULL;
INT16 j = 0;
if ((currentMenu == m && i == itemOn) || extend[i][0]) // Selected / unfolded
{
colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE);
}
if (extend[i][2])
{
for (j=0; j <= extend[i][2]/2; j++)
{
// Draw rectangles that look like the current selected item starting from the top of the actual selection graphic and going up to where it's supposed to go.
// With colour 169 (that's the index of the shade of black the plague colourization gives us. ...No I don't like using a magic number either.
V_DrawFill((x-1) + (extend[i][2]/2) - j - (buttback->width/2), (y + extend[i][2]) - (2*j), 226, 2, 169);
}
}
V_DrawFixedPatch((x + (extend[i][2]/2)) *FRACUNIT, (y + extend[i][2])*FRACUNIT, FRACUNIT, 0, buttback, colormap);
V_DrawCenteredGamemodeString(x, y - 3, 0, colormap, m->menuitems[i].text);
}
break;
}
x += GM_XOFFSET;
y += GM_YOFFSET + extend[i][2];
}
}
// Draws the EGGA CHANNEL background.
void M_DrawEggaChannel(void)
{
patch_t *background = W_CachePatchName("M_EGGACH", PU_CACHE);
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 25);
V_DrawFixedPatch((menuactive ? 75 : 160)<<FRACBITS, 104<<FRACBITS, FRACUNIT, 0, background, NULL);
V_DrawVhsEffect(false); // VHS the background! (...sorry OGL my love)
}
// Multiplayer mode option select
void M_DrawMPOptSelect(void)
{
M_DrawMenuTooltips();
M_MPOptDrawer(&PLAY_MP_OptSelectDef, mpmenu.modewinextend);
M_DrawMasterServerReminder();
}
// Multiplayer mode option select: HOST GAME!
// A rehash of the generic menu drawer adapted to fit into that cramped space. ...A small sacrifice for utility
void M_DrawMPHost(void)
{
patch_t *gobutt = W_CachePatchName("M_BUTTGO", PU_CACHE); // I'm very mature
INT32 xp = 40, yp = 64, i = 0, w = 0; // Starting position for the text drawing.
M_DrawMPOptSelect(); // Draw the Multiplayer option select menu first
// Now draw our host options...
for (i = 0; i < currentMenu->numitems; i++)
{
if (i == currentMenu->numitems-1)
{
xp = 202;
yp = 100;
UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE);
if (i == itemOn)
colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
// Ideally we'd calculate this but it's not worth it for a 1-off menu probably.....
V_DrawFixedPatch(xp<<FRACBITS, yp<<FRACBITS, FRACUNIT, 0, gobutt, colormap);
V_DrawCenteredGamemodeString(xp + (gobutt->width/2), yp -3, 0, colormap, currentMenu->menuitems[i].text);
}
else
{
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_TRANSTEXT2:
{
V_DrawThinString(xp, yp, V_TRANSLUCENT, currentMenu->menuitems[i].text);
xp += 5;
yp += 11;
break;
}
case IT_STRING:
{
V_DrawThinString(xp, yp, (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text);
// Cvar specific handling
switch (currentMenu->menuitems[i].status & IT_TYPE)
{
case IT_CVAR:
{
consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar;
switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
{
case IT_CV_STRING:
{
INT32 xoffs = 0;
if (itemOn == i)
{
xoffs += 8;
V_DrawString(xp + (skullAnimCounter/5) + 94, yp+1, highlightflags, "\x1D");
}
V_DrawThinString(xp + xoffs + 96, yp, 0, cv->string);
}
break;
default:
w = V_ThinStringWidth(cv->string, 0);
V_DrawThinString(xp + 138 - w, yp, ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string);
if (i == itemOn)
{
V_DrawCharacter(xp + 138 - 10 - w - (skullAnimCounter/5), yp, '\x1C' | highlightflags, false); // left arrow
V_DrawCharacter(xp + 138 + 2 + (skullAnimCounter/5), yp, '\x1D' | highlightflags, false); // right arrow
}
break;
}
break;
}
case IT_ARROWS:
{
if (currentMenu->menuitems[i].itemaction.routine != M_HandleHostMenuGametype)
break;
w = V_ThinStringWidth(gametypes[menugametype]->name, 0);
V_DrawThinString(xp + 138 - w, yp, highlightflags, gametypes[menugametype]->name);
if (i == itemOn)
{
V_DrawCharacter(xp + 138 - 10 - w - (skullAnimCounter/5), yp, '\x1C' | highlightflags, false); // left arrow
V_DrawCharacter(xp + 138 + 2 + (skullAnimCounter/5), yp, '\x1D' | highlightflags, false); // right arrow
}
break;
}
}
xp += 5;
yp += 11;
break;
}
break;
}
}
}
}
// Multiplayer mode option select: JOIN BY IP
// Once again we'll copypaste 1 bit of the generic menu handler we used for hosting but we only need it for IT_CV_STRING since that's all we got :)
// (I don't like duplicating code but I rather this than some horrible all-in-one function with too many options...)
void M_DrawMPJoinIP(void)
{
//patch_t *minibutt = W_CachePatchName("M_SBUTT", PU_CACHE);
// There is no such things as mini butts, only thick thighs to rest your head on.
//patch_t *minigo = W_CachePatchName("M_SGO", PU_CACHE);
patch_t *typebar = W_CachePatchName("M_TYPEB", PU_CACHE);
//UINT8 *colormap = NULL;
UINT8 *colormapc = NULL;
INT32 xp = 73, yp = 133, i = 0; // Starting position for the text drawing.
M_DrawMPOptSelect(); // Draw the Multiplayer option select menu first
// Now draw our host options...
for (i = 0; i < currentMenu->numitems; i++)
{
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_STRING:
{
char str[MAX_LOGIP];
strcpy(str, currentMenu->menuitems[i].text);
// The last 3 options of this menu are to be the joined IP addresses...
if (currentMenu->numitems - i <= NUMLOGIP)
{
UINT8 index = NUMLOGIP - (currentMenu->numitems - i);
if (index == 0)
{
xp += 8;
}
if (joinedIPlist[index][1][0]) // Try drawing server name
strlcpy(str, joinedIPlist[index][1], MAX_LOGIP);
else if (joinedIPlist[index][0][0]) // If that fails, get the address
strlcpy(str, joinedIPlist[index][0], MAX_LOGIP);
else
strcpy(str, "---"); // If that fails too then there's nothing!
}
V_DrawThinString(xp, yp, ((i == itemOn || currentMenu->menuitems[i].status & IT_SPACE) ? highlightflags : 0), str);
// Cvar specific handling
switch (currentMenu->menuitems[i].status & IT_TYPE)
{
case IT_CVAR:
{
consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar;
switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
{
case IT_CV_STRING:
//colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE);
colormapc = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
V_DrawFixedPatch((xp + 12)<<FRACBITS, (yp-2)<<FRACBITS, FRACUNIT, 0, typebar, colormapc); // Always consider that this is selected otherwise it clashes.
{
INT32 xoffs = 0;
if (itemOn == i)
{
xoffs += 8;
V_DrawString(xp + (skullAnimCounter/5) + 17, yp+1, highlightflags, "\x1D");
}
V_DrawThinString(xp + xoffs + 18, yp, 0, cv->string);
}
/*// On this specific menu the only time we'll ever see this is for the connect by IP typefield.
// Draw the small GO button here (and the text which is a separate graphic)
V_DrawFixedPatch((xp + 20 + typebar->width -4)<<FRACBITS, (yp-3)<<FRACBITS, FRACUNIT, 0, minibutt, i == itemOn ? colormapc : colormap);
V_DrawFixedPatch((xp + 20 + typebar->width -4 + (minibutt->width/2))<<FRACBITS, (yp-3-5)<<FRACBITS, FRACUNIT, 0, minigo, i == itemOn ? colormapc : NULL);*/
break;
default:
break;
}
break;
}
}
xp += 5;
yp += 11;
break;
}
break;
}
}
}
// Multiplayer room select
void M_DrawMPRoomSelect(void)
{
// Greyscale colormaps for the option that's not selected's background
UINT8 *colormap_l = NULL;
UINT8 *colormap_r = NULL;
patch_t *bg_l = W_CachePatchName("BG_MPS21", PU_CACHE);
patch_t *bg_r = W_CachePatchName("BG_MPS22", PU_CACHE);
patch_t *split = W_CachePatchName("MPSPLIT1", PU_CACHE);
patch_t *butt1[] = {W_CachePatchName("MP_B1", PU_CACHE), W_CachePatchName("MP_B12", PU_CACHE)};
patch_t *butt2[] = {W_CachePatchName("MP_B2", PU_CACHE), W_CachePatchName("MP_B22", PU_CACHE)};
patch_t *scrollp[] = {W_CachePatchName("MP_SCR1", PU_CACHE), W_CachePatchName("MP_SCR2", PU_CACHE)};
patch_t *drawp = scrollp[mpmenu.room];
fixed_t scrollposx[] = {(BASEVIDWIDTH/4)<<FRACBITS, (BASEVIDWIDTH/2 + BASEVIDWIDTH/4)<<FRACBITS};
UINT8 i;
INT32 soffy = 0;
if (mpmenu.room)
colormap_l = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_CACHE);
else
colormap_r = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_CACHE);
// Draw the 2 sides of the background
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg_l, colormap_l);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg_r, colormap_r);
// Draw the black split:
V_DrawFixedPatch(160<<FRACBITS, 0, FRACUNIT, 0, split, NULL);
// Vertical scrolling stuff
for (i = 0; i < 3; i++)
{
V_DrawFixedPatch(scrollposx[mpmenu.room], ((-((mpmenu.ticker*2) % drawp->height)) + soffy)*FRACUNIT , FRACUNIT, V_ADD, drawp, NULL);
soffy += scrollp[mpmenu.room]->height;
}
// Draw buttons:
V_DrawFixedPatch(160<<FRACBITS, 90<<FRACBITS, FRACUNIT, mpmenu.room ? (5<<V_ALPHASHIFT) : 0, butt1[(mpmenu.room) ? 1 : 0], NULL);
V_DrawFixedPatch(160<<FRACBITS, 90<<FRACBITS, FRACUNIT, (!mpmenu.room) ? (5<<V_ALPHASHIFT) : 0, butt2[(!mpmenu.room) ? 1 : 0], NULL);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL);
V_DrawCenteredMenuString(BASEVIDWIDTH/2, 24, 0, "\xA3 Select a Room \xA2");
V_DrawCenteredThinString(BASEVIDWIDTH/2, 12, 0, (mpmenu.room) ? "Play with community maps, characters, and gametypes. (Expect additional downloads!)" : "Jump into a standard game of Ring Racers.");
M_DrawMasterServerReminder();
}
// SERVER BROWSER
static void M_DrawServerCountAndHorizontalBar(void)
{
const char *text;
INT32 y = currentMenu->y+STRINGHEIGHT;
const char throbber[4] = {'-', '\\', '|', '/'};
UINT8 throbindex = (mpmenu.ticker/4) % 4;
switch (M_GetWaitingMode())
{
case M_WAITING_VERSION:
text = "Checking for updates...";
break;
case M_WAITING_SERVERS:
text = "Loading server list...";
break;
default:
if (serverlistultimatecount > serverlistcount)
{
text = va("%d/%d server%s found...",
serverlistcount,
serverlistultimatecount,
serverlistultimatecount == 1 ? "" : "s"
);
}
else
{
throbindex = UINT8_MAX; // No throbber!
text = va("%d server%s found",
serverlistcount,
serverlistcount == 1 ? "" : "s"
);
}
}
if (throbindex == UINT8_MAX)
{
V_DrawRightAlignedString(
BASEVIDWIDTH - currentMenu->x,
y,
highlightflags,
text
);
}
else
{
V_DrawRightAlignedString(
BASEVIDWIDTH - currentMenu->x - 12, y,
highlightflags,
text
);
V_DrawCenteredString( // Only clean way to center the throbber without exposing character width
BASEVIDWIDTH - currentMenu->x - 4, y,
highlightflags,
va("%c", throbber[throbindex])
);
}
}
void M_DrawMPServerBrowser(void)
{
const char *header[3][2] = {
{"Server Browser", "BG_MPS1"},
{"Core Servers", "BG_MPS1"},
{"Modded Servers", "BG_MPS2"},
};
int mode = M_SecretUnlocked(SECRET_ADDONS, true) ? (mpmenu.room ? 2 : 1) : 0;
patch_t *text1 = W_CachePatchName("MENUBGT1", PU_CACHE);
patch_t *text2 = W_CachePatchName("MENUBGT2", PU_CACHE);
patch_t *raceh = W_CachePatchName("M_SERV1", PU_CACHE);
patch_t *batlh = W_CachePatchName("M_SERV2", PU_CACHE);
patch_t *racehs = W_CachePatchName("M_SERV12", PU_CACHE);
patch_t *batlhs = W_CachePatchName("M_SERV22", PU_CACHE);
fixed_t text1loop = SHORT(text1->height)*FRACUNIT;
fixed_t text2loop = SHORT(text2->width)*FRACUNIT;
const UINT8 startx = 18;
const UINT8 basey = 56;
const INT32 starty = basey - 18*mpmenu.scrolln + mpmenu.slide;
INT32 ypos = 0;
UINT8 i;
// background stuff
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName(header[mode][1], PU_CACHE), NULL);
V_DrawFixedPatch(0, (BASEVIDHEIGHT + 16) * FRACUNIT, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("MENUBG2", PU_CACHE), NULL);
V_DrawFixedPatch(-bgText2Scroll, (BASEVIDHEIGHT-8) * FRACUNIT,
FRACUNIT, V_TRANSLUCENT, text2, NULL);
V_DrawFixedPatch(-bgText2Scroll + text2loop, (BASEVIDHEIGHT-8) * FRACUNIT,
FRACUNIT, V_TRANSLUCENT, text2, NULL);
V_DrawFixedPatch(8 * FRACUNIT, -bgText1Scroll,
FRACUNIT, V_TRANSLUCENT, text1, NULL);
V_DrawFixedPatch(8 * FRACUNIT, -bgText1Scroll + text1loop,
FRACUNIT, V_TRANSLUCENT, text1, NULL);
// bgText<x>Scroll is already handled by the menu background.
// the actual server list.
for (i = 0; i < serverlistcount; i++)
{
boolean racegt = strcmp(serverlist[i].info.gametypename, "Race") == 0;
INT32 transflag = 0;
INT32 basetransflag = 0;
if (serverlist[i].info.numberofplayer >= serverlist[i].info.maxplayer)
basetransflag = 5;
/*if (i < mpmenu.servernum)
// if slide > 0, then we went DOWN in the list and have to fade that server out.
if (mpmenu.slide > 0)
transflag = basetransflag + (18-mpmenu.slide)/2;
else if (mpmenu.slide < 0)
transflag = 10 - basetransflag - (18-mpmenu.slide)/2;*/
transflag = basetransflag;
if (transflag >= 0 && transflag < 10)
{
transflag = transflag << V_ALPHASHIFT; // shift the translucency flag.
if (itemOn == 2 && mpmenu.servernum == i)
V_DrawFixedPatch(startx*FRACUNIT, (starty + ypos)*FRACUNIT, FRACUNIT, transflag, racegt ? racehs : batlhs, NULL);
else
V_DrawFixedPatch(startx*FRACUNIT, (starty + ypos)*FRACUNIT, FRACUNIT, transflag, racegt ? raceh : batlh, NULL);
// Server name:
V_DrawString(startx+11, starty + ypos + 6, transflag, serverlist[i].info.servername);
// Ping:
V_DrawThinString(startx + 191, starty + ypos + 7, transflag, va("%03d", serverlist[i].info.time));
// Playercount
V_DrawThinString(startx + 214, starty + ypos + 7, transflag, va("%02d/%02d", serverlist[i].info.numberofplayer, serverlist[i].info.maxplayer));
// Power Level
V_DrawThinString(startx + 248, starty + ypos, transflag, va("%04d PLv", serverlist[i].info.avgpwrlv));
// game speed if applicable:
if (racegt)
{
UINT8 speed = serverlist[i].info.kartvars & SV_SPEEDMASK;
patch_t *pp = W_CachePatchName(va("M_SDIFF%d", speed), PU_CACHE);
V_DrawFixedPatch((startx + 251)*FRACUNIT, (starty + ypos + 9)*FRACUNIT, FRACUNIT, transflag, pp, NULL);
}
}
ypos += SERVERSPACE;
}
// Draw genericmenu ontop!
V_DrawFill(0, 0, 320, 52, 31);
V_DrawFill(0, 53, 320, 1, 31);
V_DrawFill(0, 55, 320, 1, 31);
V_DrawCenteredGamemodeString(160, 2, 0, 0, header[mode][0]);
// normal menu options
M_DrawGenericMenu();
// And finally, the overlay bar!
M_DrawServerCountAndHorizontalBar();
M_DrawMasterServerReminder();
}
// OPTIONS MENU
// Draws the cogs and also the options background!
void M_DrawOptionsCogs(void)
{
boolean trulystarted = M_GameTrulyStarted();
UINT32 tick = ((optionsmenu.ticker/10) % 3) + 1;
// the background isn't drawn outside of being in the main menu state.
if (gamestate == GS_MENU && trulystarted)
{
patch_t *back = W_CachePatchName(va("OPT_BG%u", tick), PU_CACHE);
INT32 tflag = 0;
UINT8 *c;
UINT8 *c2; // colormap for the one we're changing
if (optionsmenu.fade)
{
c2 = R_GetTranslationColormap(TC_DEFAULT, optionsmenu.lastcolour, GTC_CACHE);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, back, c2);
// prepare fade flag:
tflag = min(V_90TRANS, (optionsmenu.fade)<<V_ALPHASHIFT);
}
c = R_GetTranslationColormap(TC_DEFAULT, optionsmenu.currcolour, GTC_CACHE);
V_DrawFixedPatch(0, 0, FRACUNIT, tflag, back, c);
}
else
{
patch_t *back_pause = W_CachePatchName(va("OPT_BAK%u", tick), PU_CACHE);
V_DrawFixedPatch(0, 0, FRACUNIT, V_MODULATE, back_pause, NULL);
if (!trulystarted)
{
V_DrawFixedPatch(0, 0, FRACUNIT, (V_ADD|V_70TRANS), back_pause, NULL);
}
}
}
void M_DrawOptionsMovingButton(void)
{
patch_t *butt = W_CachePatchName("OPT_BUTT", PU_CACHE);
UINT8 *c = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
fixed_t t = M_DueFrac(optionsmenu.topt_start, M_OPTIONS_OFSTIME);
fixed_t z = Easing_OutSine(M_DueFrac(optionsmenu.offset.start, M_OPTIONS_OFSTIME), optionsmenu.offset.dist * FRACUNIT, 0);
fixed_t tx = Easing_OutQuad(t, optionsmenu.optx * FRACUNIT, optionsmenu.toptx * FRACUNIT) + z;
fixed_t ty = Easing_OutQuad(t, optionsmenu.opty * FRACUNIT, optionsmenu.topty * FRACUNIT) + z;
V_DrawFixedPatch(tx, ty, FRACUNIT, 0, butt, c);
const char *s = OPTIONS_MainDef.menuitems[OPTIONS_MainDef.lastOn].text;
fixed_t w = V_StringScaledWidth(
FRACUNIT,
FRACUNIT,
FRACUNIT,
0,
GM_FONT,
s
);
V_DrawStringScaled(
tx - 3*FRACUNIT - (w/2),
ty - 16*FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
0,
c,
GM_FONT,
s
);
}
void M_DrawOptions(void)
{
UINT8 i;
fixed_t t = Easing_OutSine(M_DueFrac(optionsmenu.offset.start, M_OPTIONS_OFSTIME), optionsmenu.offset.dist * FRACUNIT, 0);
fixed_t x = (140 - (48*itemOn))*FRACUNIT + t;
fixed_t y = 70*FRACUNIT + t;
fixed_t tx = M_EaseWithTransition(Easing_InQuart, 5 * 64 * FRACUNIT);
patch_t *buttback = W_CachePatchName("OPT_BUTT", PU_CACHE);
UINT8 *c = NULL;
for (i=0; i < currentMenu->numitems; i++)
{
fixed_t py = y - (itemOn*48)*FRACUNIT;
fixed_t px = x - tx;
INT32 tflag = 0;
if (i == itemOn)
c = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
else
c = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_BLACK, GTC_CACHE);
if (currentMenu->menuitems[i].status & IT_TRANSTEXT)
tflag = V_TRANSLUCENT;
if (!(menutransition.tics != menutransition.dest && i == itemOn))
{
V_DrawFixedPatch(px, py, FRACUNIT, 0, buttback, c);
const char *s = currentMenu->menuitems[i].text;
fixed_t w = V_StringScaledWidth(
FRACUNIT,
FRACUNIT,
FRACUNIT,
0,
GM_FONT,
s
);
V_DrawStringScaled(
px - 3*FRACUNIT - (w/2),
py - 16*FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
tflag,
(i == itemOn ? c : NULL),
GM_FONT,
s
);
}
y += 48*FRACUNIT;
x += 48*FRACUNIT;
}
M_DrawMenuTooltips();
if (menutransition.tics != menutransition.dest)
M_DrawOptionsMovingButton();
}
static void M_DrawOptionsBoxTerm(INT32 x, INT32 top, INT32 bottom)
{
INT32 px = x - 20;
V_DrawFill(px, top + 4, 2, bottom - top, orangemap[0]);
V_DrawFill(px + 1, top + 5, 2, bottom - top, 31);
V_DrawFill(BASEVIDWIDTH - px - 2, top + 4, 2, bottom - top, orangemap[0]);
V_DrawFill(BASEVIDWIDTH - px, top + 5, 1, bottom - top, 31);
V_DrawFill(px, bottom + 2, BASEVIDWIDTH - (2 * px), 2, orangemap[0]);
V_DrawFill(px, bottom + 3, BASEVIDWIDTH - (2 * px), 2, 31);
}
static void M_DrawLinkArrow(INT32 x, INT32 y, INT32 i)
{
UINT8 ch = currentMenu->menuitems[i].text[0];
V_DrawMenuString(
x + (i == itemOn ? 1 + skullAnimCounter/5 : 0),
y - 1,
// Use color of first character in text label
i == itemOn ? highlightflags : (((max(ch, 0x80) - 0x80) & 15) << V_CHARCOLORSHIFT),
"\x1D"
);
}
void M_DrawGenericOptions(void)
{
INT32 x = currentMenu->x - M_EaseWithTransition(Easing_Linear, 5 * 48), y = currentMenu->y, w, i, cursory = -100;
INT32 expand = -1;
INT32 boxy = 0;
boolean collapse = false;
boolean opening = false;
fixed_t boxt = 0;
M_DrawMenuTooltips();
M_DrawOptionsMovingButton();
for (i = itemOn; i >= 0; --i)
{
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_DYBIGSPACE:
goto box_found;
case IT_HEADERTEXT:
expand = i;
goto box_found;
}
}
box_found:
if (optionsmenu.box.dist != expand)
{
optionsmenu.box.dist = expand;
optionsmenu.box.start = I_GetTime();
}
for (i = 0; i < currentMenu->numitems; i++)
{
boolean term = false;
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_DYBIGSPACE:
collapse = false;
term = (boxy != 0);
break;
case IT_HEADERTEXT:
if (i != expand)
{
collapse = true;
term = (boxy != 0);
}
else
{
if (collapse)
y += 2;
collapse = false;
if (menutransition.tics == menutransition.dest)
{
INT32 px = x - 20;
V_DrawFill(px, y + 4, BASEVIDWIDTH - (2 * px), 2, orangemap[0]);
V_DrawFill(px + 1, y + 5, BASEVIDWIDTH - (2 * px), 2, 31);
}
boxy = y;
boxt = optionsmenu.box.dist == expand ? M_DueFrac(optionsmenu.box.start, 5) : FRACUNIT;
opening = boxt < FRACUNIT;
}
break;
default:
if (collapse)
continue;
}
if (term)
{
if (menutransition.tics == menutransition.dest)
M_DrawOptionsBoxTerm(x, boxy, Easing_Linear(boxt, boxy, y));
y += SMALLLINEHEIGHT;
boxy = 0;
opening = false;
}
if (i == itemOn && !opening)
{
cursory = y;
M_DrawUnderline(x, BASEVIDWIDTH - x, y);
}
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_PATCH:
if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
{
if (currentMenu->menuitems[i].status & IT_CENTER)
{
patch_t *p;
p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE);
V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p);
}
else
{
V_DrawScaledPatch(x, y, 0,
W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE));
}
}
/* FALLTHRU */
case IT_NOTHING:
y += SMALLLINEHEIGHT;
break;
case IT_DYBIGSPACE:
y += SMALLLINEHEIGHT/2;
break;
#if 0
case IT_BIGSLIDER:
M_DrawThermo(x, y, currentMenu->menuitems[i].itemaction.cvar);
y += LINEHEIGHT;
break;
#endif
case IT_STRING:
case IT_LINKTEXT: {
boolean textBox = (currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR &&
(currentMenu->menuitems[i].status & IT_CVARTYPE) == IT_CV_STRING;
if (textBox)
{
if (opening)
y += LINEHEIGHT;
else
V_DrawFill(x+5, y+5, MAXSTRINGLENGTH*7+6, 9+6, 159);
}
if (opening)
{
y += STRINGHEIGHT;
break;
}
INT32 px = x + ((currentMenu->menuitems[i].status & IT_TYPE) == IT_SUBMENU
|| (currentMenu->menuitems[i].status & IT_DISPLAY) == IT_LINKTEXT ? 8 : 0);
if (i == itemOn)
cursory = y;
if (i == itemOn)
V_DrawMenuString(px + 1, y, highlightflags, currentMenu->menuitems[i].text);
else
V_DrawMenuString(px, y, textBox ? V_GRAYMAP : 0, currentMenu->menuitems[i].text);
if ((currentMenu->menuitems[i].status & IT_DISPLAY) == IT_LINKTEXT)
M_DrawLinkArrow(x, y, i);
// Cvar specific handling
switch (currentMenu->menuitems[i].status & IT_TYPE)
{
case IT_SUBMENU: {
if ((currentMenu->menuitems[i].status & IT_DISPLAY) != IT_LINKTEXT)
M_DrawLinkArrow(x, y, i);
break;
}
case IT_CVAR: {
consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar;
switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
{
case IT_CV_SLIDER:
M_DrawSlider(x, y, cv, (i == itemOn));
case IT_CV_NOPRINT: // color use this
case IT_CV_INVISSLIDER: // monitor toggles use this
break;
case IT_CV_STRING:
{
INT32 xoffs = 6;
if (itemOn == i)
{
xoffs = 8;
V_DrawMenuString(x + (skullAnimCounter/5) + 7, y + 9, highlightflags, "\x1D");
}
M_DrawCaretString(x + xoffs + 8, y + 9, cv->string, false);
y += LINEHEIGHT;
}
break;
default: {
boolean isDefault = CV_IsSetToDefault(cv);
w = V_MenuStringWidth(cv->string, 0);
V_DrawMenuString(BASEVIDWIDTH - x - w, y,
(!isDefault ? warningflags : highlightflags), cv->string);
if (i == itemOn)
{
V_DrawMenuString(BASEVIDWIDTH - x - 10 - w - (skullAnimCounter/5), y - 1,
highlightflags, "\x1C"); // left arrow
V_DrawMenuString(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y - 1,
highlightflags, "\x1D"); // right arrow
}
if (!isDefault)
{
V_DrawMenuString(BASEVIDWIDTH - x + (i == itemOn ? 13 : 5), y - 2, warningflags, ".");
}
break;
}
}
break;
}
}
y += STRINGHEIGHT;
break;
}
case IT_STRING2:
V_DrawMenuString(x, y, 0, currentMenu->menuitems[i].text);
/* FALLTHRU */
case IT_DYLITLSPACE:
case IT_SPACE:
y += (currentMenu->menuitems[i].mvar1 ? currentMenu->menuitems[i].mvar1 : SMALLLINEHEIGHT);
break;
case IT_GRAYPATCH:
if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
V_DrawMappedPatch(x, y, 0,
W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap);
y += (currentMenu->menuitems[i].mvar1 ? currentMenu->menuitems[i].mvar1 : SMALLLINEHEIGHT);
break;
case IT_TRANSTEXT:
if (currentMenu->menuitems[i].mvar1)
y = currentMenu->y+currentMenu->menuitems[i].mvar1;
/* FALLTHRU */
case IT_TRANSTEXT2:
V_DrawMenuString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);
y += SMALLLINEHEIGHT;
break;
case IT_QUESTIONMARKS:
if (currentMenu->menuitems[i].mvar1)
y = currentMenu->y+currentMenu->menuitems[i].mvar1;
V_DrawMenuString(x + 8, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
y += SMALLLINEHEIGHT;
break;
case IT_HEADERTEXT: // draws 16 pixels to the left, in yellow text
if (currentMenu->menuitems[i].mvar1)
y = currentMenu->y+currentMenu->menuitems[i].mvar1;
V_DrawMenuString(x - (collapse ? 0 : 16), y, M_ALTCOLOR, currentMenu->menuitems[i].text);
y += SMALLLINEHEIGHT + 1;
break;
}
}
if (boxy && menutransition.tics == menutransition.dest)
M_DrawOptionsBoxTerm(x, boxy, Easing_Linear(boxt, boxy, y));
// DRAW THE SKULL CURSOR
if (((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_PATCH)
|| ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING))
{
V_DrawScaledPatch(x + SKULLXOFF, cursory - 5, 0,
W_CachePatchName("M_CURSOR", PU_CACHE));
}
else
{
M_DrawCursorHand(x, cursory);
}
}
// *Heavily* simplified version of the generic options menu, cattered only towards erasing profiles.
void M_DrawProfileErase(void)
{
INT32 x = currentMenu->x - M_EaseWithTransition(Easing_Linear, 5 * 48), y = currentMenu->y-SMALLLINEHEIGHT, i, cursory = 0;
UINT8 np = PR_GetNumProfiles();
M_DrawMenuTooltips();
M_DrawOptionsMovingButton();
for (i = 1; i < np; i++)
{
profile_t *pr = PR_GetProfile(i);
if (i == optionsmenu.eraseprofilen)
{
cursory = y;
M_DrawCursorHand(x, cursory);
}
V_DrawMenuString(x, y,
(i == optionsmenu.eraseprofilen ? highlightflags : 0),
va("%sPRF%03d - %s (%s)",
(cv_currprofile.value == i) ? "[In use] " : "",
i, pr->profilename, pr->playername));
y += SMALLLINEHEIGHT;
}
}
// Draws profile selection
void M_DrawProfileSelect(void)
{
INT32 i;
const INT32 maxp = PR_GetNumProfiles();
INT32 x = 160 - optionsmenu.profilen*(128 + 128/8) + Easing_OutSine(M_DueFrac(optionsmenu.offset.start, M_OPTIONS_OFSTIME), optionsmenu.offset.dist, 0);
INT32 y = 35 + M_EaseWithTransition(Easing_Linear, 5 * 32);
M_DrawMenuTooltips();
// This shouldn't be drawn when a profile is selected as optx/opty are used to move the card.
if (optionsmenu.profile == NULL && menutransition.tics)
M_DrawOptionsMovingButton();
for (i=0; i < MAXPROFILES+1; i++) // +1 because the default profile does not count
{
profile_t *p = PR_GetProfile(i);
// don't draw the card in this specific scenario
if (!(optionsmenu.profile != NULL && optionsmenu.profilen == i))
M_DrawProfileCard(x, y, i > maxp, p);
x += 128 + 128/8;
}
// needs to be drawn since it happens on the transition
if (optionsmenu.profile != NULL)
{
fixed_t t = M_DueFrac(optionsmenu.topt_start, M_OPTIONS_OFSTIME);
M_DrawProfileCard(
Easing_OutQuad(t, optionsmenu.optx, optionsmenu.toptx),
Easing_OutQuad(t, optionsmenu.opty, optionsmenu.topty),
false,
optionsmenu.profile
);
}
}
void M_DrawEditProfileTooltips(void)
{
// Tooltip
// The text is slightly shifted hence why we don't just use M_DrawMenuTooltips()
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL);
if (currentMenu->menuitems[itemOn].tooltip != NULL)
{
V_DrawCenteredThinString(224, 12, 0, currentMenu->menuitems[itemOn].tooltip);
}
}
// Profile edition menu
void M_DrawEditProfile(void)
{
INT32 y = 34;
INT32 x = (145 + M_EaseWithTransition(Easing_InSine, 5 * 48));
INT32 i;
M_DrawEditProfileTooltips();
// Draw the menu options...
for (i = 0; i < currentMenu->numitems; i++)
{
UINT8 *colormap = NULL;
INT32 tflag = (currentMenu->menuitems[i].status & IT_TRANSTEXT) ? V_TRANSLUCENT : 0;
INT32 cx = x;
y = currentMenu->menuitems[i].mvar2;
// Background -- 169 is the plague colourization
V_DrawFill(0, y, 400 - M_EaseWithTransition(Easing_InQuad, 5 * 128), 10, itemOn == i ? 169 : 30);
if (i == itemOn)
{
colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
cx += Easing_OutSine(M_DueFrac(optionsmenu.offset.start, 2), 0, 5);
V_DrawMenuString(cx - 10 - (skullAnimCounter/5), y+1, highlightflags, "\x1C"); // left arrow
}
// Text
//V_DrawGamemodeString(cx, y - 6, tflag, colormap, currentMenu->menuitems[i].text);
V_DrawStringScaled(
cx * FRACUNIT,
(y - 3) * FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
tflag,
colormap,
KART_FONT,
currentMenu->menuitems[i].text
);
//y += 32 + 2;
}
// Finally, draw the card ontop
if (optionsmenu.profile != NULL)
{
fixed_t t = M_DueFrac(optionsmenu.topt_start, M_OPTIONS_OFSTIME);
M_DrawProfileCard(
Easing_OutQuad(t, optionsmenu.optx, optionsmenu.toptx),
Easing_OutQuad(t, optionsmenu.opty, optionsmenu.topty),
false,
optionsmenu.profile
);
}
}
// Controller offsets to center on each button.
INT16 controlleroffsets[][2] = {
{0, 0}, // gc_none
{69, 142}, // gc_up
{69, 182}, // gc_down
{49, 162}, // gc_left
{89, 162}, // gc_right
{208, 200}, // gc_a
{237, 181}, // gc_b
{267, 166}, // gc_c
{191, 164}, // gc_x
{215, 149}, // gc_y
{242, 137}, // gc_z
{55, 102}, // gc_l
{253, 102}, // gc_r
{149, 187}, // gc_start
};
static void M_DrawBindBen(INT32 x, INT32 y, INT32 scroll_remaining)
{
// optionsmenu.bindben_swallow
const int pose_time = 30;
const int swallow_time = pose_time + 14;
// Lid closed
int state = 'A';
int frame = 0;
if (optionsmenu.bindben_swallow > 100)
{
// Quick swallow (C button)
state = 'C';
int t = 106 - optionsmenu.bindben_swallow;
if (t < 3)
frame = 0;
else
frame = t - 3;
}
else if (scroll_remaining <= 0)
{
// Chewing (text done scrolling)
state = 'B';
frame = I_GetTime() / 2 % 4;
// When state changes from 'lid open' to 'chewing',
// play chomp sound.
if (!optionsmenu.bindben_swallow)
S_StartSound(NULL, sfx_monch);
// Ready to swallow when button is released.
optionsmenu.bindben_swallow = swallow_time + 1;
}
else if (optionsmenu.bindben)
{
// Lid open (text scrolling)
frame = 1;
}
else if (optionsmenu.bindben_swallow)
{
if (optionsmenu.bindben_swallow > pose_time)
{
// Swallow
state = 'C';
int t = swallow_time - optionsmenu.bindben_swallow;
if (t < 8)
frame = 0;
else
frame = 1 + (t - 8) / 2 % 3;
}
else
{
// Pose
state = 'D';
int t = pose_time - optionsmenu.bindben_swallow;
if (t < 10)
frame = 0;
else
frame = 1 + (t - 10) / 4 % 5;
}
}
V_DrawMappedPatch(x-30, y, 0, W_CachePatchName(va("PR_BIN%c%c", state, '1' + frame), PU_CACHE), aquamap);
}
static void M_DrawBindMediumString(INT32 y, INT32 flags, const char *string)
{
fixed_t w = V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, flags, MED_FONT, string);
fixed_t x = BASEVIDWIDTH/2 * FRACUNIT - w/2;
V_DrawStringScaled(
x,
y * FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
flags,
NULL,
MED_FONT,
string
);
}
static INT32 M_DrawProfileLegend(INT32 x, INT32 y, const char *legend, const char *mediocre_key)
{
INT32 w = V_ThinStringWidth(legend, 0);
V_DrawThinString(x - w, y, 0, legend);
x -= w + 2;
if (mediocre_key)
M_DrawMediocreKeyboardKey(mediocre_key, &x, y, false, true);
return x;
}
// the control stuff.
// Dear god.
void M_DrawProfileControls(void)
{
const UINT8 spacing = 34;
INT32 y = 16 - (optionsmenu.controlscroll*spacing);
INT32 x = 8;
INT32 i, j, k;
const UINT8 pid = 0;
patch_t *hint = W_CachePatchName("MENUHINT", PU_CACHE);
INT32 hintofs = 3;
K_DrawInputDisplay(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 - optionsmenu.conty, 0, '_', pid, true, false);
if (optionsmenu.trycontroller)
{
optionsmenu.tcontx = BASEVIDWIDTH*2/3 - 10;
optionsmenu.tconty = BASEVIDHEIGHT/2 +70;
V_DrawCenteredLSTitleLowString(160, 164, 0, "TRY BUTTONS");
const char *msg = va("Press nothing for %d sec to go back", (optionsmenu.trycontroller + (TICRATE-1)) / TICRATE);
fixed_t w = V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, highlightflags, MED_FONT, msg);
V_DrawStringScaled(
160*FRACUNIT - w/2,
186*FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
highlightflags,
NULL,
MED_FONT,
msg
);
return; // Don't draw the rest if we're trying the controller.
}
V_DrawFill(0, 0, 138, 200, 31); // Black border
V_SetClipRect(
0,
0,
BASEVIDWIDTH * FRACUNIT,
(BASEVIDHEIGHT - SHORT(hint->height) + hintofs) * FRACUNIT,
0
);
// Draw the menu options...
for (i = 0; i < currentMenu->numitems; i++)
{
char buf[256];
char buf2[256];
INT32 keys[MAXINPUTMAPPING];
// cursor
if (i == itemOn)
{
for (j=0; j < 24; j++)
V_DrawFill(0, (y)+j, 128+j, 1, 73);
}
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_HEADERTEXT:
V_DrawFill(0, y+18, 124, 1, 0); // underline
V_DrawMenuString(x, y+8, 0, currentMenu->menuitems[i].text);
y += spacing;
break;
case IT_STRING:
V_DrawMenuString(x, y+2, (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text);
y += spacing;
break;
case IT_STRING2:
{
boolean drawnpatch = false;
if (currentMenu->menuitems[i].patch)
{
V_DrawScaledPatch(x-4, y+1, 0, W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE));
V_DrawMenuString(x+12, y+2, (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text);
drawnpatch = true;
}
else
V_DrawMenuString(x, y+2, (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text);
if (currentMenu->menuitems[i].status & IT_CVAR) // not the proper way to check but this menu only has normal onoff cvars.
{
INT32 w;
consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar;
w = V_MenuStringWidth(cv->string, 0);
V_DrawMenuString(x + 12, y + 13, ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string);
if (i == itemOn)
{
V_DrawMenuString(x - (skullAnimCounter/5), y+12, highlightflags, "\x1C"); // left arrow
V_DrawMenuString(x + 12 + w + 2 + (skullAnimCounter/5) , y+13, highlightflags, "\x1D"); // right arrow
}
}
else if (currentMenu->menuitems[i].status & IT_CONTROL)
{
UINT32 vflags = V_FORCEUPPERCASE;
INT32 gc = currentMenu->menuitems[i].mvar1;
UINT8 available = 0, set = 0;
if (i != itemOn)
vflags |= V_GRAYMAP;
// Get userbound controls...
for (k = 0; k < MAXINPUTMAPPING; k++)
{
int device;
keys[k] = optionsmenu.tempcontrols[gc][k];
if (keys[k] == KEY_NULL)
continue;
set++;
device = G_GetDeviceForPlayer(0);
if (device == -1)
{
device = 0;
}
if (!G_KeyIsAvailable(keys[k], device))
continue;
available++;
};
buf[0] = '\0';
buf2[0] = '\0';
// Cool as is this, this doesn't actually help show accurate info because of how some players would set inputs with keyboard and controller at once in a volatile way...
// @TODO: Address that mess, somehow?
// Can't reach any of them?
/*if (available == 0)
{
if (((3*optionsmenu.ticker)/(2*TICRATE)) & 1) // 1.5 seconds
{
vflags |= V_ORANGEMAP;
#ifdef SHOWCONTROLDEFAULT
if (G_KeyBindIsNecessary(gc))
{
// Get the defaults for essential keys.
// Went through all the trouble of making this look cool,
// then realised defaulting should only apply to menus.
// Too much opportunity for confusion if kept.
for (k = 0; k < MAXINPUTMAPPING; k++)
{
keys[k] = gamecontroldefault[gc][k];
if (keys[k] == KEY_NULL)
continue;
available++;
}
set = available;
}
else if (set)
#else
if (!set)
{
if (!G_KeyBindIsNecessary(gc))
vflags = V_REDMAP;
}
else
#endif
{
strcpy(buf, "CURRENTLY UNAVAILABLE");
}
}
else
{
vflags |= V_REDMAP;
}
}*/
char *p = buf;
if (buf[0])
;
else if (!set)
{
vflags &= ~V_CHARCOLORMASK;
vflags |= V_REDMAP;
strcpy(buf, "NOT BOUND");
}
else
{
for (k = 0; k < MAXINPUTMAPPING; k++)
{
if (keys[k] == KEY_NULL)
continue;
if (k > 0)
strcat(p," / ");
if (k == 2) // hacky...
p = buf2;
strcat(p, G_KeynumToString (keys[k]));
}
}
INT32 bindx = x;
INT32 benx = 142;
INT32 beny = y - 8;
if (i == itemOn)
{
// Extend yellow wedge down behind
// extra line.
if (buf2[0])
{
for (j=24; j < 34; j++)
V_DrawFill(0, (y)+j, 128+j, 1, 73);
benx += 10;
beny += 10;
}
// Scroll text into Bind Ben.
bindx += optionsmenu.bindben * 3;
if (buf2[0])
{
// Bind Ben: suck characters off
// the end of the first line onto
// the beginning of the second
// line.
UINT16 n = strlen(buf);
UINT16 t = min(optionsmenu.bindben, n);
memmove(&buf2[t], buf2, t + 1);
memcpy(buf2, &buf[n - t], t);
buf[n - t] = '\0';
}
}
{
cliprect_t clip;
V_SaveClipRect(&clip); // preserve cliprect for tooltip
// Clip text as it scrolls into Bind Ben.
V_SetClipRect(0, 0, (benx-14)*FRACUNIT, 200*FRACUNIT, 0);
if (i != itemOn || !optionsmenu.bindben_swallow)
{
// don't shift the text if we didn't draw a patch.
V_DrawThinString(bindx + (drawnpatch ? 13 : 1), y + 12, vflags, buf);
V_DrawThinString(bindx + (drawnpatch ? 13 : 1), y + 22, vflags, buf2);
}
V_RestoreClipRect(&clip);
}
if (i == itemOn)
M_DrawBindBen(benx, beny, (benx-14) - bindx);
// controller dest coords:
if (itemOn == i && gc > 0 && gc <= gc_start)
{
optionsmenu.tcontx = controlleroffsets[gc][0];
optionsmenu.tconty = controlleroffsets[gc][1];
}
}
y += spacing;
break;
}
}
}
V_ClearClipRect();
// Tooltip
// Draw it at the bottom of the screen
{
static UINT8 blue[256];
blue[31] = 253;
V_DrawMappedPatch(0, BASEVIDHEIGHT + hintofs, V_VFLIP, hint, blue);
}
if (currentMenu->menuitems[itemOn].tooltip != NULL)
{
INT32 ypos = BASEVIDHEIGHT + hintofs - 9 - 12;
V_DrawThinString(12, ypos, V_YELLOWMAP, currentMenu->menuitems[itemOn].tooltip);
boolean standardbuttons = gamedata->gonerlevel > GDGONER_PROFILE;
INT32 xpos = BASEVIDWIDTH - 12;
xpos = standardbuttons ?
M_DrawProfileLegend(xpos, ypos, "\xB2 / \xBC Clear", NULL) :
M_DrawProfileLegend(xpos, ypos, "Clear", "BKSP");
}
// Overlay for control binding
if (optionsmenu.bindtimer)
{
INT16 reversetimer = TICRATE*5 - optionsmenu.bindtimer;
INT32 fade = reversetimer;
INT32 ypos;
if (fade > 9)
fade = 9;
ypos = (BASEVIDHEIGHT/2) - 20 +16*(9 - fade);
V_DrawFadeScreen(31, fade);
M_DrawTextBox((BASEVIDWIDTH/2) - (120), ypos - 12, 30, 8);
V_DrawCenteredMenuString(BASEVIDWIDTH/2, ypos, V_GRAYMAP, "Hold and release inputs for");
V_DrawCenteredMenuString(BASEVIDWIDTH/2, ypos + 10, V_GRAYMAP, va("\"%s\"", currentMenu->menuitems[itemOn].text));
if (optionsmenu.bindtimer > 0)
{
M_DrawBindMediumString(
ypos + 50,
highlightflags,
va("(WAIT %d SEC TO SKIP)", (optionsmenu.bindtimer + (TICRATE-1)) / TICRATE)
);
}
else
{
for (i = 0; i < MAXINPUTMAPPING && optionsmenu.bindinputs[i]; ++i)
{
M_DrawBindMediumString(
ypos + (2 + i)*10,
highlightflags | V_FORCEUPPERCASE,
G_KeynumToString(optionsmenu.bindinputs[i])
);
}
}
}
}
// Draw the video modes list, a-la-Quake
void M_DrawVideoModes(void)
{
INT32 i, j, row, col;
INT32 t = M_EaseWithTransition(Easing_Linear, 5 * 64);
M_DrawMenuTooltips();
M_DrawOptionsMovingButton();
V_DrawCenteredMenuString(BASEVIDWIDTH/2 + t, currentMenu->y,
highlightflags, "Choose mode, reselect to change default");
row = 41 + t;
col = currentMenu->y + 14;
for (i = 0; i < optionsmenu.vidm_nummodes; i++)
{
INT32 colorflag = 0;
boolean isdefault = !strcmp(optionsmenu.modedescs[i].desc, va("%dx%d", cv_scr_width.value, cv_scr_height.value));
if (i == optionsmenu.vidm_selected)
colorflag = highlightflags;
else if (isdefault)
colorflag = V_ORANGEMAP;
else if (optionsmenu.modedescs[i].goodratio)
colorflag = recommendedflags; // Show multiples of 320x200 as green.
if (isdefault)
V_DrawScaledPatch(row + 2 + V_MenuStringWidth(optionsmenu.modedescs[i].desc, colorflag), col - 2, 0, W_CachePatchName("RHFAV", PU_CACHE));
V_DrawMenuString(row, col, colorflag, optionsmenu.modedescs[i].desc);
col += 9;
if ((i % optionsmenu.vidm_column_size) == (optionsmenu.vidm_column_size-1))
{
row += 7*13;
col = currentMenu->y + 14;
}
}
if (optionsmenu.vidm_testingmode > 0)
{
INT32 testtime = (optionsmenu.vidm_testingmode/TICRATE) + 1;
M_CentreText(t, currentMenu->y + 75,
va("Previewing mode %c%dx%d",
(SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80,
vid.width, vid.height));
M_CentreText(t, currentMenu->y + 75+9,
"Press ENTER again to keep this mode");
M_CentreText(t, currentMenu->y + 75+18,
va("Wait %d second%s", testtime, (testtime > 1) ? "s" : ""));
M_CentreText(t, currentMenu->y + 75+27,
"or press ESC to return");
}
else
{
M_CentreText(t, currentMenu->y + 75,
va("Current mode is %c%dx%d",
(SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80,
vid.width, vid.height));
M_CentreText(t, currentMenu->y + 75+9,
va("\x87" "Default mode is %dx%d",
cv_scr_width.value, cv_scr_height.value));
if (vid.width > 1280 || vid.height > 800)
V_DrawCenteredMenuString(BASEVIDWIDTH/2 + t, currentMenu->y + 75+24,
(I_GetTime() % 20 >= 10) ? V_REDMAP : V_YELLOWMAP, va("High resolutions will impact performance. Careful!"));
else
V_DrawCenteredMenuString(BASEVIDWIDTH/2 + t, currentMenu->y + 75+24,
recommendedflags, "Modes marked in GREEN are recommended.");
/*
V_DrawCenteredString(BASEVIDWIDTH/2 + t, currentMenu->y + 75+16,
highlightflags, "High resolutions stress your PC more, but will");
V_DrawCenteredString(BASEVIDWIDTH/2 + t, currentMenu->y + 75+24,
highlightflags, "look sharper. Balance visual quality and FPS!");
*/
}
// Draw the cursor for the VidMode menu
i = 41 - 10 + ((optionsmenu.vidm_selected / optionsmenu.vidm_column_size)*7*13) + t;
j = currentMenu->y + 14 + ((optionsmenu.vidm_selected % optionsmenu.vidm_column_size)*9);
M_DrawCursorHand(i + 14, j);
}
// Gameplay Item Tggles:
tic_t shitsfree = 0;
static void DrawMappedString(INT32 x, INT32 y, INT32 option, int font, const char *text, const UINT8 *colormap)
{
V_DrawStringScaled(
x * FRACUNIT,
y * FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
option,
colormap,
font,
text
);
}
void M_DrawItemToggles(void)
{
static UINT8 black[256];
memset(black, 16, 256);
const INT32 edges = 8;
const INT32 height = 4;
const INT32 spacing = 35;
const INT32 column = itemOn/height;
//const INT32 row = itemOn%height;
INT32 leftdraw, rightdraw, totaldraw;
INT32 x = currentMenu->x + M_EaseWithTransition(Easing_Linear, 5 * 64), y = currentMenu->y;
INT32 onx = 0, ony = 0;
consvar_t *cv;
INT32 i, drawnum;
patch_t *pat;
M_DrawMenuTooltips();
M_DrawOptionsMovingButton();
// Find the available space around column
leftdraw = rightdraw = column;
totaldraw = 0;
for (i = 0; (totaldraw < edges*2 && i < edges*4); i++)
{
if (rightdraw+1 < (currentMenu->numitems/height)+1)
{
rightdraw++;
totaldraw++;
}
if (leftdraw-1 >= 0)
{
leftdraw--;
totaldraw++;
}
}
patch_t *isbg = W_CachePatchName("K_ISBG", PU_CACHE);
patch_t *isbgd = W_CachePatchName("K_ISBGD", PU_CACHE);
patch_t *ismul = W_CachePatchName("K_ISMUL", PU_CACHE);
patch_t *isstrk = W_CachePatchName("K_ISSTRK", PU_CACHE);
for (i = leftdraw; i <= rightdraw; i++)
{
INT32 j;
for (j = 0; j < height; j++)
{
const INT32 thisitem = (i*height)+j;
if (thisitem >= currentMenu->numitems)
break;
if (thisitem == itemOn)
{
onx = x;
ony = y;
y += spacing;
continue;
}
if (currentMenu->menuitems[thisitem].mvar1 == 0)
{
V_DrawScaledPatch(x, y, 0, isbg);
V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISTOGL", PU_CACHE));
y += spacing;
continue;
}
if (currentMenu->menuitems[thisitem].mvar1 == 255)
{
V_DrawScaledPatch(x, y, 0, isbgd);
y += spacing;
continue;
}
cv = &cv_items[currentMenu->menuitems[thisitem].mvar1-1];
drawnum = K_ItemResultToAmount(currentMenu->menuitems[thisitem].mvar1);
V_DrawScaledPatch(x, y, 0, cv->value ? isbg : isbgd);
if (drawnum > 1)
V_DrawScaledPatch(x, y, 0, ismul);
pat = W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].mvar1, true), PU_CACHE);
V_DrawScaledPatch(x, y, 0, pat);
if (!cv->value)
V_DrawMappedPatch(x, y, V_MODULATE, pat, black);
if (drawnum > 1)
{
V_DrawString(x+24, y+31, 0, va("x%d", drawnum));
if (!cv->value)
DrawMappedString(x+24, y+31, V_MODULATE, HU_FONT, va("x%d", drawnum), black);
}
if (!cv->value)
V_DrawScaledPatch(x, y, 0, isstrk);
y += spacing;
}
x += spacing;
y = currentMenu->y;
}
{
if (currentMenu->menuitems[itemOn].mvar1 == 0)
{
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE));
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITTOGL", PU_CACHE));
}
else if (currentMenu->menuitems[itemOn].mvar1 == 255)
{
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE));
if (shitsfree)
{
INT32 trans = V_TRANSLUCENT;
if (shitsfree-1 > TICRATE-5)
trans = ((10-TICRATE)+shitsfree-1)<<V_ALPHASHIFT;
else if (shitsfree < 5)
trans = (10-shitsfree)<<V_ALPHASHIFT;
V_DrawScaledPatch(onx-1, ony-2, trans, W_CachePatchName("K_ITFREE", PU_CACHE));
}
}
else
{
cv = &cv_items[currentMenu->menuitems[itemOn].mvar1-1];
drawnum = K_ItemResultToAmount(currentMenu->menuitems[itemOn].mvar1);
if (cv->value)
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE));
else
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE));
if (drawnum > 1)
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITMUL", PU_CACHE));
pat = W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].mvar1, false), PU_CACHE);
V_DrawScaledPatch(onx-1, ony-2, 0, pat);
if (!cv->value)
V_DrawMappedPatch(onx-1, ony-2, V_MODULATE, pat, black);
if (drawnum > 1)
{
V_DrawScaledPatch(onx+27, ony+39, 0, W_CachePatchName("K_ITX", PU_CACHE));
V_DrawTimerString(onx+37, ony+34, 0, va("%d", drawnum));
if (!cv->value)
{
V_DrawMappedPatch(onx+27, ony+39, V_MODULATE, W_CachePatchName("K_ITX", PU_CACHE), black);
DrawMappedString(onx+37, ony+34, V_MODULATE, TIMER_FONT, va("%d", drawnum), black);
}
}
if (!cv->value)
{
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITSTRK", PU_CACHE));
}
}
}
}
// EXTRAS:
// Copypasted from options but separate either way in case we want it to look more unique later down the line.
void M_DrawExtrasMovingButton(void)
{
patch_t *butt = W_CachePatchName("OPT_BUTT", PU_CACHE);
UINT8 *c = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
V_DrawFixedPatch((extrasmenu.extx)*FRACUNIT, (extrasmenu.exty)*FRACUNIT, FRACUNIT, 0, butt, c);
V_DrawCenteredGamemodeString((extrasmenu.extx)-3, (extrasmenu.exty) - 16, 0, c, EXTRAS_MainDef.menuitems[EXTRAS_MainDef.lastOn].text);
}
void M_DrawExtras(void)
{
UINT8 i;
INT32 t = Easing_OutSine(M_DueFrac(extrasmenu.offset.start, M_EXTRAS_OFSTIME), extrasmenu.offset.dist, 0);
INT32 x = 140 - (48*itemOn) + t;
INT32 y = 70 + t;
patch_t *buttback = W_CachePatchName("OPT_BUTT", PU_CACHE);
UINT8 *c = NULL;
for (i=0; i < currentMenu->numitems; i++)
{
INT32 py = y - (itemOn*48);
INT32 px = x - menutransition.tics*64;
INT32 tflag = 0;
if (i == itemOn)
c = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
else
c = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_BLACK, GTC_CACHE);
if (currentMenu->menuitems[i].status & IT_TRANSTEXT)
tflag = V_TRANSLUCENT;
if (!(menutransition.tics && i == itemOn))
{
V_DrawFixedPatch(px*FRACUNIT, py*FRACUNIT, FRACUNIT, 0, buttback, c);
V_DrawCenteredGamemodeString(px-3, py - 16, tflag, (i == itemOn ? c : NULL), currentMenu->menuitems[i].text);
}
y += 48;
x += 48;
}
M_DrawMenuTooltips();
if (menutransition.tics)
M_DrawExtrasMovingButton();
}
//
// INGAME / PAUSE MENUS
//
static char *M_GetGameplayMode(void)
{
if (grandprixinfo.gp == true)
{
if (grandprixinfo.masterbots)
return va("Master");
if (grandprixinfo.gamespeed == KARTSPEED_HARD)
return va("Hard");
if (grandprixinfo.gamespeed == KARTSPEED_NORMAL)
return va("Normal");
return va("Easy");
}
if (cv_4thgear.value)
return va("4th Gear!");
return va("Gear %d\n", gamespeed+1);
}
// PAUSE MAIN MENU
void M_DrawPause(void)
{
SINT8 i;
SINT8 itemsdrawn = 0;
SINT8 countdown = 0;
INT16 ypos = -50; // Draw 3 items from selected item (y=100 - 3 items spaced by 50 px each... you get the idea.)
INT16 dypos;
fixed_t mt = M_DueFrac(pausemenu.openoffset.start, 6);
if (pausemenu.openoffset.dist)
mt = FRACUNIT - mt;
INT16 offset = menutransition.tics ? floor(pow(2, (double)menutransition.tics)) : Easing_OutQuad(mt, 256, 0);
INT16 arrxpos = 150 + 2*offset; // To draw the background arrow.
INT16 j = 0;
patch_t *vertbg = W_CachePatchName("M_STRIPV", PU_CACHE);
patch_t *arrstart = W_CachePatchName("M_PTIP", PU_CACHE);
patch_t *arrfill = W_CachePatchName("M_PFILL", PU_CACHE);
fixed_t t = M_DueFrac(pausemenu.offset.start, 3);
UINT8 splitspectatestate = 0;
if (G_GametypeHasSpectators() && pausemenu.splitscreenfocusid <= splitscreen)
{
// Identify relevant spectator state of pausemenu.splitscreenfocusid.
// See also M_HandleSpectatorToggle.
const UINT8 splitspecid =
g_localplayers[pausemenu.splitscreenfocusid];
if (players[splitspecid].spectator)
{
splitspectatestate =
(players[splitspecid].pflags & PF_WANTSTOJOIN)
? UINT8_MAX
: 1;
}
}
//V_DrawFadeScreen(0xFF00, 16);
{
INT32 x = Easing_OutQuad(mt, -BASEVIDWIDTH, 0);
INT32 y = 56;
if (g_realsongcredit && !S_MusicDisabled())
{
V_DrawThinString(x + 2, y, 0, g_realsongcredit);
}
if (gamestate == GS_LEVEL)
{
const char *name = bossinfo.valid && bossinfo.enemyname ?
bossinfo.enemyname : mapheaderinfo[gamemap-1]->menuttl;
char *buf = NULL;
if (!name[0])
{
buf = G_BuildMapTitle(gamemap);
name = buf;
}
INT32 width = V_StringScaledWidth(
FRACUNIT,
FRACUNIT,
FRACUNIT,
0,
MED_FONT,
name
) / FRACUNIT;
y += 11;
V_DrawFill(x + 1, y + 8, width + 20, 3, 31);
V_DrawStringScaled(
(x + 19) * FRACUNIT,
y * FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
V_AQUAMAP,
NULL,
MED_FONT,
name
);
K_DrawMapThumbnail(
(x + 1) * FRACUNIT,
(y - 1) * FRACUNIT,
16 * FRACUNIT,
0,
gamemap - 1,
NULL
);
Z_Free(buf);
}
}
// "PAUSED"
if (!paused && !demo.playback && !modeattacking && !netgame) // as close to possible as P_AutoPause, but not dependent on menuactive
{
M_DrawPausedText(-offset*FRACUNIT);
}
// Vertical Strip:
V_DrawFixedPatch((230 + offset)<<FRACBITS, 0, FRACUNIT, V_ADD, vertbg, NULL);
// Okay that's cool but which icon do we draw first? let's roll back from itemOn!
// At most we'll draw 7 items, 1 in the center, 3 above, 3 below.
// Which means... let's count down from itemOn
for (i = itemOn; countdown < 3; countdown++)
{
i--;
if (i < 0)
i = currentMenu->numitems-1;
while (currentMenu->menuitems[i].status == IT_DISABLED)
{
i--;
if (i < 0)
i = currentMenu->numitems-1;
}
}
// Aaaaand now we can start drawing!
// Reminder that we set the patches of the options to the description since we're not using that. I'm smart, I know...
// Draw the background arrow
V_DrawFixedPatch(arrxpos<<FRACBITS, 100<<FRACBITS, FRACUNIT, 0, arrstart, NULL);
while ((arrxpos - arrfill->width) < BASEVIDWIDTH)
{
V_DrawFixedPatch(arrxpos<<FRACBITS, 100<<FRACBITS, FRACUNIT, 0, arrfill, NULL);
arrxpos += arrfill->width;
}
while (itemsdrawn < 7)
{
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
{
case IT_STRING:
{
patch_t *pp;
UINT8 *colormap = NULL;
if (i == itemOn && (i == mpause_restartmap || i == mpause_tryagain))
{
pp = W_CachePatchName(
va("M_ICOR2%c", ('A'+(pausemenu.ticker & 1))),
PU_CACHE);
}
else if (i == mpause_spectatetoggle)
{
pp = W_CachePatchName(
((splitspectatestate == 1)
? "M_ICOENT"
: "M_ICOSPC"
), PU_CACHE
);
if (i == itemOn)
colormap = yellowmap;
}
else
{
pp = W_CachePatchName(currentMenu->menuitems[i].tooltip, PU_CACHE);
if (i == itemOn)
colormap = yellowmap;
}
// 294 - 261 = 33
// We need to move 33 px in 50 tics which means we move 33/50 = 0.66 px every tic = 2/3 of the offset.
// trust me i graduated highschool!!!!
// Multiply by -1 or 1 depending on whether we're below or above 100 px.
// This double ternary is awful, yes.
INT32 yofs = Easing_InQuad(t, pausemenu.offset.dist, 0);
dypos = ypos + yofs;
V_DrawFixedPatch( ((i == itemOn ? (294 - yofs*2/3 * (dypos > 100 ? 1 : -1)) : 261) + offset) << FRACBITS, (dypos)*FRACUNIT, FRACUNIT, 0, pp, colormap);
ypos += 50;
itemsdrawn++; // We drew that!
break;
}
}
i++; // Regardless of whether we drew or not, go to the next item in the menu.
if (i >= currentMenu->numitems)
{
i = 0;
while (!(currentMenu->menuitems[i].status & IT_DISPLAY))
i++;
}
}
// Draw the string!
const char *maintext = NULL;
const char *selectableheadertext = NULL;
const char *selectabletext = NULL;
INT32 mainflags = 0, selectableflags = 0;
if (itemOn == mpause_changegametype)
{
selectableheadertext = currentMenu->menuitems[itemOn].text;
selectabletext = gametypes[menugametype]->name;
}
else if (itemOn == mpause_addons)
{
selectableheadertext = "ADDONS";
selectabletext = menuaddonoptions ? "LOAD..." : "SETTINGS";
}
else if (itemOn == mpause_callvote)
{
selectableheadertext = currentMenu->menuitems[itemOn].text;
selectabletext = K_GetMidVoteLabel(menucallvote);
if (K_MinimalCheckNewMidVote(menucallvote) == false)
{
if (g_midVote.active == true)
{
maintext = "ACTIVE...";
}
else if (g_midVote.delay > 0)
{
if (g_midVote.delay != 1)
maintext = va("%u", ((g_midVote.delay - 1) / TICRATE) + 1);
}
else if (K_PlayerIDAllowedInMidVote(consoleplayer) == false)
{
maintext = "SPECTATING";
}
else
{
maintext = "INVALID!?";
}
if (maintext != NULL)
{
mainflags |= V_YELLOWMAP;
selectableflags |= V_MODULATE;
}
}
}
else if (itemOn == mpause_spectatetoggle)
{
const char *spectatetext = NULL;
INT32 spectateflags = 0;
if (splitspectatestate == 0)
spectatetext = "SPECTATE";
else if (splitspectatestate == 1)
{
spectatetext = "ENTER GAME";
if (!cv_allowteamchange.value)
{
spectateflags |= V_MODULATE;
}
}
else
spectatetext = "CANCEL JOIN";
if (splitscreen)
{
selectableheadertext = spectatetext;
selectabletext = va("PLAYER %c", 'A' + pausemenu.splitscreenfocusid);
selectableflags |= spectateflags;
}
else
{
maintext = spectatetext;
mainflags |= spectateflags;
}
}
else
{
maintext = currentMenu->menuitems[itemOn].text;
}
if (selectableheadertext != NULL)
{
// For selections, show the full menu text on top.
V_DrawCenteredLSTitleHighString(220 + offset*2, 75, selectableflags, selectableheadertext);
}
if (selectabletext != NULL)
{
// The selectable text is shown below.
selectableflags |= V_YELLOWMAP;
INT32 w = V_LSTitleLowStringWidth(selectabletext, selectableflags)/2;
V_DrawLSTitleLowString(220-w + offset*2, 103, selectableflags, selectabletext);
V_DrawMenuString(220-w + offset*2 - 8 - (skullAnimCounter/5), 103+6, selectableflags, "\x1C"); // left arrow
V_DrawMenuString(220+w + offset*2 + (skullAnimCounter/5), 103+6, selectableflags, "\x1D"); // right arrow
}
if (maintext != NULL)
{
// This is a regular menu option. Try to break it onto two lines.
char word1[MAXSTRINGLENGTH];
INT16 word1len = 0;
char word2[MAXSTRINGLENGTH];
INT16 word2len = 0;
boolean sok = false;
while (maintext[j] && j < MAXSTRINGLENGTH)
{
if (maintext[j] == ' ' && !sok)
{
sok = true;
j++;
continue; // We don't care about this :moyai:
}
if (sok)
{
word2[word2len] = maintext[j];
word2len++;
}
else
{
word1[word1len] = maintext[j];
word1len++;
}
j++;
}
word1[word1len] = '\0';
word2[word2len] = '\0';
// If there's no 2nd word, take this opportunity to center this line of text.
if (word1len)
V_DrawCenteredLSTitleHighString(220 + offset*2, 75 + (!word2len ? 10 : 0), mainflags, word1);
if (word2len)
V_DrawCenteredLSTitleLowString(220 + offset*2, 103, mainflags, word2);
}
if (gamestate != GS_INTERMISSION && roundqueue.size > 0)
{
y_data_t standings;
memset(&standings, 0, sizeof (standings));
standings.mainplayer = (demo.playback ? displayplayers[0] : consoleplayer);
// See also G_GetNextMap, Y_CalculateMatchData
if (
grandprixinfo.gp == true
&& netgame == false // TODO netgame Special Mode support
&& grandprixinfo.gamespeed >= KARTSPEED_NORMAL
&& roundqueue.size > 1
&& roundqueue.entries[roundqueue.size - 1].rankrestricted == true
&& (
gamedata->everseenspecial == true
|| roundqueue.position == roundqueue.size
)
)
{
// Additional cases in which it should always be shown.
standings.showrank = true;
}
if (roundqueue.position > 0 && roundqueue.position <= roundqueue.size)
{
patch_t *smallroundpatch = ST_getRoundPicture(true);
if (smallroundpatch != NULL)
{
V_DrawMappedPatch(
24, 145 + offset/2,
0,
smallroundpatch,
NULL);
}
}
V_DrawCenteredMenuString(24, 167 + offset/2, V_YELLOWMAP, M_GetGameplayMode());
Y_RoundQueueDrawer(&standings, offset/2, false, false);
}
else
{
V_DrawMenuString(4, 188 + offset/2, V_YELLOWMAP, M_GetGameplayMode());
}
}
void M_DrawKickHandler(void)
{
// fake round queue drawer simply to make release
INT32 x = 29 + 4, y = 70, returny = y;
INT32 pokeamount = playerkickmenu.poke ? ((playerkickmenu.poke & 1) ? -playerkickmenu.poke/2 : playerkickmenu.poke/2) : (I_GetTime() % 16 < 8);
INT32 x2 = x + pokeamount - 9 - 8 - 2;
boolean datarightofcolumn = false;
patch_t *resbar = W_CachePatchName("R_RESBAR", PU_CACHE); // Results bars for players
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
V_DrawMappedPatch(
x, y,
(playeringame[i] == true)
? ((players[i].spectator == true) ? V_TRANSLUCENT : 0)
: V_MODULATE,
resbar, NULL
);
V_DrawRightAlignedThinString(
x+13, y-2,
((i == playerkickmenu.player)
? highlightflags
: 0
),
va("%u", i)
);
if (playeringame[i] == true)
{
if (players[i].skincolor != SKINCOLOR_NONE)
{
UINT8 *charcolormap;
if ((players[i].pflags & PF_NOCONTEST) && players[i].bot)
{
// RETIRED !!
charcolormap = R_GetTranslationColormap(TC_DEFAULT, players[i].skincolor, GTC_CACHE);
V_DrawMappedPatch(x+14, y-5, 0, W_CachePatchName("MINIDEAD", PU_CACHE), charcolormap);
}
else
{
charcolormap = R_GetTranslationColormap(players[i].skin, players[i].skincolor, GTC_CACHE);
V_DrawMappedPatch(x+14, y-5, 0, faceprefix[players[i].skin][FACE_MINIMAP], charcolormap);
}
}
V_DrawThinString(
x+27, y-2,
(
P_IsMachineLocalPlayer(&players[i])
? highlightflags
: 0
),
player_names[i]
);
V_DrawRightAlignedThinString(
x+118, y-2,
0,
(players[i].spectator) ? "SPECTATOR" : "PLAYING"
);
}
if (i == playerkickmenu.player)
{
V_DrawScaledPatch(
x2, y-1,
(datarightofcolumn ? V_FLIP : 0),
W_CachePatchName("M_CURSOR", PU_CACHE)
);
}
y += 13;
if (i == (MAXPLAYERS-1)/2)
{
x = 169 - 6;
y = returny;
datarightofcolumn = true;
x2 = x + 118 + 9 + 8 + 8 - pokeamount;
}
}
//V_DrawFill(32 + (playerkickmenu.player & 8), 32 + (playerkickmenu.player & 7)*8, 8, 8, playeringame[playerkickmenu.player] ? 0 : 16);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL);
V_DrawCenteredThinString(
BASEVIDWIDTH/2, 12,
0,
(playerkickmenu.adminpowered)
? "You are using ""\x85""Admin Tools""\x80"", ""\x83""(A)""\x80"" to kick and ""\x84""(C)""\x80"" to ban"
: K_GetMidVoteLabel(menucallvote)
);
}
void M_DrawPlaybackMenu(void)
{
INT16 i;
patch_t *icon = NULL;
UINT8 *activemap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GOLD, GTC_MENUCACHE);
UINT32 transmap = max(0, (INT32)(leveltime - playback_last_menu_interaction_leveltime - 4*TICRATE)) / 5;
transmap = min(8, transmap) << V_ALPHASHIFT;
// wip
//M_DrawTextBox(currentMenu->x-68, currentMenu->y-7, 15, 15);
//M_DrawCenteredMenu();
for (i = 0; i < currentMenu->numitems; i++)
{
UINT8 *inactivemap = NULL;
if (i >= playback_view1 && i <= playback_view4)
{
if (modeattacking) continue;
if (r_splitscreen >= i - playback_view1)
{
INT32 ply = displayplayers[i - playback_view1];
icon = faceprefix[players[ply].skin][FACE_RANK];
if (i != itemOn)
inactivemap = R_GetTranslationColormap(players[ply].skin, players[ply].skincolor, GTC_MENUCACHE);
}
else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR)
icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE);
}
else if (currentMenu->menuitems[i].status == IT_DISABLED)
continue;
else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR)
icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE);
if ((i == playback_fastforward && cv_playbackspeed.value > 1) || (i == playback_rewind && demo.rewinding))
V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].mvar1, currentMenu->y, V_SNAPTOTOP, icon, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JAWZ, GTC_MENUCACHE));
else
V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].mvar1, currentMenu->y, V_SNAPTOTOP, icon, (i == itemOn) ? activemap : inactivemap);
if (i == itemOn)
{
V_DrawMenuString(currentMenu->x + currentMenu->menuitems[i].mvar1 + 4, currentMenu->y + 14,
V_SNAPTOTOP|highlightflags, "\x1A");
V_DrawCenteredMenuString(BASEVIDWIDTH/2, currentMenu->y + 19, V_SNAPTOTOP, currentMenu->menuitems[i].text);
if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_ARROWS)
{
char *str;
if (!(i == playback_viewcount && r_splitscreen == 3))
V_DrawMenuString(BASEVIDWIDTH/2 - 4, currentMenu->y + 28 - (skullAnimCounter/5),
V_SNAPTOTOP|highlightflags, "\x1A"); // up arrow
if (!(i == playback_viewcount && r_splitscreen == 0))
V_DrawMenuString(BASEVIDWIDTH/2 - 4, currentMenu->y + 48 + (skullAnimCounter/5),
V_SNAPTOTOP|highlightflags, "\x1B"); // down arrow
switch (i)
{
case playback_viewcount:
str = va("%d", r_splitscreen+1);
break;
case playback_view1:
case playback_view2:
case playback_view3:
case playback_view4:
str = player_names[displayplayers[i - playback_view1]]; // 0 to 3
break;
default: // shouldn't ever be reached but whatever
continue;
}
V_DrawCenteredMenuString(BASEVIDWIDTH/2, currentMenu->y + 38, V_SNAPTOTOP|highlightflags, str);
}
}
}
}
// Draw misc menus:
// Addons
#define lsheadingheight 16
// Just do this here instead.
static void M_CacheAddonPatches(void)
{
addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_STATIC);
addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_STATIC);
addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_STATIC);
addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_STATIC);
addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_STATIC);
addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_STATIC);
#ifdef USE_KART
addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_STATIC);
#endif
addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_STATIC);
addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_STATIC);
addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_STATIC);
addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_STATIC);
addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_STATIC);
addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_STATIC);
addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_STATIC);
addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_STATIC);
}
#define addonsseperation 16
void M_DrawAddons(void)
{
INT32 x, y;
ssize_t i, m;
const UINT8 *flashcol = NULL;
UINT8 hilicol;
M_CacheAddonPatches();
if (Playing())
V_DrawCenteredMenuString(BASEVIDWIDTH/2, 4, warningflags, "Adding files mid-game may cause problems.");
else
V_DrawCenteredMenuString(BASEVIDWIDTH/2, 4, 0,
LOCATIONSTRING1);
// DRAW MENU
x = currentMenu->x;
y = currentMenu->y - 1;
hilicol = V_GetStringColormap(highlightflags)[0];
y -= 16;
V_DrawString(x-21, y + (lsheadingheight - 12), highlightflags, M_AddonsHeaderPath());
V_DrawFill(x-21, y + (lsheadingheight - 3), MAXSTRINGLENGTH*8+6, 1, hilicol);
//V_DrawFill(x-21, y + (lsheadingheight - 2), MAXSTRINGLENGTH*8+6, 1, 30);
y += 10;
M_DrawTextBox(x - (21 + 5), y, MAXSTRINGLENGTH, 1);
{
const char *str = (menusearch[0] ? cv_dummyaddonsearch.string : "Search...");
INT32 tflag = (menusearch[0] ? 0 : V_TRANSLUCENT);
INT32 xoffs = 0;
if (itemOn == 0)
{
xoffs += 8;
V_DrawMenuString(x + (skullAnimCounter/5) - 20, y+8, highlightflags, "\x1D");
}
V_DrawString(x + xoffs - 18, y+8, tflag, str);
}
V_DrawSmallScaledPatch(x - (21 + 5 + 16), y+4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+3]);
y += 21;
m = (addonsseperation*(2*numaddonsshown + 1)) + 1 + 2*(16-addonsseperation);
V_DrawFill(x - 21, y - 1, MAXSTRINGLENGTH*8+6, m, 159);
// scrollbar!
if (sizedirmenu <= (2*numaddonsshown + 1))
i = 0;
else
{
ssize_t q = m;
m = ((2*numaddonsshown + 1) * m)/sizedirmenu;
if (dir_on[menudepthleft] <= numaddonsshown) // all the way up
i = 0;
else if (sizedirmenu <= (dir_on[menudepthleft] + numaddonsshown + 1)) // all the way down
i = q-m;
else
i = ((dir_on[menudepthleft] - numaddonsshown) * (q-m))/(sizedirmenu - (2*numaddonsshown + 1));
}
V_DrawFill(x + MAXSTRINGLENGTH*8+5 - 21, (y - 1) + i, 1, m, hilicol);
// get bottom...
m = dir_on[menudepthleft] + numaddonsshown + 1;
if (m > (ssize_t)sizedirmenu)
m = sizedirmenu;
// then compute top and adjust bottom if needed!
if (m < (2*numaddonsshown + 1))
{
m = min(sizedirmenu, 2*numaddonsshown + 1);
i = 0;
}
else
i = m - (2*numaddonsshown + 1);
if (i != 0)
V_DrawMenuString(19, y+4 - (skullAnimCounter/5), highlightflags, "\x1A");
if (skullAnimCounter < 4)
flashcol = V_GetStringColormap(highlightflags);
y -= (16-addonsseperation);
for (; i < m; i++)
{
UINT32 flags = 0;
if (y > BASEVIDHEIGHT) break;
if (dirmenu[i])
#define type (UINT8)(dirmenu[i][DIR_TYPE])
{
if (type & EXT_LOADED)
{
flags |= V_TRANSLUCENT;
V_DrawSmallScaledPatch(x-(16+4), y, V_TRANSLUCENT, addonsp[(type & ~EXT_LOADED)]);
V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+2]);
}
else
V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[(type & ~EXT_LOADED)]);
if (itemOn == 1 && (size_t)i == dir_on[menudepthleft])
{
V_DrawFixedPatch((x-(16+4))<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, 0, addonsp[NUM_EXT+1], flashcol);
flags = highlightflags;
}
#define charsonside 14
if (dirmenu[i][DIR_LEN] > (charsonside*2 + 3))
V_DrawString(x, y+4, flags, va("%.*s...%s", charsonside, dirmenu[i]+DIR_STRING, dirmenu[i]+DIR_STRING+dirmenu[i][DIR_LEN]-(charsonside+1)));
#undef charsonside
else
V_DrawString(x, y+4, flags, dirmenu[i]+DIR_STRING);
}
#undef type
y += addonsseperation;
}
if (m != (ssize_t)sizedirmenu)
V_DrawMenuString(19, y-12 + (skullAnimCounter/5), highlightflags, "\x1B");
if (m < (2*numaddonsshown + 1))
{
y += ((2*numaddonsshown + 1)-m)*addonsseperation;
}
y -= 2;
V_DrawSmallScaledPatch(x, y, ((!majormods) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
if (modifiedgame)
V_DrawSmallScaledPatch(x, y, 0, addonsp[NUM_EXT+2]);
m = numwadfiles-(mainwads+musicwads+1);
V_DrawCenteredMenuString(BASEVIDWIDTH/2, y+4, (majormods ? highlightflags : V_TRANSLUCENT), va("%ld ADD-ON%s LOADED", (long)m, (m == 1) ? "" : "S")); //+2 for music, sounds, +1 for bios.pk3
}
#undef addonsseperation
// Challenges Menu
static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, UINT8 *flashmap, boolean hili)
{
#ifdef DEVELOP
extern consvar_t cv_debugchallenges;
#endif
unlockable_t *ref = NULL;
patch_t *pat = missingpat;
UINT8 *colormap = NULL, *bgmap = NULL;
INT32 tileflags = 0;
fixed_t siz, accordion;
UINT16 id, num;
boolean unlockedyet;
boolean categoryside;
id = (i * CHALLENGEGRIDHEIGHT) + j;
num = gamedata->challengegrid[id];
// Empty spots in the grid are always unconnected.
if (num >= MAXUNLOCKABLES)
{
goto drawborder;
}
// Okay, this is what we want to draw.
ref = &unlockables[num];
#ifdef DEVELOP
if (cv_debugchallenges.value > 0 &&
cv_debugchallenges.value != ref->conditionset)
{
// Searching for a conditionset, fade out any tiles
// that don't match.
tileflags = V_80TRANS;
}
#endif
unlockedyet = !((gamedata->unlocked[num] == false)
|| (challengesmenu.pending && num == challengesmenu.currentunlock && challengesmenu.unlockanim <= UNLOCKTIME));
// If we aren't unlocked yet, return early.
if (!unlockedyet)
{
UINT32 flags = 0;
boolean hint = !!(challengesmenu.extradata[id].flags & CHE_HINT);
pat = W_CachePatchName(
va("UN_OUTL%c",
ref->majorunlock ? 'B' : 'A'
),
PU_CACHE);
V_DrawFixedPatch(
x*FRACUNIT, y*FRACUNIT,
FRACUNIT,
(hint ? V_ADD : V_SUBTRACT)|V_90TRANS,
pat,
colormap
);
if (!hint)
{
colormap = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_CACHE);
flags = V_SUBTRACT|V_90TRANS;
}
pat = W_CachePatchName(
va("UN_HNT%c%c",
(hili && !colormap) ? '1' : '2',
ref->majorunlock ? 'B' : 'A'
),
PU_CACHE);
V_DrawFixedPatch(
x*FRACUNIT, y*FRACUNIT,
FRACUNIT,
flags, pat,
colormap
);
pat = missingpat;
colormap = NULL;
goto drawborder;
}
accordion = FRACUNIT;
if (challengesmenu.extradata[id].flip != 0
&& challengesmenu.extradata[id].flip != (TILEFLIP_MAX/2))
{
angle_t bad = (FixedAngle(FixedMul(challengesmenu.extradata[id].flip * FRACUNIT + rendertimefrac, 360*FRACUNIT/TILEFLIP_MAX)) >> ANGLETOFINESHIFT) & FINEMASK;
accordion = FINECOSINE(bad);
if (accordion < 0)
accordion = -accordion;
}
pat = W_CachePatchName(
(ref->majorunlock ? "UN_BORDB" : "UN_BORDA"),
PU_CACHE);
bgmap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_SILVER, GTC_MENUCACHE);
V_DrawStretchyFixedPatch(
(x*FRACUNIT) + (SHORT(pat->width)*(FRACUNIT-accordion)/2), y*FRACUNIT,
accordion,
FRACUNIT,
tileflags, pat,
bgmap
);
pat = missingpat;
categoryside = (challengesmenu.extradata[id].flip <= TILEFLIP_MAX/4
|| challengesmenu.extradata[id].flip > (3*TILEFLIP_MAX)/4);
#ifdef DEVELOP
if (cv_debugchallenges.value)
{
// Show the content of every tile without needing to
// flip them.
categoryside = false;
}
#endif
if (categoryside)
{
char categoryid = '0';
colormap = bgmap;
switch (ref->type)
{
case SECRET_SKIN:
categoryid = '1';
break;
case SECRET_FOLLOWER:
categoryid = '2';
break;
case SECRET_COLOR:
categoryid = '3';
break;
case SECRET_CUP:
categoryid = '4';
break;
case SECRET_MAP:
categoryid = '8';
break;
case SECRET_HARDSPEED:
case SECRET_MASTERMODE:
case SECRET_ENCORE:
categoryid = '5';
break;
case SECRET_ONLINE:
case SECRET_ADDONS:
case SECRET_EGGTV:
case SECRET_SOUNDTEST:
case SECRET_ALTTITLE:
case SECRET_MEMETAUNTS:
categoryid = '6';
break;
case SECRET_TIMEATTACK:
case SECRET_PRISONBREAK:
case SECRET_SPECIALATTACK:
case SECRET_SPBATTACK:
categoryid = '7';
break;
case SECRET_ALTMUSIC:
categoryid = '9';
break;
}
pat = challengesmenu.tile_category[categoryid - '0'][ref->majorunlock ? 1 : 0];
if (pat == missingpat)
{
pat = challengesmenu.tile_category[categoryid - '0'][ref->majorunlock ? 0 : 1];
}
}
else if (ref->icon != NULL && ref->icon[0])
{
pat = W_CachePatchName(ref->icon, PU_CACHE);
if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors)
{
colormap = R_GetTranslationColormap(TC_DEFAULT, ref->color, GTC_MENUCACHE);
}
}
else
{
UINT8 iconid = 0;
switch (ref->type)
{
case SECRET_SKIN:
{
INT32 skin = M_UnlockableSkinNum(ref);
if (skin != -1)
{
colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
pat = faceprefix[skin][(ref->majorunlock) ? FACE_WANTED : FACE_RANK];
}
break;
}
case SECRET_FOLLOWER:
{
INT32 skin = M_UnlockableFollowerNum(ref);
if (skin != -1)
{
INT32 psk = R_SkinAvailableEx(cv_skin[0].string, false);
UINT16 col = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, &followers[skin], cv_playercolor[0].value, (psk != -1) ? &skins[psk] : &skins[0]);
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
pat = W_CachePatchName(followers[skin].icon, PU_CACHE);
}
break;
}
case SECRET_COLOR:
{
INT32 colorid = M_UnlockableColorNum(ref);
if (colorid != SKINCOLOR_NONE)
{
colormap = R_GetTranslationColormap(TC_DEFAULT, colorid, GTC_MENUCACHE);
}
iconid = 2;
break;
}
case SECRET_MAP:
iconid = 14;
break;
case SECRET_ALTMUSIC:
iconid = 16;
break;
case SECRET_HARDSPEED:
iconid = 3;
break;
case SECRET_MASTERMODE:
iconid = 4;
break;
case SECRET_ENCORE:
iconid = 5;
break;
case SECRET_ONLINE:
iconid = 10;
break;
case SECRET_ADDONS:
iconid = 12;
break;
case SECRET_EGGTV:
iconid = 11;
break;
case SECRET_SOUNDTEST:
iconid = 1;
break;
case SECRET_ALTTITLE:
iconid = 6;
break;
case SECRET_MEMETAUNTS:
iconid = 13;
break;
case SECRET_TIMEATTACK:
iconid = 7;
break;
case SECRET_PRISONBREAK:
iconid = 8;
break;
case SECRET_SPECIALATTACK:
iconid = 9;
break;
case SECRET_SPBATTACK:
iconid = 15;
break;
default:
{
if (!colormap && ref->color != SKINCOLOR_NONE && ref->color < numskincolors)
{
colormap = R_GetTranslationColormap(TC_RAINBOW, ref->color, GTC_MENUCACHE);
}
break;
}
}
if (pat == missingpat)
{
pat = W_CachePatchName(va("UN_IC%02u%c",
iconid,
ref->majorunlock ? 'B' : 'A'),
PU_CACHE);
if (pat == missingpat)
{
pat = W_CachePatchName(va("UN_IC%02u%c",
iconid,
ref->majorunlock ? 'A' : 'B'),
PU_CACHE);
}
}
}
siz = (SHORT(pat->width) << FRACBITS);
if (!siz)
; // prevent div/0
else if (ref->majorunlock)
{
V_DrawStretchyFixedPatch(
((x + 5)*FRACUNIT) + (32*(FRACUNIT-accordion)/2), (y + 5)*FRACUNIT,
FixedDiv(32*accordion, siz),
FixedDiv(32 << FRACBITS, siz),
tileflags, pat,
colormap
);
}
else
{
V_DrawStretchyFixedPatch(
((x + 2)*FRACUNIT) + (16*(FRACUNIT-accordion)/2), (y + 2)*FRACUNIT,
FixedDiv(16*accordion, siz),
FixedDiv(16 << FRACBITS, siz),
tileflags, pat,
colormap
);
}
drawborder:
if (num < MAXUNLOCKABLES && gamedata->unlockpending[num])
{
INT32 area = (ref->majorunlock) ? 42 : 20;
V_DrawFadeFill(x, y, area, area, 0, flashmap[(skullAnimCounter/5) ? 99 : 101], 2);
}
if (hili)
{
boolean maj = (ref != NULL && ref->majorunlock);
char buffer[9];
sprintf(buffer, "UN_RETA1");
buffer[6] = maj ? 'B' : 'A';
buffer[7] = (skullAnimCounter/5) ? '2' : '1';
pat = W_CachePatchName(buffer, PU_CACHE);
V_DrawFixedPatch(
x*FRACUNIT, y*FRACUNIT,
FRACUNIT,
0, pat,
flashmap
);
}
#ifdef DEVELOP
if (cv_debugchallenges.value == -2 ||
cv_debugchallenges.value > 0)
{
// Display the conditionset for this tile.
V_DrawThinString(x, y,
ref->conditionset == cv_debugchallenges.value ? V_AQUAMAP : V_GRAYMAP,
va("%u", ref->conditionset));
}
#endif
}
#define challengetransparentstrength 8
void M_DrawCharacterIconAndEngine(INT32 x, INT32 y, UINT8 skin, UINT8 *colormap, boolean dot)
{
V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT,
FRACUNIT,
0, faceprefix[skin][FACE_RANK],
colormap);
if (dot)
{
V_DrawScaledPatch(x, y + 11, 0, W_CachePatchName("ALTSDOT", PU_CACHE));
}
V_DrawFadeFill(x+16, y, 16, 16, 0, 31, challengetransparentstrength);
V_DrawFill(x+16+5, y+1, 1, 14, 0);
V_DrawFill(x+16+5+5, y+1, 1, 14, 0);
V_DrawFill(x+16+1, y+5, 14, 1, 0);
V_DrawFill(x+16+1, y+5+5, 14, 1, 0);
// The following is a partial duplication of R_GetEngineClass
{
INT32 s = (skins[skin].kartspeed - 1)/3;
INT32 w = (skins[skin].kartweight - 1)/3;
#define LOCKSTAT(stat) \
if (stat < 0) { stat = 0; } \
if (stat > 2) { stat = 2; }
LOCKSTAT(s);
LOCKSTAT(w);
#undef LOCKSTAT
V_DrawFill(x+16 + (s*5), y + (w*5), 6, 6, 0);
}
}
static void M_DrawChallengePreview(INT32 x, INT32 y)
{
unlockable_t *ref = NULL;
UINT8 *colormap = NULL;
UINT16 specialmap = NEXTMAP_INVALID;
if (challengesmenu.currentunlock >= MAXUNLOCKABLES)
{
return;
}
// Okay, this is what we want to draw.
ref = &unlockables[challengesmenu.currentunlock];
// Funny question mark?
if (!gamedata->unlocked[challengesmenu.currentunlock])
{
spritedef_t *sprdef = &sprites[SPR_UQMK];
spriteframe_t *sprframe;
patch_t *patch;
UINT32 useframe;
UINT32 addflags = 0;
if (!sprdef->numframes)
{
return;
}
useframe = (challengesmenu.ticker / 2) % sprdef->numframes;
sprframe = &sprdef->spriteframes[useframe];
patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
if (sprframe->flip & 1) // Only for first sprite
{
addflags ^= V_FLIP; // This sprite is left/right flipped!
}
V_DrawFixedPatch(x*FRACUNIT, (y+2)*FRACUNIT, FRACUNIT, addflags, patch, NULL);
return;
}
switch (ref->type)
{
case SECRET_SKIN:
{
INT32 skin = M_UnlockableSkinNum(ref), i;
// Draw our character!
if (skin != -1)
{
colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
M_DrawCharacterSprite(x, y, skin, SPR2_STIN, 7, 0, 0, colormap);
for (i = 0; i < skin; i++)
{
if (!R_SkinUsable(-1, i, false))
continue;
if (skins[i].kartspeed != skins[skin].kartspeed)
continue;
if (skins[i].kartweight != skins[skin].kartweight)
continue;
colormap = R_GetTranslationColormap(i, skins[i].prefcolor, GTC_MENUCACHE);
break;
}
M_DrawCharacterIconAndEngine(4, BASEVIDHEIGHT-(4+16), i, colormap, (i != skin));
}
break;
}
case SECRET_FOLLOWER:
{
INT32 skin = R_SkinAvailableEx(cv_skin[0].string, false);
INT32 fskin = M_UnlockableFollowerNum(ref);
// Draw proximity reference for character
if (skin == -1)
skin = 0;
colormap = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_MENUCACHE);
M_DrawCharacterSprite(x, y, skin, SPR2_STIN, 7, 0, 0, colormap);
// Draw follower next to them
if (fskin != -1)
{
UINT16 col = K_GetEffectiveFollowerColor(followers[fskin].defaultcolor, &followers[fskin], cv_playercolor[0].value, &skins[skin]);
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
M_DrawFollowerSprite(x - 16, y, fskin, false, 0, colormap, NULL);
if (followers[fskin].category < numfollowercategories)
{
V_DrawFixedPatch(4*FRACUNIT, (BASEVIDHEIGHT-(4+16))*FRACUNIT,
FRACUNIT,
0, W_CachePatchName(followercategories[followers[fskin].category].icon, PU_CACHE),
NULL);
}
}
break;
}
case SECRET_COLOR:
{
INT32 colorid = M_UnlockableColorNum(ref);
if (colorid == SKINCOLOR_NONE)
break;
INT32 skin = R_SkinAvailableEx(cv_skin[0].string, false);
if (skin == -1)
skin = 0;
colormap = R_GetTranslationColormap(skin, colorid, GTC_MENUCACHE);
// Draw reference for character bathed in coloured slime
M_DrawCharacterSprite(x, y, skin, SPR2_STIN, 7, 0, 0, colormap);
break;
}
case SECRET_CUP:
{
levelsearch_t templevelsearch;
UINT32 i, id, maxid, offset;
cupheader_t *temp = M_UnlockableCup(ref);
if (!temp)
break;
templevelsearch.cup = temp;
templevelsearch.typeoflevel = G_TOLFlag(GT_RACE)|G_TOLFlag(GT_BATTLE);
templevelsearch.cupmode = true;
templevelsearch.timeattack = false;
templevelsearch.tutorial = false;
templevelsearch.checklocked = true;
M_DrawCupPreview(146, &templevelsearch);
maxid = id = (temp->id % (CUPMENU_COLUMNS * CUPMENU_ROWS));
offset = (temp->id - id) * 2;
while (temp && maxid < (CUPMENU_COLUMNS * CUPMENU_ROWS))
{
maxid++;
temp = temp->next;
}
y = (BASEVIDHEIGHT-(4+16));
if (challengesmenu.cache_secondrowlocked == true)
y += 8;
V_DrawFadeFill(
4,
y,
28 + offset,
(challengesmenu.cache_secondrowlocked ? 8 : 16),
0,
31,
challengetransparentstrength
);
for (i = 0; i < offset; i += 4)
{
V_DrawFill(4+1 + i, y+3, 2, 2, 15);
if (challengesmenu.cache_secondrowlocked == false)
V_DrawFill(4+1 + i, y+8+3, 2, 2, 15);
}
for (i = 0; i < CUPMENU_COLUMNS; i++)
{
if (templevelsearch.cup && id == i)
{
V_DrawFill(offset + 4 + (i*4), y, 4, 8, 0);
}
else if (i < maxid)
{
V_DrawFill(offset + 4+1 + (i*4), y+3, 2, 2, 0);
}
if (templevelsearch.cup && id == i+CUPMENU_COLUMNS)
{
V_DrawFill(offset + 4 + (i*4), y+8, 4, 8, 0);
}
else if (challengesmenu.cache_secondrowlocked == true)
;
else if (i+CUPMENU_COLUMNS < maxid)
{
V_DrawFill(offset + 4+1 + (i*4), y+8+3, 2, 2, 0);
}
}
break;
}
case SECRET_MAP:
{
boolean validdraw = false;
const char *gtname = "Find your prize...";
UINT16 mapnum = M_UnlockableMapNum(ref);
if (mapnum >= nummapheaders
|| mapheaderinfo[mapnum] == NULL
|| mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU)
{
gtname = "INVALID HEADER";
}
else if (
( // Check for visitation
(mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
|| (mapheaderinfo[mapnum]->records.mapvisited & MV_VISITED)
) && ( // Check for completion
!(mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED)
|| (mapheaderinfo[mapnum]->records.mapvisited & MV_BEATEN)
)
)
{
validdraw = true;
}
if (validdraw)
{
K_DrawMapThumbnail(
(x-50)<<FRACBITS, (146+2)<<FRACBITS,
80<<FRACBITS,
0,
mapnum,
NULL);
INT32 guessgt = G_GuessGametypeByTOL(mapheaderinfo[mapnum]->typeoflevel);
if (guessgt == -1)
{
// No Time Attack support, so specify...
gtname = "Match Race/Online";
}
else
{
if (guessgt == GT_VERSUS)
{
// Fudge since there's no Versus-specific menu right now...
guessgt = GT_SPECIAL;
}
if (guessgt == GT_SPECIAL && !M_SecretUnlocked(SECRET_SPECIALATTACK, true))
{
gtname = "???";
}
else
{
gtname = gametypes[guessgt]->name;
}
}
}
else
{
V_DrawFixedPatch(
(x-50)<<FRACBITS, (146+2)<<FRACBITS,
FRACUNIT,
0,
unvisitedlvl[challengesmenu.ticker % 4],
NULL);
}
V_DrawThinString(1, BASEVIDHEIGHT-(9+3), 0, gtname);
break;
}
case SECRET_ENCORE:
{
static UINT16 encoremapcache = NEXTMAP_INVALID;
if (encoremapcache > nummapheaders)
{
encoremapcache = G_RandMap(G_TOLFlag(GT_RACE), UINT16_MAX, true, false, NULL);
}
specialmap = encoremapcache;
break;
}
case SECRET_TIMEATTACK:
{
static UINT16 tamapcache = NEXTMAP_INVALID;
if (tamapcache > nummapheaders)
{
tamapcache = G_RandMap(G_TOLFlag(GT_RACE), UINT16_MAX, true, false, NULL);
}
specialmap = tamapcache;
break;
}
case SECRET_PRISONBREAK:
{
static UINT16 btcmapcache = NEXTMAP_INVALID;
if (btcmapcache > nummapheaders)
{
btcmapcache = G_RandMap(G_TOLFlag(GT_BATTLE), UINT16_MAX, true, false, NULL);
}
specialmap = btcmapcache;
break;
}
case SECRET_SPECIALATTACK:
{
static UINT16 sscmapcache = NEXTMAP_INVALID;
if (sscmapcache > nummapheaders)
{
sscmapcache = G_RandMap(G_TOLFlag(GT_SPECIAL), UINT16_MAX, true, false, NULL);
}
specialmap = sscmapcache;
break;
}
case SECRET_SPBATTACK:
{
static UINT16 spbmapcache = NEXTMAP_INVALID;
if (spbmapcache > nummapheaders)
{
spbmapcache = G_RandMap(G_TOLFlag(GT_RACE), UINT16_MAX, true, false, NULL);
}
specialmap = spbmapcache;
break;
}
case SECRET_HARDSPEED:
{
static UINT16 hardmapcache = NEXTMAP_INVALID;
if (hardmapcache > nummapheaders)
{
hardmapcache = G_RandMap(G_TOLFlag(GT_RACE), UINT16_MAX, true, false, NULL);
}
specialmap = hardmapcache;
break;
}
case SECRET_MASTERMODE:
{
static UINT16 mastermapcache = NEXTMAP_INVALID;
if (mastermapcache > nummapheaders)
{
mastermapcache = G_RandMap(G_TOLFlag(GT_RACE), UINT16_MAX, true, false, NULL);
}
specialmap = mastermapcache;
break;
}
case SECRET_ONLINE:
{
V_DrawFixedPatch(-3*FRACUNIT, (y-40)*FRACUNIT,
FRACUNIT,
0, W_CachePatchName("EGGASTLA", PU_CACHE),
NULL);
break;
}
case SECRET_ALTTITLE:
{
x = 8;
y = BASEVIDHEIGHT-16;
V_DrawGamemodeString(x, y - 33, 0, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_MENUCACHE), M_UseAlternateTitleScreen() ? "On" : "Off");
K_drawButtonAnim(x, y, 0, kp_button_a[1], challengesmenu.ticker);
x += SHORT(kp_button_a[1][0]->width);
V_DrawThinString(x, y + 1, highlightflags, "Toggle");
break;
}
case SECRET_ALTMUSIC:
{
UINT16 map = M_UnlockableMapNum(ref);
if (map >= nummapheaders
|| !mapheaderinfo[map])
{
return;
}
UINT8 musicid;
for (musicid = 1; musicid < MAXMUSNAMES; musicid++)
{
if (mapheaderinfo[map]->cache_muslock[musicid - 1] == challengesmenu.currentunlock)
break;
}
if (musicid == MAXMUSNAMES)
{
return;
}
spritedef_t *sprdef = &sprites[SPR_ALTM];
spriteframe_t *sprframe;
patch_t *patch;
UINT32 addflags = 0;
x -= 10;
y += 15;
if (sprdef->numframes)
{
sprframe = &sprdef->spriteframes[0];
patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
if (sprframe->flip & 1) // Only for first sprite
{
addflags ^= V_FLIP; // This sprite is left/right flipped!
}
V_DrawFixedPatch(x*FRACUNIT, (y+2)*FRACUNIT, FRACUNIT/2, addflags, patch, NULL);
}
x = 8;
y = BASEVIDHEIGHT-16;
const boolean thismusplaying = Music_Playing("challenge_altmusic");
boolean pushed = false;
const char *song = NULL;
if (M_SecretUnlocked(SECRET_ENCORE, true)
&& musicid < mapheaderinfo[map]->encoremusname_size)
{
if (thismusplaying)
{
song = Music_Song("challenge_altmusic");
pushed = strcmp(song, mapheaderinfo[map]->encoremusname[musicid]) == 0;
}
K_drawButton(x&FRACUNIT, y*FRACUNIT, 0, kp_button_l, pushed);
x += SHORT(kp_button_l[0]->width);
V_DrawThinString(x, y + 1, (pushed ? V_GRAYMAP : highlightflags), "E Side");
x = 8;
y -= 10;
}
if (musicid < mapheaderinfo[map]->musname_size)
{
if (pushed || !thismusplaying)
{
pushed = false;
}
else
{
if (!song)
song = Music_Song("challenge_altmusic");
pushed = strcmp(song, mapheaderinfo[map]->musname[musicid]) == 0;
}
K_drawButton(x*FRACUNIT, y*FRACUNIT, 0, kp_button_a[1], pushed);
x += SHORT(kp_button_a[1][0]->width);
V_DrawThinString(x, y + 1, (pushed ? V_GRAYMAP : highlightflags), "Play CD");
}
}
default:
{
break;
}
}
if (specialmap == NEXTMAP_INVALID || !ref)
return;
x -= 50;
y = 146+2;
K_DrawMapThumbnail(
(x)<<FRACBITS, (y)<<FRACBITS,
80<<FRACBITS,
(ref->type == SECRET_ENCORE) ? V_FLIP : 0,
specialmap,
NULL);
if (ref->type == SECRET_ENCORE)
{
static angle_t rubyfloattime = 0;
const fixed_t rubyheight = FINESINE(rubyfloattime>>ANGLETOFINESHIFT);
V_DrawFixedPatch((x+40)<<FRACBITS, ((y+25)<<FRACBITS) - (rubyheight<<1), FRACUNIT, 0, W_CachePatchName("RUBYICON", PU_CACHE), NULL);
rubyfloattime += FixedMul(ANGLE_MAX/NEWTICRATE, renderdeltatics);
}
else if (ref->type == SECRET_SPBATTACK)
{
V_DrawFixedPatch((x+40-25)<<FRACBITS, ((y+25-25)<<FRACBITS),
FRACUNIT, 0,
W_CachePatchName(K_GetItemPatch(KITEM_SPB, false), PU_CACHE),
NULL);
}
else if (ref->type == SECRET_HARDSPEED)
{
V_DrawFixedPatch((x+40-25)<<FRACBITS, ((y+25-25)<<FRACBITS),
FRACUNIT, 0,
W_CachePatchName(K_GetItemPatch(KITEM_ROCKETSNEAKER, false), PU_CACHE),
NULL);
}
else if (ref->type == SECRET_MASTERMODE)
{
V_DrawFixedPatch((x+40-25)<<FRACBITS, ((y+25-25)<<FRACBITS),
FRACUNIT, 0,
W_CachePatchName(K_GetItemPatch(KITEM_JAWZ, false), PU_CACHE),
NULL);
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, M_GetCvPlayerColor(0), GTC_MENUCACHE);
V_DrawFixedPatch((x+40)<<FRACBITS, ((y+25)<<FRACBITS),
FRACUNIT/2, 0,
W_CachePatchName("K_LAPE02", PU_CACHE),
colormap);
}
}
#define challengesgridstep 22
static void M_DrawChallengeKeys(INT32 tilex, INT32 tiley)
{
const UINT8 pid = 0;
patch_t *key = W_CachePatchName("UN_CHA00", PU_CACHE);
INT32 offs = challengesmenu.unlockcount[CMC_CHAONOPE];
if (offs & 1)
offs = -offs;
offs /= 2;
fixed_t keyx = (8+offs)*FRACUNIT, keyy = 0;
const boolean keybuttonpress = (menumessage.active == false && M_MenuExtraHeld(pid) == true);
// Button prompt
K_drawButton(
24 << FRACBITS,
16 << FRACBITS,
0, kp_button_c[1],
keybuttonpress
);
// Metyr of rounds played that contribute to Chao Key generation
{
const INT32 keybarlen = 32, keybary = 28;
offs = keybarlen;
if (gamedata->chaokeys < GDMAX_CHAOKEYS)
{
#if (GDCONVERT_ROUNDSTOKEY != 32)
offs = ((gamedata->pendingkeyroundoffset * keybarlen)/GDCONVERT_ROUNDSTOKEY);
#else
offs = gamedata->pendingkeyroundoffset;
#endif
}
if (offs > 0)
V_DrawFill(1+2, keybary, offs, 1, 0);
if (offs < keybarlen)
V_DrawFadeFill(1+2+offs, keybary, keybarlen-offs, 1, 0, 31, challengetransparentstrength);
}
// Counter
{
INT32 textx = 4, texty = 20-challengesmenu.unlockcount[CMC_CHAOANIM];
UINT8 numbers[4];
numbers[0] = ((gamedata->chaokeys / 100) % 10);
numbers[1] = ((gamedata->chaokeys / 10) % 10);
numbers[2] = (gamedata->chaokeys % 10);
numbers[3] = ((gamedata->chaokeys / 1000) % 10);
if (numbers[3] != 0)
{
V_DrawScaledPatch(textx - 4, texty, 0, kp_facenum[numbers[3]]);
textx += 2;
}
UINT8 i = 0;
while (i < 3)
{
V_DrawScaledPatch(textx, texty, 0, kp_facenum[numbers[i]]);
textx += 6;
i++;
}
}
// Hand
if (challengesmenu.keywasadded == true)
{
INT32 handx = 32 + 16;
if (keybuttonpress == false)
{
// Only animate if it's the focus
handx -= (skullAnimCounter/5);
}
V_DrawScaledPatch(handx, 8, V_FLIP,
W_CachePatchName("M_CURSOR", PU_CACHE));
}
UINT8 keysbeingused = 0;
// The Chao Key swooping animation
if (challengesmenu.currentunlock < MAXUNLOCKABLES && challengesmenu.chaokeyhold)
{
fixed_t baseradius = challengesgridstep;
boolean major = false, ending = false;
if (unlockables[challengesmenu.currentunlock].majorunlock == true)
{
major = true;
tilex += challengesgridstep/2;
tiley += challengesgridstep/2;
baseradius = (7*baseradius)/4;
}
const INT32 chaohold_duration =
CHAOHOLD_PADDING
+ (major
? CHAOHOLD_MAJOR
: CHAOHOLD_STANDARD
);
if (challengesmenu.chaokeyhold >= chaohold_duration - CHAOHOLD_END)
{
ending = true;
baseradius = ((chaohold_duration - challengesmenu.chaokeyhold)*baseradius)*(FRACUNIT/CHAOHOLD_END);
}
INT16 specifickeyholdtime = challengesmenu.chaokeyhold;
for (; keysbeingused < (major ? 10 : 1); keysbeingused++, specifickeyholdtime -= (CHAOHOLD_STANDARD/10))
{
fixed_t radius = baseradius;
fixed_t thiskeyx, thiskeyy;
fixed_t keyholdrotation = 0;
if (specifickeyholdtime < CHAOHOLD_BEGIN)
{
if (specifickeyholdtime <= 0)
{
// Nothing following will be relevant
break;
}
radius = (specifickeyholdtime*radius)*(FRACUNIT/CHAOHOLD_BEGIN);
thiskeyx = keyx + specifickeyholdtime*((tilex*FRACUNIT) - keyx)/CHAOHOLD_BEGIN;
thiskeyy = keyy + specifickeyholdtime*((tiley*FRACUNIT) - keyy)/CHAOHOLD_BEGIN;
}
else
{
keyholdrotation = (-36 * keysbeingused) * FRACUNIT; // 360/10
if (ending == false)
{
radius <<= FRACBITS;
keyholdrotation += 360 * ((challengesmenu.chaokeyhold - CHAOHOLD_BEGIN))
* (FRACUNIT/(CHAOHOLD_STANDARD)); // intentionally not chaohold_duration
if (keysbeingused == 0)
{
INT32 time = (major ? 5 : 3) - (keyholdrotation - 1) / (90 * FRACUNIT);
if (time <= 5 && time >= 0)
V_DrawScaledPatch(tilex + 2, tiley - 2, 0, kp_eggnum[time]);
}
}
thiskeyx = tilex*FRACUNIT;
thiskeyy = tiley*FRACUNIT;
}
if (radius != 0)
{
angle_t ang = (FixedAngle(
keyholdrotation
) >> ANGLETOFINESHIFT) & FINEMASK;
thiskeyx += FixedMul(radius, FINESINE(ang));
thiskeyy -= FixedMul(radius, FINECOSINE(ang));
}
V_DrawFixedPatch(thiskeyx, thiskeyy, FRACUNIT, 0, key, NULL);
}
}
// The final Chao Key on the stack
{
UINT8 *lastkeycolormap = NULL;
if (gamedata->chaokeys <= keysbeingused)
{
// Greyed out if there's going to be none left
lastkeycolormap = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_MENUCACHE);
}
V_DrawFixedPatch(keyx, keyy, FRACUNIT, 0, key, lastkeycolormap);
// Extra glowverlay if you can use a Chao Key
if (keysbeingused == 0 && M_CanKeyHiliTile())
{
INT32 trans = (((challengesmenu.ticker/5) % 6) - 3);
if (trans)
{
trans = ((trans < 0)
? (10 + trans)
: (10 - trans)
) << V_ALPHASHIFT;
V_DrawFixedPatch(keyx, keyy, FRACUNIT, trans, key,
R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_MENUCACHE)
);
}
}
}
}
void M_DrawChallenges(void)
{
INT32 x = currentMenu->x, explodex, selectx = 0, selecty = 0;
INT32 y;
INT16 i, j;
const char *str;
INT16 offset;
{
#define questionslow 4 // slows down the scroll by this factor
#define questionloop (questionslow*100) // modulo
INT32 questionoffset;
double questionoffset_f;
patch_t *bg = W_CachePatchName("BGUNLCKG", PU_CACHE);
patch_t *qm = W_CachePatchName("BGUNLSC", PU_CACHE);
questionoffset_f = fmod(challengesmenu.ticker + FixedToFloat(rendertimefrac), questionloop);
questionoffset = floor(questionoffset_f);
// Background gradient
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL);
// Scrolling question mark overlay
V_DrawFixedPatch(
-((160 + questionoffset)*FRACUNIT)/questionslow,
-(4*FRACUNIT) - (245*(FixedDiv((questionloop - questionoffset)*FRACUNIT, questionloop*FRACUNIT))),
FRACUNIT,
V_MODULATE,
qm,
NULL);
#undef questionslow
#undef questionloop
}
// Do underlay for everything else early so the bottom of the reticule doesn't get shaded over.
if (challengesmenu.currentunlock < MAXUNLOCKABLES)
{
y = 120;
V_DrawScaledPatch(0, y,
(10-challengetransparentstrength)<<V_ALPHASHIFT,
W_CachePatchName("MENUHINT", PU_CACHE));
V_DrawFadeFill(0, y+27, BASEVIDWIDTH, BASEVIDHEIGHT - (y+27), 0, 31, challengetransparentstrength);
}
if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL)
{
V_DrawCenteredMenuString(x, y, V_REDMAP, "No challenges available!?");
goto challengedesc;
}
UINT8 *flashmap = R_GetTranslationColormap(TC_DEFAULT, M_GetCvPlayerColor(0), GTC_MENUCACHE);
y = currentMenu->y;
V_DrawFadeFill(0, y-2, BASEVIDWIDTH, (challengesgridstep * CHALLENGEGRIDHEIGHT) + 2, 0, 31, challengetransparentstrength);
x -= (challengesgridstep-1);
x += challengesmenu.offset;
x += Easing_OutQuad(M_DueFrac(challengesmenu.move.start, 4), challengesgridstep * challengesmenu.move.dist, 0);
if (challengegridloops)
{
if (!challengesmenu.col && challengesmenu.hilix)
x -= gamedata->challengegridwidth*challengesgridstep;
i = challengesmenu.col + challengesmenu.focusx;
explodex = x - (i*challengesgridstep);
while (x < BASEVIDWIDTH-challengesgridstep)
{
i = (i + 1) % gamedata->challengegridwidth;
x += challengesgridstep;
}
}
else
{
if (gamedata->challengegridwidth & 1)
x += (challengesgridstep/2);
i = gamedata->challengegridwidth-1;
explodex = x - (i*challengesgridstep)/2;
x += (i*challengesgridstep)/2;
}
selectx = explodex + (challengesmenu.hilix*challengesgridstep);
selecty = currentMenu->y + (challengesmenu.hiliy*challengesgridstep);
while (i >= 0 && x >= -(challengesgridstep*2))
{
y = currentMenu->y-challengesgridstep;
for (j = 0; j < CHALLENGEGRIDHEIGHT; j++)
{
y += challengesgridstep;
if (challengesmenu.extradata[(i * CHALLENGEGRIDHEIGHT) + j].flags & CHE_DONTDRAW)
{
continue;
}
if (x == selectx && j == challengesmenu.hiliy)
{
continue;
}
M_DrawChallengeTile(i, j, x, y, flashmap, false);
}
x -= challengesgridstep;
i--;
if (challengegridloops && i < 0)
{
i = (i + gamedata->challengegridwidth)
% gamedata->challengegridwidth;
}
}
if (challengesmenu.fade)
V_DrawFadeScreen(31, challengesmenu.fade);
M_DrawChallengeTile(
challengesmenu.hilix,
challengesmenu.hiliy,
selectx,
selecty,
flashmap,
true);
M_DrawCharSelectExplosions(false, explodex, currentMenu->y);
challengedesc:
// Name bar
{
y = 120;
if (challengesmenu.currentunlock < MAXUNLOCKABLES)
{
str = unlockables[challengesmenu.currentunlock].name;
if (!gamedata->unlocked[challengesmenu.currentunlock])
{
str = "???"; //M_CreateSecretMenuOption(str);
}
offset = V_LSTitleLowStringWidth(str, 0) / 2;
V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str);
}
}
// Wings
{
const INT32 endy = 18, endlen = 38;
patch_t *endwing = W_CachePatchName("K_BOSB01", PU_CACHE);
V_DrawFill(0, endy, endlen, 11, 24);
V_DrawFixedPatch(endlen*FRACUNIT, endy*FRACUNIT, FRACUNIT, V_FLIP, endwing, NULL);
V_DrawFill(BASEVIDWIDTH - endlen, endy, endlen, 11, 24);
V_DrawFixedPatch((BASEVIDWIDTH - endlen)*FRACUNIT, endy*FRACUNIT, FRACUNIT, 0, endwing, NULL);
}
// Percentage
{
patch_t *medal = W_CachePatchName(
va("UN_MDL%c", '0' + challengesmenu.unlockcount[CMC_MEDALID]),
PU_CACHE
);
fixed_t medalchopy = 1;
for (i = CMC_MEDALBLANK; i <= CMC_MEDALFILLED; i++)
{
if (challengesmenu.unlockcount[i] == 0)
continue;
V_SetClipRect(
0,
medalchopy << FRACBITS,
BASEVIDWIDTH << FRACBITS,
(medalchopy + challengesmenu.unlockcount[i]) << FRACBITS,
0
);
UINT8 *medalcolormap = NULL;
if (i == CMC_MEDALBLANK)
{
medalcolormap = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_MENUCACHE);
}
else if (challengesmenu.unlockcount[CMC_MEDALID] == 0)
{
medalcolormap = R_GetTranslationColormap(TC_DEFAULT, M_GetCvPlayerColor(0), GTC_MENUCACHE);
}
V_DrawFixedPatch((BASEVIDWIDTH - 31)*FRACUNIT, 1*FRACUNIT, FRACUNIT, 0, medal, medalcolormap);
V_ClearClipRect();
medalchopy += challengesmenu.unlockcount[i];
}
INT32 textx = BASEVIDWIDTH - 21, texty = 20-challengesmenu.unlockcount[CMC_ANIM];
UINT8 numbers[3];
numbers[0] = ((challengesmenu.unlockcount[CMC_PERCENT] / 100) % 10);
numbers[1] = ((challengesmenu.unlockcount[CMC_PERCENT] / 10) % 10);
numbers[2] = (challengesmenu.unlockcount[CMC_PERCENT] % 10);
patch_t *percent = W_CachePatchName("K_SPDML1", PU_CACHE);
V_DrawScaledPatch(textx + 2, texty, 0, percent);
i = 3;
while (i)
{
i--;
textx -= 6;
V_DrawScaledPatch(textx, texty, 0, kp_facenum[numbers[i]]);
}
}
// Chao Key information
M_DrawChallengeKeys(selectx, selecty);
// Derived from M_DrawCharSelectPreview
x = 40;
y = BASEVIDHEIGHT-16;
// Unlock preview
M_DrawChallengePreview(x, y);
// Conditions for unlock
i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy;
if (challengesmenu.unlockcondition != NULL
&& challengesmenu.currentunlock < MAXUNLOCKABLES
&& ((gamedata->unlocked[challengesmenu.currentunlock] == true)
|| ((challengesmenu.extradata != NULL)
&& (challengesmenu.extradata[i].flags & CHE_HINT))
)
)
{
V_DrawCenteredThinString(BASEVIDWIDTH/2, 120 + 32, 0, challengesmenu.unlockcondition);
}
}
#undef challengetransparentstrength
#undef challengesgridstep
// Statistics menu
#define STATSSTEP 10
static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y)
{
UINT8 lasttype = UINT8_MAX, curtype;
// M_GetLevelEmblems is ONE-indexed, urgh
mapnum++;
emblem_t *emblem = M_GetLevelEmblems(mapnum);
boolean hasmedals = (emblem != NULL);
while (emblem)
{
switch (emblem->type)
{
case ET_TIME:
curtype = 1;
break;
case ET_GLOBAL:
{
if (emblem->flags & GE_NOTMEDAL)
{
emblem = M_GetLevelEmblems(-1);
continue;
}
curtype = 2;
break;
}
case ET_MAP:
{
if (((emblem->flags & ME_ENCORE) && !M_SecretUnlocked(SECRET_ENCORE, true))
|| ((emblem->flags & ME_SPBATTACK) && !M_SecretUnlocked(SECRET_SPBATTACK, true)))
{
emblem = M_GetLevelEmblems(-1);
continue;
}
curtype = 0;
break;
}
default:
curtype = 0;
break;
}
// Shift over if emblem is of a different discipline
if (lasttype != UINT8_MAX && lasttype != curtype)
x -= 4;
lasttype = curtype;
if (gamedata->collected[emblem-emblemlocations])
V_DrawMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE),
R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE));
else
V_DrawScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_CACHE));
emblem = M_GetLevelEmblems(-1);
x -= 8;
}
// Undo offset
mapnum--;
if (hasmedals)
x -= 4;
if (mapheaderinfo[mapnum]->cache_spraycan < gamedata->numspraycans)
{
UINT16 col = gamedata->spraycans[mapheaderinfo[mapnum]->cache_spraycan].col;
if (col < numskincolors)
{
V_DrawMappedPatch(x, y, 0, W_CachePatchName("GOTCAN", PU_CACHE),
R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE));
//V_DrawRightAlignedThinString(x - 2, y, 0, skincolors[col].name);
}
x -= 8;
}
if (mapheaderinfo[mapnum]->records.mapvisited & MV_MYSTICMELODY)
{
V_DrawScaledPatch(x, y, 0, W_CachePatchName("GOTMEL", PU_CACHE));
x -= 8;
}
}
static void M_DrawStatsMaps(void)
{
INT32 y = 70, i;
INT16 mnum;
boolean dotopname = true, dobottomarrow = (statisticsmenu.location < statisticsmenu.maxscroll);
INT32 location = statisticsmenu.location;
tic_t besttime = 0;
if (!statisticsmenu.maplist)
{
V_DrawCenteredThinString(BASEVIDWIDTH/2, 70, 0, "No maps!?");
return;
}
INT32 mapsunfinished = 0;
V_DrawThinString(30, 60, 0, va("x %d/%d", statisticsmenu.gotmedals, statisticsmenu.nummedals));
V_DrawMappedPatch(20, 60, 0, W_CachePatchName("GOTITA", PU_CACHE),
R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GOLD, GTC_MENUCACHE));
INT32 medalspos = BASEVIDWIDTH - 20;
boolean timeattack[3];
timeattack[0] = M_SecretUnlocked(SECRET_TIMEATTACK, true);
timeattack[1] = M_SecretUnlocked(SECRET_PRISONBREAK, true);
timeattack[2] = M_SecretUnlocked(SECRET_SPECIALATTACK, true);
if (timeattack[0] || timeattack[1] || timeattack[2])
{
medalspos -= 64;
for (i = 0; i < nummapheaders; i++)
{
// Check for no visibility
if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINMENU)))
continue;
// Has to be accessible via time attack
if (!(mapheaderinfo[i]->typeoflevel & (TOL_RACE|TOL_BATTLE|TOL_SPECIAL|TOL_VERSUS)))
continue;
if (mapheaderinfo[i]->records.timeattack.time <= 0)
{
mapsunfinished++;
continue;
}
besttime += mapheaderinfo[i]->records.timeattack.time;
}
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 60, 0,
va(
"Combined time: %c%i:%02i:%02i.%02i (%s)",
(mapsunfinished ? '\x85' : '\x80'),
G_TicsToHours(besttime),
G_TicsToMinutes(besttime, false),
G_TicsToSeconds(besttime),
G_TicsToCentiseconds(besttime),
(mapsunfinished ? "incomplete" : "complete")
)
);
}
if (location)
V_DrawMenuString(10, 80-(skullAnimCounter/5),
highlightflags, "\x1A"); // up arrow
i = -1;
while ((mnum = statisticsmenu.maplist[++i]) != NEXTMAP_INVALID)
{
if (location)
{
--location;
continue;
}
if (dotopname || mnum >= nummapheaders)
{
if (mnum >= nummapheaders)
{
mnum = statisticsmenu.maplist[1+i];
if (mnum >= nummapheaders)
mnum = statisticsmenu.maplist[i-1];
}
if (mnum < nummapheaders)
{
const char *str;
if (mapheaderinfo[mnum]->typeoflevel & TOL_TUTORIAL)
str = "TUTORIAL MODE";
else if (mapheaderinfo[mnum]->cup)
str = va("%s CUP", mapheaderinfo[mnum]->cup->realname);
else
str = "LOST & FOUND";
V_DrawThinString(20, y, highlightflags, str);
}
if (dotopname)
{
V_DrawRightAlignedThinString(medalspos, y, highlightflags, "MEDALS");
if (timeattack[0] || timeattack[1] || timeattack[2])
V_DrawRightAlignedThinString((BASEVIDWIDTH-20), y, highlightflags, "TIME");
dotopname = false;
}
y += STATSSTEP;
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
continue;
}
V_DrawFadeFill(24, y + 5, (BASEVIDWIDTH - 24) - 24, 3, 0, 31, 8 - (i & 1)*2);
if (!(mapheaderinfo[mnum]->menuflags & LF2_NOTIMEATTACK)
&& (
(timeattack[0] && (mapheaderinfo[mnum]->typeoflevel & TOL_RACE))
|| (timeattack[1] && (mapheaderinfo[mnum]->typeoflevel & TOL_BATTLE))
|| (timeattack[2] && (mapheaderinfo[mnum]->typeoflevel & (TOL_SPECIAL|TOL_VERSUS)))
)
)
{
besttime = mapheaderinfo[mnum]->records.timeattack.time;
if (besttime)
{
V_DrawRightAlignedString((BASEVIDWIDTH-24), y+1, 0,
va("%02d'%02d\"%02d",
G_TicsToMinutes(besttime, true),
G_TicsToSeconds(besttime),
G_TicsToCentiseconds(besttime)
)
);
}
else
{
V_DrawRightAlignedString((BASEVIDWIDTH-24), y+1, V_GRAYMAP, "--'--\"--");
}
}
M_DrawMapMedals(mnum, medalspos - 8, y);
if (mapheaderinfo[mnum]->menuttl[0])
{
V_DrawThinString(24, y, V_FORCEUPPERCASE, mapheaderinfo[mnum]->menuttl);
}
else
{
char *title = G_BuildMapTitle(mnum+1);
V_DrawThinString(24, y, V_FORCEUPPERCASE, title);
Z_Free(title);
}
y += STATSSTEP;
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
}
if (location)
--location;
if (statisticsmenu.numextramedals == 0)
goto bottomarrow;
// Extra Emblem headers
for (i = 0; i < 2; ++i)
{
if (i == 1)
{
V_DrawThinString(20, y, highlightflags, "EXTRA MEDALS");
if (location)
{
y += STATSSTEP;
location++;
}
}
if (location)
{
--location;
continue;
}
y += STATSSTEP;
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
}
// Extra Emblems
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (unlockables[i].type != SECRET_EXTRAMEDAL)
{
continue;
}
if (location)
{
--location;
continue;
}
{
if (gamedata->unlocked[i])
{
UINT16 color = min(unlockables[i].color, numskincolors-1);
if (!color)
color = SKINCOLOR_GOLD;
V_DrawMappedPatch(291, y+1, 0, W_CachePatchName("GOTITA", PU_CACHE),
R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE));
}
else
{
V_DrawScaledPatch(291, y+1, 0, W_CachePatchName("NEEDIT", PU_CACHE));
}
V_DrawThinString(24, y, 0, va("%s", unlockables[i].name));
}
y += STATSSTEP;
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
}
bottomarrow:
if (dobottomarrow)
V_DrawMenuString(10, BASEVIDHEIGHT-20 + (skullAnimCounter/5),
highlightflags, "\x1B"); // down arrow
}
#undef STATSSTEP
#define STATSSTEP 18
static void M_DrawStatsChars(void)
{
INT32 y = 80, i, j;
INT16 skin;
boolean dobottomarrow = (statisticsmenu.location < statisticsmenu.maxscroll);
INT32 location = statisticsmenu.location;
if (!statisticsmenu.maplist || !statisticsmenu.nummaps)
{
V_DrawCenteredThinString(BASEVIDWIDTH/2, 70, 0, "No chars!?");
return;
}
if (location)
V_DrawMenuString(10, y-(skullAnimCounter/5),
highlightflags, "\x1A"); // up arrow
i = -1;
V_DrawThinString(20, y - 10, highlightflags, "CHARACTER");
V_DrawRightAlignedThinString(BASEVIDWIDTH/2 + 34, y - 10, highlightflags, "WINS");
while ((skin = statisticsmenu.maplist[++i]) < numskins)
{
if (location)
{
--location;
continue;
}
{
UINT8 *colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
M_DrawCharacterIconAndEngine(24, y, skin, colormap, false);
}
V_DrawThinString(24+32+2, y+3, 0, skins[skin].realname);
V_DrawRightAlignedThinString(BASEVIDWIDTH/2 + 30, y+3, 0, va("%d", skins[skin].records.wins));
y += STATSSTEP;
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
}
bottomarrow:
if (dobottomarrow)
V_DrawMenuString(10, BASEVIDHEIGHT-20 + (skullAnimCounter/5),
highlightflags, "\x1B"); // down arrow
UINT32 x = BASEVIDWIDTH - 20 - 90;
y = 88;
V_DrawCenteredThinString(x + 45, y - 10, highlightflags, "HEATMAP");
V_DrawFadeFill(x, y, 91, 91, 0, 31, 8); // challengetransparentstrength
V_DrawFill(x+30, y+1, 1, 89, 0);
V_DrawFill(x+60, y+1, 1, 89, 0);
V_DrawFill(x+1, y+30, 89, 1, 0);
V_DrawFill(x+1, y+60, 89, 1, 0);
x++;
y++;
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9; j++)
{
if (statisticsmenu.statgridplayed[i][j] == 0)
continue;
V_DrawFill(
x + (i * 10),
y + (j * 10),
9,
9,
31 - ((statisticsmenu.statgridplayed[i][j] - 1) * 32) / FRACUNIT
);
}
}
}
#undef STATSSTEP
#define STATSSTEP 21
static void M_DrawStatsGP(void)
{
INT32 y = 80, i, x, j, endj;
INT16 id;
boolean dobottomarrow = (statisticsmenu.location < statisticsmenu.maxscroll);
INT32 location = statisticsmenu.location;
if (!statisticsmenu.maplist || !statisticsmenu.nummaps)
{
V_DrawCenteredThinString(BASEVIDWIDTH/2, 70, 0, "No cups!?");
return;
}
if (location)
V_DrawMenuString(10, y-(skullAnimCounter/5),
highlightflags, "\x1A"); // up arrow
const INT32 width = 53;
endj = KARTSPEED_NORMAL;
if (M_SecretUnlocked(SECRET_HARDSPEED, true))
{
endj = M_SecretUnlocked(SECRET_MASTERMODE, true)
? KARTGP_MASTER
: KARTSPEED_HARD;
}
const INT32 h = (21 * min(5, statisticsmenu.nummaps)) - 1;
x = 7 + BASEVIDWIDTH - 20 - width;
for (j = endj; j >= KARTSPEED_EASY; j--, x -= width)
{
if (j == KARTSPEED_EASY || !gamedata->everseenspecial)
{
V_DrawFadeFill(x + 6, y + 1, width - (12 + 1), h, 0, 31, 6 + (j & 1)*2);
V_DrawCenteredThinString(x + 19 + 7, y - 10, highlightflags|V_FORCEUPPERCASE, gpdifficulty_cons_t[j].strvalue);
x += (12 + 1);
}
else
{
V_DrawFadeFill(x - 7, y + 1, width, h, 0, 31, 6 + (j & 1)*2);
V_DrawCenteredThinString(x + 19, y - 10, highlightflags|V_FORCEUPPERCASE, gpdifficulty_cons_t[j].strvalue);
}
}
i = -1;
V_DrawThinString(20, y - 10, highlightflags, "CUP");
cupheader_t *cup = kartcupheaders;
while ((id = statisticsmenu.maplist[++i]) < numkartcupheaders)
{
if (location)
{
--location;
continue;
}
// If we have ANY sort of sorting other than instantiation, this won't work
while (cup && cup->id != id)
{
cup = cup->next;
}
if (!cup)
{
goto bottomarrow;
}
V_DrawFill(24, y+1, 21, 20, 31);
V_DrawScaledPatch(24-1, y, 0, W_CachePatchName(cup->icon, PU_CACHE));
V_DrawScaledPatch(24-1, y, 0, W_CachePatchName("CUPBOX", PU_CACHE));
V_DrawThinString(24+21+2, y + 7, 0, cup->realname);
x = 7 + BASEVIDWIDTH - 20 - width;
for (j = endj; j >= KARTSPEED_EASY; j--)
{
x -= (M_DrawCupWinData(x, y + 5, cup, j, false, true) + 2);
}
y += STATSSTEP;
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
}
bottomarrow:
if (dobottomarrow)
V_DrawMenuString(10, BASEVIDHEIGHT-20 + (skullAnimCounter/5),
highlightflags, "\x1B"); // down arrow
}
#undef STATSSTEP
void M_DrawStatistics(void)
{
char beststr[256];
tic_t besttime = 0;
{
const char *pagename = NULL;
INT32 pagenamewidth = 0;
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL);
switch (statisticsmenu.page)
{
case statisticspage_gp:
{
pagename = gamedata->everseenspecial
? "GRAND PRIX & EMERALDS"
: "GRAND PRIX";
M_DrawStatsGP();
break;
}
case statisticspage_maps:
{
pagename = "COURSES & MEDALS";
M_DrawStatsMaps();
break;
}
case statisticspage_chars:
{
pagename = "CHARACTERS & ENGINE CLASSES";
M_DrawStatsChars();
break;
}
default:
break;
}
if (pagename)
{
pagenamewidth = V_ThinStringWidth(pagename, 0);
V_DrawThinString((BASEVIDWIDTH - pagenamewidth)/2, 12, 0, pagename);
}
V_DrawMenuString((BASEVIDWIDTH - pagenamewidth)/2 - 10 - (skullAnimCounter/5), 12,
0, "\x1C"); // left arrow
V_DrawMenuString((BASEVIDWIDTH + pagenamewidth)/2 + 2 + (skullAnimCounter/5), 12,
0, "\x1D"); // right arrow
}
beststr[0] = 0;
V_DrawThinString(20, 30, highlightflags, "Total Play Time:");
besttime = G_TicsToHours(gamedata->totalplaytime);
if (besttime)
{
if (besttime >= 24)
{
strcat(beststr, va("%u day%s, ", besttime/24, (besttime < 48 ? "" : "s")));
besttime %= 24;
}
strcat(beststr, va("%u hour%s, ", besttime, (besttime == 1 ? "" : "s")));
}
besttime = G_TicsToMinutes(gamedata->totalplaytime, false);
if (besttime)
{
strcat(beststr, va("%u minute%s, ", besttime, (besttime == 1 ? "" : "s")));
}
besttime = G_TicsToSeconds(gamedata->totalplaytime);
strcat(beststr, va("%i second%s", besttime, (besttime == 1 ? "" : "s")));
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 30, 0, beststr);
beststr[0] = 0;
V_DrawThinString(20, 40, highlightflags, "Total Rings:");
if (gamedata->totalrings > GDMAX_RINGS)
{
sprintf(beststr, "%c999,999,999+", '\x82');
}
else if (gamedata->totalrings >= 1000000)
{
sprintf(beststr, "%u,%03u,%03u", (gamedata->totalrings/1000000), (gamedata->totalrings/1000)%1000, (gamedata->totalrings%1000));
}
else if (gamedata->totalrings >= 1000)
{
sprintf(beststr, "%u,%03u", (gamedata->totalrings/1000), (gamedata->totalrings%1000));
}
else
{
sprintf(beststr, "%u", gamedata->totalrings);
}
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 40, 0, va("%s collected", beststr));
beststr[0] = 0;
V_DrawThinString(20, 50, highlightflags, "Total Rounds:");
strcat(beststr, va("%u Race", gamedata->roundsplayed[GDGT_RACE]));
if (gamedata->roundsplayed[GDGT_PRISONS] > 0)
{
strcat(beststr, va(", %u Prisons", gamedata->roundsplayed[GDGT_PRISONS]));
}
strcat(beststr, va(", %u Battle", gamedata->roundsplayed[GDGT_BATTLE]));
if (gamedata->roundsplayed[GDGT_SPECIAL] > 0)
{
strcat(beststr, va(", %u Special", gamedata->roundsplayed[GDGT_SPECIAL]));
}
if (gamedata->roundsplayed[GDGT_CUSTOM] > 0)
{
strcat(beststr, va(", %u Custom", gamedata->roundsplayed[GDGT_CUSTOM]));
}
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 50, 0, beststr);
}
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 (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((M_UseAlternateTitleScreen() ? "MTSJUMPR1" : "MTSBUMPR1"), PU_CACHE);
V_DrawScaledPatch(x-(SHORT(bumper->width)), (BASEVIDHEIGHT-8)-(SHORT(bumper->height)), 0, bumper);
}
void M_DrawSoundTest(void)
{
const UINT8 pid = 0;
INT32 x, y, i, cursorx = 0;
INT32 titleoffset = 0, titlewidth;
const char *titletext;
patch_t *btn = W_CachePatchName("STER_BTN", PU_CACHE);
const char *tune = S_SoundTestTune(0);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("STER_BG", PU_CACHE), NULL);
x = 24;
y = 18;
V_SetClipRect(
x << FRACBITS, y << FRACBITS,
272 << FRACBITS, 106 << FRACBITS,
0
);
y += 32;
if (soundtest.current != NULL)
{
if (soundtest.current->sequence.map < nummapheaders)
{
K_DrawMapThumbnail(
0, 0,
BASEVIDWIDTH<<FRACBITS,
V_20TRANS|V_ADD,
soundtest.current->sequence.map,
NULL);
V_DrawFixedPatch(
0, 0,
FRACUNIT,
V_60TRANS|V_SUBTRACT,
W_CachePatchName("STER_DOT", PU_CACHE),
NULL
);
}
titletext = soundtest.current->title;
if (!titletext)
titletext = "Untitled"; // Har har.
V_DrawThinString(x+1, y, 0, titletext);
if (soundtest.current->numtracks > 1)
V_DrawThinString(x+1, (y += 10), 0, va("Track %c", 'A'+soundtest.currenttrack));
if (soundtest.current->author)
V_DrawThinString(x+1, (y += 10), 0, soundtest.current->author);
if (soundtest.current->source)
V_DrawThinString(x+1, (y += 10), 0, soundtest.current->source);
if (soundtest.current->composers)
V_DrawThinString(x+1, (y += 10), 0, soundtest.current->composers);
}
else
{
const char *sfxstr = (cv_soundtest.value) ? S_sfx[cv_soundtest.value].name : "N/A";
titletext = "Sound Test";
V_DrawThinString(x+1, y, 0, "Track ");
V_DrawThinString(
x+1 + V_ThinStringWidth("Track ", 0),
y,
0,
va("%04X - %s", cv_soundtest.value, sfxstr)
);
}
titletext = va("%s - ", titletext);
titlewidth = V_LSTitleHighStringWidth(titletext, 0);
titleoffset = (-soundtest.menutick) % titlewidth;
while (titleoffset < 272)
{
V_DrawLSTitleHighString(x + titleoffset, 18+1, 0, titletext);
titleoffset += titlewidth;
}
{
UINT32 currenttime = min(Music_Elapsed(tune), Music_TotalDuration(tune));
V_DrawRightAlignedThinString(x + 272-1, 18+32, 0,
va("%02u:%02u",
G_TicsToMinutes(currenttime, true),
G_TicsToSeconds(currenttime)
)
);
}
if ((soundtest.playing && soundtest.current)
&& (soundtest.current->basenoloop[soundtest.currenttrack] == true
|| soundtest.autosequence == true))
{
UINT32 exittime = Music_TotalDuration(tune);
V_DrawRightAlignedThinString(x + 272-1, 18+32+10, 0,
va("%02u:%02u",
G_TicsToMinutes(exittime, true),
G_TicsToSeconds(exittime)
)
);
}
V_ClearClipRect();
x = currentMenu->x;
for (i = 0; i < currentMenu->numitems; i++)
{
if (currentMenu->menuitems[i].status == IT_SPACE)
{
if (currentMenu->menuitems[i].mvar2 != 0)
{
x = currentMenu->menuitems[i].mvar2;
}
x += currentMenu->menuitems[i].mvar1;
continue;
}
y = currentMenu->y;
if (i == itemOn)
{
cursorx = x + 13;
}
if (currentMenu->menuitems[i].tooltip)
{
V_SetClipRect(
x << FRACBITS, y << FRACBITS,
27 << FRACBITS, 22 << FRACBITS,
0
);
// Special cases
if (currentMenu->menuitems[i].mvar2 == stereospecial_back) // back
{
if (!soundtest.justopened && M_MenuBackHeld(pid))
{
y = currentMenu->y + 7;
}
}
// The following are springlocks.
else if (currentMenu->menuitems[i].mvar2 == stereospecial_pause) // pause
{
if (Music_Paused(tune) == true)
y = currentMenu->y + 6;
}
else if (currentMenu->menuitems[i].mvar2 == stereospecial_play) // play
{
if (soundtest.playing == true && Music_Paused(tune) == false)
y = currentMenu->y + 6;
}
else if (currentMenu->menuitems[i].mvar2 == stereospecial_seq) // seq
{
if (soundtest.autosequence == true)
y = currentMenu->y + 6;
}
else if (currentMenu->menuitems[i].mvar2 == stereospecial_shf) // shf
{
if (soundtest.shuffle == true)
y = currentMenu->y + 6;
}
// Button is being pressed
if (i == itemOn && !soundtest.justopened && M_MenuConfirmHeld(pid))
{
y = currentMenu->y + 7;
}
// Button itself
V_DrawFixedPatch(x << FRACBITS, y << FRACBITS, FRACUNIT, 0, btn, NULL);
// Icon
V_DrawFixedPatch(x << FRACBITS, y << FRACBITS,
FRACUNIT, 0,
W_CachePatchName(currentMenu->menuitems[i].tooltip, PU_CACHE),
NULL
);
// Text
V_DrawCenteredThinString(x + 13, y + 1, 0, currentMenu->menuitems[i].text);
V_ClearClipRect();
V_DrawFill(x+2, currentMenu->y + 22, 23, 1, 30);
}
else if (currentMenu->menuitems[i].mvar2 == stereospecial_vol) // Vol
{
consvar_t *voltoadjust = M_GetSoundTestVolumeCvar();
INT32 j = 0, vol = 0;
const INT32 barheight = 22;
patch_t *knob = NULL;
INT32 knobflags = 0;
if (i == itemOn)
{
if ((menucmd[pid].dpad_ud < 0 && (soundtest.menutick & 2)) || M_MenuConfirmPressed(pid))
{
knob = W_CachePatchName("STER_KNT", PU_CACHE);
knobflags = V_FLIP;
j = 24;
}
else if (menucmd[pid].dpad_ud > 0 && (soundtest.menutick & 2))
{
knob = W_CachePatchName("STER_KNT", PU_CACHE);
}
}
if (knob == NULL)
knob = W_CachePatchName("STER_KNB", PU_CACHE);
V_DrawFixedPatch((x+1+j) << FRACBITS, y << FRACBITS,
FRACUNIT, knobflags,
knob,
NULL
);
V_DrawFill(x+1+24, y+1, 5, barheight, 30);
if (voltoadjust != NULL)
{
vol = (barheight*voltoadjust->value)/(MAX_SOUND_VOLUME*3);
}
for (j = 0; j <= barheight/3; j++)
{
UINT8 col = 130;
if (j == 0)
{
continue;
}
if (j > vol)
{
col = 20;
}
else if (j > (barheight/3)-2)
{
col = 34;
}
V_DrawFill(x+1+24+2, y+1 + (barheight-(j*3)), 1, 2, col);
}
x += 5;
}
else if (currentMenu->menuitems[i].mvar2 == stereospecial_track) // Track
{
if (i == itemOn)
{
if (menucmd[pid].dpad_ud < 0 || M_MenuConfirmPressed(pid))
{
y--;
}
else if (menucmd[pid].dpad_ud > 0)
{
y++;
}
}
V_DrawFixedPatch(x << FRACBITS, (y-1) << FRACBITS,
FRACUNIT, 0,
W_CachePatchName("STER_WH0", PU_CACHE),
NULL
);
}
else
{
V_DrawCenteredThinString(x + 13, y + 1, 0, currentMenu->menuitems[i].text);
}
x += 25;
}
V_DrawMenuString(cursorx - 4, currentMenu->y - 8 - (skullAnimCounter/5),
V_SNAPTOTOP|highlightflags, "\x1B"); // up arrow
}
#ifdef HAVE_DISCORDRPC
void M_DrawDiscordRequests(void)
{
discordRequest_t *curRequest = discordRequestList;
UINT8 *colormap;
patch_t *hand = NULL;
const char *wantText = "...would like to join!";
const char *acceptText = "Accept" ;
const char *declineText = "Decline";
INT32 x = 100;
INT32 y = 133;
INT32 slide = 0;
INT32 maxYSlide = 18;
if (discordrequestmenu.confirmDelay > 0)
{
if (discordrequestmenu.confirmAccept == true)
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREEN, GTC_MENUCACHE);
hand = W_CachePatchName("K_LAPH02", PU_CACHE);
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE);
hand = W_CachePatchName("K_LAPH03", PU_CACHE);
}
slide = discordrequestmenu.confirmLength - discordrequestmenu.confirmDelay;
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREY, GTC_MENUCACHE);
}
V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT, FRACUNIT, 0, W_CachePatchName("K_LAPE01", PU_CACHE), colormap);
if (hand != NULL)
{
fixed_t handoffset = (4 - abs((signed)(skullAnimCounter - 4))) * FRACUNIT;
V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT + handoffset, FRACUNIT, 0, hand, NULL);
}
K_DrawSticker(x + (slide * 32), y - 2, V_ThinStringWidth(M_GetDiscordName(curRequest), 0), 0, false);
V_DrawThinString(x + (slide * 32), y - 1, V_YELLOWMAP, M_GetDiscordName(curRequest));
K_DrawSticker(x, y + 12, V_ThinStringWidth(wantText, 0), 0, true);
V_DrawThinString(x, y + 10, 0, wantText);
INT32 confirmButtonWidth = SHORT(kp_button_a[1][0]->width);
INT32 declineButtonWidth = SHORT(kp_button_b[1][0]->width);
INT32 altDeclineButtonWidth = SHORT(kp_button_x[1][0]->width);
INT32 acceptTextWidth = V_ThinStringWidth(acceptText, 0);
INT32 declineTextWidth = V_ThinStringWidth(declineText, 0);
INT32 stickerWidth = (confirmButtonWidth + declineButtonWidth + altDeclineButtonWidth + acceptTextWidth + declineTextWidth);
K_DrawSticker(x, y + 26, stickerWidth, 0, true);
K_drawButtonAnim(x, y + 22, V_SNAPTORIGHT, kp_button_a[1], discordrequestmenu.ticker);
INT32 xoffs = confirmButtonWidth;
V_DrawThinString((x + xoffs), y + 24, 0, acceptText);
xoffs += acceptTextWidth;
K_drawButtonAnim((x + xoffs), y + 22, V_SNAPTORIGHT, kp_button_b[1], discordrequestmenu.ticker);
xoffs += declineButtonWidth;
K_drawButtonAnim((x + xoffs), y + 22, V_SNAPTORIGHT, kp_button_x[1], discordrequestmenu.ticker);
xoffs += altDeclineButtonWidth;
V_DrawThinString((x + xoffs), y + 24, 0, declineText);
y -= 18;
while (curRequest->next != NULL)
{
INT32 ySlide = min(slide * 4, maxYSlide);
curRequest = curRequest->next;
const char *discordname = M_GetDiscordName(curRequest);
K_DrawSticker(x, y - 1 + ySlide, V_ThinStringWidth(discordname, 0), 0, false);
V_DrawThinString(x, y + ySlide, 0, discordname);
y -= 12;
maxYSlide = 12;
}
}
#endif