/// \file k_menudraw.c /// \brief SRB2Kart's menu drawer functions #ifdef __GNUC__ #include #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 // 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<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<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<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<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<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<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 = ¤tMenu->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)<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)<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)<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)<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<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)<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)<width -4 + (minibutt->width/2))<height)) + soffy)*FRACUNIT , FRACUNIT, V_ADD, drawp, NULL); soffy += scrollp[mpmenu.room]->height; } // Draw buttons: V_DrawFixedPatch(160<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); // bgTextScroll 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)<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)<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)<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<width) < BASEVIDWIDTH) { V_DrawFixedPatch(arrxpos<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))< (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)<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)< 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)<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)<type == SECRET_SPBATTACK) { V_DrawFixedPatch((x+40-25)<type == SECRET_HARDSPEED) { V_DrawFixedPatch((x+40-25)<type == SECRET_MASTERMODE) { V_DrawFixedPatch((x+40-25)<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)<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<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