// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file k_hud.c /// \brief HUD drawing functions exclusive to Kart #include <algorithm> #include <array> #include <vector> #include <deque> #include "v_draw.hpp" #include "k_hud.h" #include "k_kart.h" #include "k_battle.h" #include "k_grandprix.h" #include "k_specialstage.h" #include "k_objects.h" #include "k_boss.h" #include "k_color.h" #include "k_director.h" #include "screen.h" #include "doomtype.h" #include "doomdef.h" #include "hu_stuff.h" #include "d_netcmd.h" #include "v_video.h" #include "r_draw.h" #include "st_stuff.h" #include "lua_hud.h" #include "doomstat.h" #include "d_clisrv.h" #include "g_game.h" #include "p_local.h" #include "z_zone.h" #include "m_cond.h" #include "r_main.h" #include "s_sound.h" #include "r_things.h" #include "r_fps.h" #include "m_random.h" #include "k_roulette.h" #include "k_bot.h" #include "k_rank.h" #include "g_party.h" #include "k_hitlag.h" #include "g_input.h" #include "k_dialogue.h" #include "f_finale.h" #include "m_easing.h" //{ Patch Definitions static patch_t *kp_nodraw; static patch_t *kp_timesticker; static patch_t *kp_timestickerwide; static patch_t *kp_lapsticker; static patch_t *kp_lapstickerwide; static patch_t *kp_lapstickernarrow; static patch_t *kp_splitlapflag; static patch_t *kp_bumpersticker; static patch_t *kp_bumperstickerwide; static patch_t *kp_capsulesticker; static patch_t *kp_capsulestickerwide; static patch_t *kp_karmasticker; static patch_t *kp_spheresticker; static patch_t *kp_splitspheresticker; static patch_t *kp_splitkarmabomb; static patch_t *kp_timeoutsticker; static patch_t *kp_prestartbulb[15]; static patch_t *kp_prestartletters[7]; static patch_t *kp_prestartbulb_split[15]; static patch_t *kp_prestartletters_split[7]; static patch_t *kp_startcountdown[20]; static patch_t *kp_racefault[6]; static patch_t *kp_racefinish[6]; static patch_t *kp_positionnum[10][2][2]; // number, overlay or underlay, splitscreen patch_t *kp_facenum[MAXPLAYERS+1]; static patch_t *kp_facehighlight[8]; static patch_t *kp_nocontestminimap; patch_t *kp_unknownminimap; static patch_t *kp_spbminimap; static patch_t *kp_wouldyoustillcatchmeifiwereaworm; static patch_t *kp_catcherminimap; static patch_t *kp_emeraldminimap[2]; static patch_t *kp_capsuleminimap[3]; static patch_t *kp_battleufominimap; static patch_t *kp_superflickyminimap; static patch_t *kp_ringsticker[2]; static patch_t *kp_ringstickersplit[4]; static patch_t *kp_ring[6]; static patch_t *kp_smallring[6]; static patch_t *kp_ringdebtminus; static patch_t *kp_ringdebtminussmall; static patch_t *kp_ringspblock[16]; static patch_t *kp_ringspblocksmall[16]; static patch_t *kp_speedometersticker; static patch_t *kp_speedometerlabel[4]; static patch_t *kp_rankbumper; static patch_t *kp_bigbumper; static patch_t *kp_tinybumper[2]; static patch_t *kp_ranknobumpers; static patch_t *kp_rankcapsule; static patch_t *kp_rankemerald; static patch_t *kp_rankemeraldflash; static patch_t *kp_rankemeraldback; static patch_t *kp_pts[2]; static patch_t *kp_goal[2][2]; // [skull][4p] static patch_t *kp_goalrod[2]; // [4p] static patch_t *kp_goaltext1p; static patch_t *kp_battlewin; static patch_t *kp_battlecool; static patch_t *kp_battlelose; static patch_t *kp_battlewait; static patch_t *kp_battleinfo; static patch_t *kp_wanted; static patch_t *kp_wantedsplit; static patch_t *kp_wantedreticle; static patch_t *kp_minimapdot; static patch_t *kp_itembg[6]; static patch_t *kp_ringbg[4]; static patch_t *kp_itemtimer[2]; static patch_t *kp_itemmulsticker[2]; static patch_t *kp_itemx; static patch_t *kp_sadface[3]; static patch_t *kp_sneaker[3]; static patch_t *kp_rocketsneaker[3]; static patch_t *kp_invincibility[19]; static patch_t *kp_banana[3]; static patch_t *kp_eggman[3]; static patch_t *kp_orbinaut[6]; static patch_t *kp_jawz[3]; static patch_t *kp_mine[3]; static patch_t *kp_landmine[3]; static patch_t *kp_ballhog[3]; static patch_t *kp_selfpropelledbomb[3]; static patch_t *kp_grow[3]; static patch_t *kp_shrink[3]; static patch_t *kp_lightningshield[3]; static patch_t *kp_bubbleshield[3]; static patch_t *kp_flameshield[3]; static patch_t *kp_hyudoro[3]; static patch_t *kp_pogospring[3]; static patch_t *kp_superring[3]; static patch_t *kp_kitchensink[3]; static patch_t *kp_droptarget[3]; static patch_t *kp_gardentop[3]; static patch_t *kp_gachabom[3]; static patch_t *kp_bar[2]; static patch_t *kp_doublebar[2]; static patch_t *kp_triplebar[2]; static patch_t *kp_slotring[2]; static patch_t *kp_seven[2]; static patch_t *kp_jackpot[2]; static patch_t *kp_check[6]; static patch_t *kp_rival[2]; static patch_t *kp_localtag[4][2]; static patch_t *kp_talk; static patch_t *kp_typdot; patch_t *kp_eggnum[6]; static patch_t *kp_flameshieldmeter[FLAMESHIELD_MAX][2]; static patch_t *kp_flameshieldmeter_bg[FLAMESHIELD_MAX][2]; static patch_t *kp_fpview[3]; static patch_t *kp_inputwheel[5]; static patch_t *kp_challenger[25]; static patch_t *kp_lapanim_lap[7]; static patch_t *kp_lapanim_final[11]; static patch_t *kp_lapanim_number[10][3]; static patch_t *kp_lapanim_emblem[2]; static patch_t *kp_lapanim_hand[3]; static patch_t *kp_yougotem; static patch_t *kp_itemminimap; static patch_t *kp_alagles[10]; static patch_t *kp_blagles[6]; static patch_t *kp_cpu; static patch_t *kp_nametagstem; static patch_t *kp_bossbar[8]; static patch_t *kp_bossret[4]; static patch_t *kp_trickcool[2]; patch_t *kp_autoroulette; patch_t *kp_autoring; patch_t *kp_capsuletarget_arrow[2][2]; patch_t *kp_capsuletarget_icon[2]; patch_t *kp_capsuletarget_far[2][2]; patch_t *kp_capsuletarget_far_text[2]; patch_t *kp_capsuletarget_near[2][8]; patch_t *kp_superflickytarget[2][4]; patch_t *kp_spraycantarget_far[2][6]; patch_t *kp_spraycantarget_near[2][6]; patch_t *kp_button_a[2][2]; patch_t *kp_button_b[2][2]; patch_t *kp_button_c[2][2]; patch_t *kp_button_x[2][2]; patch_t *kp_button_y[2][2]; patch_t *kp_button_z[2][2]; patch_t *kp_button_start[2]; patch_t *kp_button_l[2]; patch_t *kp_button_r[2]; patch_t *kp_button_up[2]; patch_t *kp_button_down[2]; patch_t *kp_button_right[2]; patch_t *kp_button_left[2]; patch_t *kp_button_dpad[2]; static void K_LoadButtonGraphics(patch_t *kp[2], int letter) { HU_UpdatePatch(&kp[0], "TLB_%c", letter); HU_UpdatePatch(&kp[1], "TLB_%cB", letter); } void K_LoadKartHUDGraphics(void) { INT32 i, j, k; char buffer[9]; // Null Stuff HU_UpdatePatch(&kp_nodraw, "K_TRNULL"); // Stickers HU_UpdatePatch(&kp_timesticker, "K_STTIME"); HU_UpdatePatch(&kp_timestickerwide, "K_STTIMW"); HU_UpdatePatch(&kp_lapsticker, "K_STLAPS"); HU_UpdatePatch(&kp_lapstickerwide, "K_STLAPW"); HU_UpdatePatch(&kp_lapstickernarrow, "K_STLAPN"); HU_UpdatePatch(&kp_splitlapflag, "K_SPTLAP"); HU_UpdatePatch(&kp_bumpersticker, "K_STBALN"); HU_UpdatePatch(&kp_bumperstickerwide, "K_STBALW"); HU_UpdatePatch(&kp_capsulesticker, "K_STCAPN"); HU_UpdatePatch(&kp_capsulestickerwide, "K_STCAPW"); HU_UpdatePatch(&kp_karmasticker, "K_STKARM"); HU_UpdatePatch(&kp_spheresticker, "K_STBSMT"); HU_UpdatePatch(&kp_splitspheresticker, "K_SPBSMT"); HU_UpdatePatch(&kp_splitkarmabomb, "K_SPTKRM"); HU_UpdatePatch(&kp_timeoutsticker, "K_STTOUT"); // Pre-start countdown bulbs sprintf(buffer, "K_BULBxx"); for (i = 0; i < 15; i++) { buffer[6] = '0'+((i+1)/10); buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_prestartbulb[i], "%s", buffer); } sprintf(buffer, "K_SBLBxx"); for (i = 0; i < 15; i++) { buffer[6] = '0'+((i+1)/10); buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_prestartbulb_split[i], "%s", buffer); } // Pre-start position letters HU_UpdatePatch(&kp_prestartletters[0], "K_PL_P"); HU_UpdatePatch(&kp_prestartletters[1], "K_PL_O"); HU_UpdatePatch(&kp_prestartletters[2], "K_PL_S"); HU_UpdatePatch(&kp_prestartletters[3], "K_PL_I"); HU_UpdatePatch(&kp_prestartletters[4], "K_PL_T"); HU_UpdatePatch(&kp_prestartletters[5], "K_PL_N"); HU_UpdatePatch(&kp_prestartletters[6], "K_PL_EX"); HU_UpdatePatch(&kp_prestartletters_split[0], "K_SPL_P"); HU_UpdatePatch(&kp_prestartletters_split[1], "K_SPL_O"); HU_UpdatePatch(&kp_prestartletters_split[2], "K_SPL_S"); HU_UpdatePatch(&kp_prestartletters_split[3], "K_SPL_I"); HU_UpdatePatch(&kp_prestartletters_split[4], "K_SPL_T"); HU_UpdatePatch(&kp_prestartletters_split[5], "K_SPL_N"); HU_UpdatePatch(&kp_prestartletters_split[6], "K_SPL_EX"); // Starting countdown HU_UpdatePatch(&kp_startcountdown[0], "K_CNT3A"); HU_UpdatePatch(&kp_startcountdown[1], "K_CNT2A"); HU_UpdatePatch(&kp_startcountdown[2], "K_CNT1A"); HU_UpdatePatch(&kp_startcountdown[3], "K_CNTGOA"); HU_UpdatePatch(&kp_startcountdown[4], "K_DUEL1"); HU_UpdatePatch(&kp_startcountdown[5], "K_CNT3B"); HU_UpdatePatch(&kp_startcountdown[6], "K_CNT2B"); HU_UpdatePatch(&kp_startcountdown[7], "K_CNT1B"); HU_UpdatePatch(&kp_startcountdown[8], "K_CNTGOB"); HU_UpdatePatch(&kp_startcountdown[9], "K_DUEL2"); // Splitscreen HU_UpdatePatch(&kp_startcountdown[10], "K_SMC3A"); HU_UpdatePatch(&kp_startcountdown[11], "K_SMC2A"); HU_UpdatePatch(&kp_startcountdown[12], "K_SMC1A"); HU_UpdatePatch(&kp_startcountdown[13], "K_SMCGOA"); HU_UpdatePatch(&kp_startcountdown[14], "K_SDUEL1"); HU_UpdatePatch(&kp_startcountdown[15], "K_SMC3B"); HU_UpdatePatch(&kp_startcountdown[16], "K_SMC2B"); HU_UpdatePatch(&kp_startcountdown[17], "K_SMC1B"); HU_UpdatePatch(&kp_startcountdown[18], "K_SMCGOB"); HU_UpdatePatch(&kp_startcountdown[19], "K_SDUEL2"); // Fault HU_UpdatePatch(&kp_racefault[0], "K_FAULTA"); HU_UpdatePatch(&kp_racefault[1], "K_FAULTB"); // Splitscreen HU_UpdatePatch(&kp_racefault[2], "K_SMFLTA"); HU_UpdatePatch(&kp_racefault[3], "K_SMFLTB"); // 2P splitscreen HU_UpdatePatch(&kp_racefault[4], "K_2PFLTA"); HU_UpdatePatch(&kp_racefault[5], "K_2PFLTB"); // Finish HU_UpdatePatch(&kp_racefinish[0], "K_FINA"); HU_UpdatePatch(&kp_racefinish[1], "K_FINB"); // Splitscreen HU_UpdatePatch(&kp_racefinish[2], "K_SMFINA"); HU_UpdatePatch(&kp_racefinish[3], "K_SMFINB"); // 2P splitscreen HU_UpdatePatch(&kp_racefinish[4], "K_2PFINA"); HU_UpdatePatch(&kp_racefinish[5], "K_2PFINB"); // Position numbers sprintf(buffer, "KRNKxyz"); for (i = 0; i < 10; i++) { buffer[6] = '0'+i; for (j = 0; j < 2; j++) { buffer[5] = 'A'+j; for (k = 0; k < 2; k++) { if (k > 0) { buffer[4] = 'S'; } else { buffer[4] = 'B'; } HU_UpdatePatch(&kp_positionnum[i][j][k], "%s", buffer); } } } sprintf(buffer, "OPPRNKxx"); for (i = 0; i <= MAXPLAYERS; i++) { buffer[6] = '0'+(i/10); buffer[7] = '0'+(i%10); HU_UpdatePatch(&kp_facenum[i], "%s", buffer); } sprintf(buffer, "K_CHILIx"); for (i = 0; i < 8; i++) { buffer[7] = '0'+(i+1); HU_UpdatePatch(&kp_facehighlight[i], "%s", buffer); } // Special minimap icons HU_UpdatePatch(&kp_nocontestminimap, "MINIDEAD"); HU_UpdatePatch(&kp_unknownminimap, "HUHMAP"); HU_UpdatePatch(&kp_spbminimap, "SPBMMAP"); HU_UpdatePatch(&kp_wouldyoustillcatchmeifiwereaworm, "MINIPROG"); HU_UpdatePatch(&kp_catcherminimap, "UFOMAP"); HU_UpdatePatch(&kp_emeraldminimap[0], "EMEMAP"); HU_UpdatePatch(&kp_emeraldminimap[1], "SUPMAP"); HU_UpdatePatch(&kp_capsuleminimap[0], "MINICAP1"); HU_UpdatePatch(&kp_capsuleminimap[1], "MINICAP2"); HU_UpdatePatch(&kp_capsuleminimap[2], "MINICAP3"); HU_UpdatePatch(&kp_battleufominimap, "MINIBUFO"); HU_UpdatePatch(&kp_superflickyminimap, "FLKMAPA"); // Rings & Lives HU_UpdatePatch(&kp_ringsticker[0], "RNGBACKA"); HU_UpdatePatch(&kp_ringsticker[1], "RNGBACKB"); sprintf(buffer, "K_RINGx"); for (i = 0; i < 6; i++) { buffer[6] = '0'+(i+1); HU_UpdatePatch(&kp_ring[i], "%s", buffer); } HU_UpdatePatch(&kp_ringdebtminus, "RDEBTMIN"); sprintf(buffer, "SPBRNGxx"); for (i = 0; i < 16; i++) { buffer[6] = '0'+((i+1) / 10); buffer[7] = '0'+((i+1) % 10); HU_UpdatePatch(&kp_ringspblock[i], "%s", buffer); } HU_UpdatePatch(&kp_ringstickersplit[0], "SMRNGBGA"); HU_UpdatePatch(&kp_ringstickersplit[1], "SMRNGBGB"); sprintf(buffer, "K_SRINGx"); for (i = 0; i < 6; i++) { buffer[7] = '0'+(i+1); HU_UpdatePatch(&kp_smallring[i], "%s", buffer); } HU_UpdatePatch(&kp_ringdebtminussmall, "SRDEBTMN"); sprintf(buffer, "SPBRGSxx"); for (i = 0; i < 16; i++) { buffer[6] = '0'+((i+1) / 10); buffer[7] = '0'+((i+1) % 10); HU_UpdatePatch(&kp_ringspblocksmall[i], "%s", buffer); } // Speedometer HU_UpdatePatch(&kp_speedometersticker, "K_SPDMBG"); sprintf(buffer, "K_SPDMLx"); for (i = 0; i < 4; i++) { buffer[7] = '0'+(i+1); HU_UpdatePatch(&kp_speedometerlabel[i], "%s", buffer); } // Extra ranking icons HU_UpdatePatch(&kp_rankbumper, "K_BLNICO"); HU_UpdatePatch(&kp_bigbumper, "K_BLNREG"); HU_UpdatePatch(&kp_tinybumper[0], "K_BLNA"); HU_UpdatePatch(&kp_tinybumper[1], "K_BLNB"); HU_UpdatePatch(&kp_ranknobumpers, "K_NOBLNS"); HU_UpdatePatch(&kp_rankcapsule, "K_CAPICO"); HU_UpdatePatch(&kp_rankemerald, "K_EMERC"); HU_UpdatePatch(&kp_rankemeraldflash, "K_EMERW"); HU_UpdatePatch(&kp_rankemeraldback, "K_EMERBK"); HU_UpdatePatch(&kp_pts[0], "K_POINTS"); HU_UpdatePatch(&kp_pts[1], "K_POINT4"); // Battle goal HU_UpdatePatch(&kp_goal[0][0], "K_ST1GLA"); HU_UpdatePatch(&kp_goal[1][0], "K_ST1GLB"); HU_UpdatePatch(&kp_goal[0][1], "K_ST4GLA"); HU_UpdatePatch(&kp_goal[1][1], "K_ST4GLB"); HU_UpdatePatch(&kp_goalrod[0], "K_ST1GLD"); HU_UpdatePatch(&kp_goalrod[1], "K_ST4GLD"); HU_UpdatePatch(&kp_goaltext1p, "K_ST1GLC"); // Battle graphics HU_UpdatePatch(&kp_battlewin, "K_BWIN"); HU_UpdatePatch(&kp_battlecool, "K_BCOOL"); HU_UpdatePatch(&kp_battlelose, "K_BLOSE"); HU_UpdatePatch(&kp_battlewait, "K_BWAIT"); HU_UpdatePatch(&kp_battleinfo, "K_BINFO"); HU_UpdatePatch(&kp_wanted, "K_WANTED"); HU_UpdatePatch(&kp_wantedsplit, "4PWANTED"); HU_UpdatePatch(&kp_wantedreticle, "MMAPWANT"); HU_UpdatePatch(&kp_minimapdot, "MMAPDOT"); // Kart Item Windows HU_UpdatePatch(&kp_itembg[0], "K_ITBG"); HU_UpdatePatch(&kp_itembg[1], "K_ITBGD"); HU_UpdatePatch(&kp_itemtimer[0], "K_ITIMER"); HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); HU_UpdatePatch(&kp_itemx, "K_ITX"); HU_UpdatePatch(&kp_ringbg[0], "K_RBBG"); HU_UpdatePatch(&kp_ringbg[1], "K_SBBG"); HU_UpdatePatch(&kp_sadface[0], "K_ITSAD"); HU_UpdatePatch(&kp_sneaker[0], "K_ITSHOE"); HU_UpdatePatch(&kp_rocketsneaker[0], "K_ITRSHE"); sprintf(buffer, "K_ITINVx"); for (i = 0; i < 7; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_invincibility[i], "%s", buffer); } HU_UpdatePatch(&kp_banana[0], "K_ITBANA"); HU_UpdatePatch(&kp_eggman[0], "K_ITEGGM"); sprintf(buffer, "K_ITORBx"); for (i = 0; i < 4; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_orbinaut[i], "%s", buffer); } HU_UpdatePatch(&kp_jawz[0], "K_ITJAWZ"); HU_UpdatePatch(&kp_mine[0], "K_ITMINE"); HU_UpdatePatch(&kp_landmine[0], "K_ITLNDM"); HU_UpdatePatch(&kp_ballhog[0], "K_ITBHOG"); HU_UpdatePatch(&kp_selfpropelledbomb[0], "K_ITSPB"); HU_UpdatePatch(&kp_grow[0], "K_ITGROW"); HU_UpdatePatch(&kp_shrink[0], "K_ITSHRK"); HU_UpdatePatch(&kp_lightningshield[0], "K_ITTHNS"); HU_UpdatePatch(&kp_bubbleshield[0], "K_ITBUBS"); HU_UpdatePatch(&kp_flameshield[0], "K_ITFLMS"); HU_UpdatePatch(&kp_hyudoro[0], "K_ITHYUD"); HU_UpdatePatch(&kp_pogospring[0], "K_ITPOGO"); HU_UpdatePatch(&kp_superring[0], "K_ITRING"); HU_UpdatePatch(&kp_kitchensink[0], "K_ITSINK"); HU_UpdatePatch(&kp_droptarget[0], "K_ITDTRG"); HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP"); HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM"); HU_UpdatePatch(&kp_bar[0], "K_RBBAR"); HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2"); HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3"); HU_UpdatePatch(&kp_slotring[0], "K_RBRING"); HU_UpdatePatch(&kp_seven[0], "K_RBSEV"); HU_UpdatePatch(&kp_jackpot[0], "K_RBJACK"); sprintf(buffer, "FSMFGxxx"); for (i = 0; i < FLAMESHIELD_MAX; i++) { buffer[5] = '0'+((i+1)/100); buffer[6] = '0'+(((i+1)/10)%10); buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_flameshieldmeter[i][0], "%s", buffer); } sprintf(buffer, "FSMBGxxx"); for (i = 0; i < FLAMESHIELD_MAX; i++) { buffer[5] = '0'+((i+1)/100); buffer[6] = '0'+(((i+1)/10)%10); buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_flameshieldmeter_bg[i][0], "%s", buffer); } // Splitscreen HU_UpdatePatch(&kp_itembg[2], "K_ISBG"); HU_UpdatePatch(&kp_itembg[3], "K_ISBGD"); HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); HU_UpdatePatch(&kp_itemmulsticker[1], "K_ISMUL"); HU_UpdatePatch(&kp_sadface[1], "K_ISSAD"); HU_UpdatePatch(&kp_sneaker[1], "K_ISSHOE"); HU_UpdatePatch(&kp_rocketsneaker[1], "K_ISRSHE"); sprintf(buffer, "K_ISINVx"); for (i = 0; i < 6; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_invincibility[i+7], "%s", buffer); } HU_UpdatePatch(&kp_banana[1], "K_ISBANA"); HU_UpdatePatch(&kp_eggman[1], "K_ISEGGM"); HU_UpdatePatch(&kp_orbinaut[4], "K_ISORBN"); HU_UpdatePatch(&kp_jawz[1], "K_ISJAWZ"); HU_UpdatePatch(&kp_mine[1], "K_ISMINE"); HU_UpdatePatch(&kp_landmine[1], "K_ISLNDM"); HU_UpdatePatch(&kp_ballhog[1], "K_ISBHOG"); HU_UpdatePatch(&kp_selfpropelledbomb[1], "K_ISSPB"); HU_UpdatePatch(&kp_grow[1], "K_ISGROW"); HU_UpdatePatch(&kp_shrink[1], "K_ISSHRK"); HU_UpdatePatch(&kp_lightningshield[1], "K_ISTHNS"); HU_UpdatePatch(&kp_bubbleshield[1], "K_ISBUBS"); HU_UpdatePatch(&kp_flameshield[1], "K_ISFLMS"); HU_UpdatePatch(&kp_hyudoro[1], "K_ISHYUD"); HU_UpdatePatch(&kp_pogospring[1], "K_ISPOGO"); HU_UpdatePatch(&kp_superring[1], "K_ISRING"); HU_UpdatePatch(&kp_kitchensink[1], "K_ISSINK"); HU_UpdatePatch(&kp_droptarget[1], "K_ISDTRG"); HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP"); HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM"); HU_UpdatePatch(&kp_bar[1], "K_SBBAR"); HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2"); HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3"); HU_UpdatePatch(&kp_slotring[1], "K_SBRING"); HU_UpdatePatch(&kp_seven[1], "K_SBSEV"); HU_UpdatePatch(&kp_jackpot[1], "K_SBJACK"); sprintf(buffer, "FSMFSxxx"); for (i = 0; i < 120; i++) { buffer[5] = '0'+((i+1)/100); buffer[6] = '0'+(((i+1)/10)%10); buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_flameshieldmeter[i][1], "%s", buffer); } sprintf(buffer, "FSMBS0xx"); for (i = 0; i < 120; i++) { buffer[5] = '0'+((i+1)/100); buffer[6] = '0'+(((i+1)/10)%10); buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_flameshieldmeter_bg[i][1], "%s", buffer); } // 4P item spy HU_UpdatePatch(&kp_itembg[4], "ISPYBG"); HU_UpdatePatch(&kp_itembg[5], "ISPYBGD"); //HU_UpdatePatch(&kp_sadface[2], "ISPYSAD"); HU_UpdatePatch(&kp_sneaker[2], "ISPYSHOE"); HU_UpdatePatch(&kp_rocketsneaker[2], "ISPYRSHE"); sprintf(buffer, "ISPYINVx"); for (i = 0; i < 6; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_invincibility[i+13], "%s", buffer); } HU_UpdatePatch(&kp_banana[2], "ISPYBANA"); HU_UpdatePatch(&kp_eggman[2], "ISPYEGGM"); HU_UpdatePatch(&kp_orbinaut[5], "ISPYORBN"); HU_UpdatePatch(&kp_jawz[2], "ISPYJAWZ"); HU_UpdatePatch(&kp_mine[2], "ISPYMINE"); HU_UpdatePatch(&kp_landmine[2], "ISPYLNDM"); HU_UpdatePatch(&kp_ballhog[2], "ISPYBHOG"); HU_UpdatePatch(&kp_selfpropelledbomb[2], "ISPYSPB"); HU_UpdatePatch(&kp_grow[2], "ISPYGROW"); HU_UpdatePatch(&kp_shrink[2], "ISPYSHRK"); HU_UpdatePatch(&kp_lightningshield[2], "ISPYTHNS"); HU_UpdatePatch(&kp_bubbleshield[2], "ISPYBUBS"); HU_UpdatePatch(&kp_flameshield[2], "ISPYFLMS"); HU_UpdatePatch(&kp_hyudoro[2], "ISPYHYUD"); HU_UpdatePatch(&kp_pogospring[2], "ISPYPOGO"); HU_UpdatePatch(&kp_superring[2], "ISPYRING"); HU_UpdatePatch(&kp_kitchensink[2], "ISPYSINK"); HU_UpdatePatch(&kp_droptarget[2], "ISPYDTRG"); HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP"); HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM"); // CHECK indicators sprintf(buffer, "K_CHECKx"); for (i = 0; i < 6; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_check[i], "%s", buffer); } // Rival indicators sprintf(buffer, "K_RIVALx"); for (i = 0; i < 2; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_rival[i], "%s", buffer); } // Rival indicators sprintf(buffer, "K_SSPLxx"); for (i = 0; i < 4; i++) { buffer[6] = 'A'+i; for (j = 0; j < 2; j++) { buffer[7] = '1'+j; HU_UpdatePatch(&kp_localtag[i][j], "%s", buffer); } } // Typing indicator HU_UpdatePatch(&kp_talk, "K_TALK"); HU_UpdatePatch(&kp_typdot, "K_TYPDOT"); // Eggman warning numbers sprintf(buffer, "K_EGGNx"); for (i = 0; i < 6; i++) { buffer[6] = '0'+i; HU_UpdatePatch(&kp_eggnum[i], "%s", buffer); } // First person mode HU_UpdatePatch(&kp_fpview[0], "VIEWA0"); HU_UpdatePatch(&kp_fpview[1], "VIEWB0D0"); HU_UpdatePatch(&kp_fpview[2], "VIEWC0E0"); // Input UI Wheel sprintf(buffer, "K_WHEELx"); for (i = 0; i < 5; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_inputwheel[i], "%s", buffer); } // HERE COMES A NEW CHALLENGER sprintf(buffer, "K_CHALxx"); for (i = 0; i < 25; i++) { buffer[6] = '0'+((i+1)/10); buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_challenger[i], "%s", buffer); } // Lap start animation sprintf(buffer, "K_LAP0x"); for (i = 0; i < 7; i++) { buffer[6] = '0'+(i+1); HU_UpdatePatch(&kp_lapanim_lap[i], "%s", buffer); } sprintf(buffer, "K_LAPFxx"); for (i = 0; i < 11; i++) { buffer[6] = '0'+((i+1)/10); buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_lapanim_final[i], "%s", buffer); } sprintf(buffer, "K_LAPNxx"); for (i = 0; i < 10; i++) { buffer[6] = '0'+i; for (j = 0; j < 3; j++) { buffer[7] = '0'+(j+1); HU_UpdatePatch(&kp_lapanim_number[i][j], "%s", buffer); } } sprintf(buffer, "K_LAPE0x"); for (i = 0; i < 2; i++) { buffer[7] = '0'+(i+1); HU_UpdatePatch(&kp_lapanim_emblem[i], "%s", buffer); } sprintf(buffer, "K_LAPH0x"); for (i = 0; i < 3; i++) { buffer[7] = '0'+(i+1); HU_UpdatePatch(&kp_lapanim_hand[i], "%s", buffer); } HU_UpdatePatch(&kp_yougotem, "YOUGOTEM"); HU_UpdatePatch(&kp_itemminimap, "MMAPITEM"); sprintf(buffer, "ALAGLESx"); for (i = 0; i < 10; ++i) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_alagles[i], "%s", buffer); } sprintf(buffer, "BLAGLESx"); for (i = 0; i < 6; ++i) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_blagles[i], "%s", buffer); } HU_UpdatePatch(&kp_cpu, "K_CPU"); HU_UpdatePatch(&kp_nametagstem, "K_NAMEST"); HU_UpdatePatch(&kp_trickcool[0], "K_COOL1"); HU_UpdatePatch(&kp_trickcool[1], "K_COOL2"); HU_UpdatePatch(&kp_autoroulette, "A11YITEM"); HU_UpdatePatch(&kp_autoring, "A11YRING"); sprintf(buffer, "K_BOSB0x"); for (i = 0; i < 8; i++) { buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_bossbar[i], "%s", buffer); } sprintf(buffer, "K_BOSR0x"); for (i = 0; i < 4; i++) { buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_bossret[i], "%s", buffer); } sprintf(buffer, "HCAPARxx"); for (i = 0; i < 2; i++) { buffer[6] = 'A'+i; for (j = 0; j < 2; j++) { buffer[7] = '0'+j; HU_UpdatePatch(&kp_capsuletarget_arrow[i][j], "%s", buffer); } } sprintf(buffer, "HUDCAPDx"); for (i = 0; i < 2; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_capsuletarget_far_text[i], "%s", buffer); } sprintf(buffer, "HUDCAPCx"); for (i = 0; i < 2; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_capsuletarget_icon[i], "%s", buffer); } sprintf(buffer, "HUDCAPBx"); for (i = 0; i < 2; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_capsuletarget_far[0][i], "%s", buffer); } sprintf(buffer, "HUDC4PBx"); for (i = 0; i < 2; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_capsuletarget_far[1][i], "%s", buffer); } sprintf(buffer, "HUDCAPAx"); for (i = 0; i < 8; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_capsuletarget_near[0][i], "%s", buffer); } sprintf(buffer, "HUDC4PAx"); for (i = 0; i < 8; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_capsuletarget_near[1][i], "%s", buffer); } sprintf(buffer, "HUDFLKAx"); for (i = 0; i < 4; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_superflickytarget[0][i], "%s", buffer); } sprintf(buffer, "H4PFLKAx"); for (i = 0; i < 4; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_superflickytarget[1][i], "%s", buffer); } sprintf(buffer, "SPCNBFAx"); for (i = 0; i < 6; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_spraycantarget_far[0][i], "%s", buffer); } sprintf(buffer, "SPCNSFAx"); for (i = 0; i < 6; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_spraycantarget_far[1][i], "%s", buffer); } sprintf(buffer, "SPCNBCLx"); for (i = 0; i < 6; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_spraycantarget_near[0][i], "%s", buffer); } sprintf(buffer, "SPCNSCLx"); for (i = 0; i < 6; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_spraycantarget_near[1][i], "%s", buffer); } K_LoadButtonGraphics(kp_button_a[0], 'A'); K_LoadButtonGraphics(kp_button_a[1], 'N'); K_LoadButtonGraphics(kp_button_b[0], 'B'); K_LoadButtonGraphics(kp_button_b[1], 'O'); K_LoadButtonGraphics(kp_button_c[0], 'C'); K_LoadButtonGraphics(kp_button_c[1], 'P'); K_LoadButtonGraphics(kp_button_x[0], 'D'); K_LoadButtonGraphics(kp_button_x[1], 'Q'); K_LoadButtonGraphics(kp_button_y[0], 'E'); K_LoadButtonGraphics(kp_button_y[1], 'R'); K_LoadButtonGraphics(kp_button_z[0], 'F'); K_LoadButtonGraphics(kp_button_z[1], 'S'); K_LoadButtonGraphics(kp_button_start, 'G'); K_LoadButtonGraphics(kp_button_l, 'H'); K_LoadButtonGraphics(kp_button_r, 'I'); K_LoadButtonGraphics(kp_button_up, 'J'); K_LoadButtonGraphics(kp_button_down, 'K'); K_LoadButtonGraphics(kp_button_right, 'L'); K_LoadButtonGraphics(kp_button_left, 'M'); K_LoadButtonGraphics(kp_button_dpad, 'T'); } // For the item toggle menu const char *K_GetItemPatch(UINT8 item, boolean tiny) { switch (item) { case KITEM_SNEAKER: case KRITEM_DUALSNEAKER: case KRITEM_TRIPLESNEAKER: return (tiny ? "K_ISSHOE" : "K_ITSHOE"); case KITEM_ROCKETSNEAKER: return (tiny ? "K_ISRSHE" : "K_ITRSHE"); case KITEM_INVINCIBILITY: return (tiny ? "K_ISINV1" : "K_ITINV1"); case KITEM_BANANA: case KRITEM_TRIPLEBANANA: return (tiny ? "K_ISBANA" : "K_ITBANA"); case KITEM_EGGMAN: return (tiny ? "K_ISEGGM" : "K_ITEGGM"); case KITEM_ORBINAUT: return (tiny ? "K_ISORBN" : "K_ITORB1"); case KITEM_JAWZ: case KRITEM_DUALJAWZ: return (tiny ? "K_ISJAWZ" : "K_ITJAWZ"); case KITEM_MINE: return (tiny ? "K_ISMINE" : "K_ITMINE"); case KITEM_LANDMINE: return (tiny ? "K_ISLNDM" : "K_ITLNDM"); case KITEM_BALLHOG: return (tiny ? "K_ISBHOG" : "K_ITBHOG"); case KITEM_SPB: return (tiny ? "K_ISSPB" : "K_ITSPB"); case KITEM_GROW: return (tiny ? "K_ISGROW" : "K_ITGROW"); case KITEM_SHRINK: return (tiny ? "K_ISSHRK" : "K_ITSHRK"); case KITEM_LIGHTNINGSHIELD: return (tiny ? "K_ISTHNS" : "K_ITTHNS"); case KITEM_BUBBLESHIELD: return (tiny ? "K_ISBUBS" : "K_ITBUBS"); case KITEM_FLAMESHIELD: return (tiny ? "K_ISFLMS" : "K_ITFLMS"); case KITEM_HYUDORO: return (tiny ? "K_ISHYUD" : "K_ITHYUD"); case KITEM_POGOSPRING: return (tiny ? "K_ISPOGO" : "K_ITPOGO"); case KITEM_SUPERRING: return (tiny ? "K_ISRING" : "K_ITRING"); case KITEM_KITCHENSINK: return (tiny ? "K_ISSINK" : "K_ITSINK"); case KITEM_DROPTARGET: return (tiny ? "K_ISDTRG" : "K_ITDTRG"); case KITEM_GARDENTOP: return (tiny ? "K_ISGTOP" : "K_ITGTOP"); case KITEM_GACHABOM: case KRITEM_TRIPLEGACHABOM: return (tiny ? "K_ISGBOM" : "K_ITGBOM"); case KRITEM_TRIPLEORBINAUT: return (tiny ? "K_ISORBN" : "K_ITORB3"); case KRITEM_QUADORBINAUT: return (tiny ? "K_ISORBN" : "K_ITORB4"); default: return (tiny ? "K_ISSAD" : "K_ITSAD"); } } static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) { patch_t **kp[1 + NUMKARTITEMS] = { kp_sadface, NULL, kp_sneaker, kp_rocketsneaker, kp_invincibility, kp_banana, kp_eggman, kp_orbinaut, kp_jawz, kp_mine, kp_landmine, kp_ballhog, kp_selfpropelledbomb, kp_grow, kp_shrink, kp_lightningshield, kp_bubbleshield, kp_flameshield, kp_hyudoro, kp_pogospring, kp_superring, kp_kitchensink, kp_droptarget, kp_gardentop, kp_gachabom, }; if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) return kp[item - KITEM_SAD][offset]; else return NULL; } static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item) { UINT8 offset; item = static_cast<kartitems_t>(K_ItemResultToType(item)); switch (item) { case KITEM_INVINCIBILITY: offset = 7; break; case KITEM_ORBINAUT: offset = 4; break; default: offset = 1; } return K_GetCachedItemPatch(item, offset); } static patch_t *K_GetCachedSlotMachinePatch(INT32 item, UINT8 offset) { patch_t **kp[KSM__MAX] = { kp_bar, kp_doublebar, kp_triplebar, kp_slotring, kp_seven, kp_jackpot, }; if (item >= 0 && item < KSM__MAX) return kp[item][offset]; else return NULL; } //} INT32 ITEM_X, ITEM_Y; // Item Window INT32 TIME_X, TIME_Y; // Time Sticker INT32 LAPS_X, LAPS_Y; // Lap Sticker INT32 POSI_X, POSI_Y; // Position Number INT32 FACE_X, FACE_Y; // Top-four Faces INT32 STCD_X, STCD_Y; // Starting countdown INT32 CHEK_Y; // CHECK graphic INT32 MINI_X, MINI_Y; // Minimap INT32 WANT_X, WANT_Y; // Battle WANTED poster // This is for the P2 and P4 side of splitscreen. Then we'll flip P1's and P2's to the bottom with V_SPLITSCREEN. INT32 ITEM2_X, ITEM2_Y; INT32 LAPS2_X, LAPS2_Y; INT32 POSI2_X, POSI2_Y; // trick "cool" INT32 TCOOL_X, TCOOL_Y; // This version of the function was prototyped in Lua by Nev3r ... a HUGE thank you goes out to them! void K_ObjectTracking(trackingResult_t *result, const vector3_t *point, boolean reverse) { #define NEWTAN(x) FINETANGENT(((x + ANGLE_90) >> ANGLETOFINESHIFT) & 4095) // tan function used by Lua #define NEWCOS(x) FINECOSINE((x >> ANGLETOFINESHIFT) & FINEMASK) angle_t viewpointAngle, viewpointAiming, viewpointRoll; INT32 screenWidth, screenHeight; fixed_t screenHalfW, screenHalfH; const fixed_t baseFov = 90*FRACUNIT; fixed_t fovDiff, fov, fovTangent, fg; fixed_t h; INT32 da, dp; UINT8 cameraNum = R_GetViewNumber(); I_Assert(result != NULL); I_Assert(point != NULL); // Initialize defaults result->x = result->y = 0; result->scale = FRACUNIT; result->onScreen = false; // Take the view's properties as necessary. if (reverse) { viewpointAngle = (INT32)(viewangle + ANGLE_180); viewpointAiming = (INT32)InvAngle(aimingangle); viewpointRoll = (INT32)viewroll; } else { viewpointAngle = (INT32)viewangle; viewpointAiming = (INT32)aimingangle; viewpointRoll = (INT32)InvAngle(viewroll); } // Calculate screen size adjustments. screenWidth = vid.width/vid.dupx; screenHeight = vid.height/vid.dupy; if (r_splitscreen >= 2) { // Half-wide screens screenWidth >>= 1; } if (r_splitscreen >= 1) { // Half-tall screens screenHeight >>= 1; } screenHalfW = (screenWidth >> 1) << FRACBITS; screenHalfH = (screenHeight >> 1) << FRACBITS; // Calculate FOV adjustments. fovDiff = R_FOV(cameraNum) - baseFov; fov = ((baseFov - fovDiff) / 2) - (stplyr->fovadd / 2); fovTangent = NEWTAN(FixedAngle(fov)); if (r_splitscreen == 1) { // Splitscreen FOV is adjusted to maintain expected vertical view fovTangent = 10*fovTangent/17; } fg = (screenWidth >> 1) * fovTangent; // Determine viewpoint factors. h = R_PointToDist2(point->x, point->y, viewx, viewy); da = AngleDeltaSigned(viewpointAngle, R_PointToAngle2(viewx, viewy, point->x, point->y)); dp = AngleDeltaSigned(viewpointAiming, R_PointToAngle2(0, 0, h, viewz)); if (reverse) { da = -(da); } // Set results relative to top left! result->x = FixedMul(NEWTAN(da), fg); result->y = FixedMul((NEWTAN(viewpointAiming) - FixedDiv((point->z - viewz), 1 + FixedMul(NEWCOS(da), h))), fg); result->angle = da; result->pitch = dp; result->fov = fg; // Rotate for screen roll... if (viewpointRoll) { fixed_t tempx = result->x; viewpointRoll >>= ANGLETOFINESHIFT; result->x = FixedMul(FINECOSINE(viewpointRoll), tempx) - FixedMul(FINESINE(viewpointRoll), result->y); result->y = FixedMul(FINESINE(viewpointRoll), tempx) + FixedMul(FINECOSINE(viewpointRoll), result->y); } // Flipped screen? if (encoremode) { result->x = -result->x; } // Center results. result->x += screenHalfW; result->y += screenHalfH; result->scale = FixedDiv(screenHalfW, h+1); result->onScreen = !((abs(da) > ANG60) || (abs(AngleDeltaSigned(viewpointAiming, R_PointToAngle2(0, 0, h, (viewz - point->z)))) > ANGLE_45)); // Cheap dirty hacks for some split-screen related cases if (result->x < 0 || result->x > (screenWidth << FRACBITS)) { result->onScreen = false; } if (result->y < 0 || result->y > (screenHeight << FRACBITS)) { result->onScreen = false; } // adjust to non-green-resolution screen coordinates result->x -= ((vid.width/vid.dupx) - BASEVIDWIDTH)<<(FRACBITS-((r_splitscreen >= 2) ? 2 : 1)); result->y -= ((vid.height/vid.dupy) - BASEVIDHEIGHT)<<(FRACBITS-((r_splitscreen >= 1) ? 2 : 1)); return; #undef NEWTAN #undef NEWCOS } static void K_initKartHUD(void) { /* BASEVIDWIDTH = 320 BASEVIDHEIGHT = 200 Item window graphic is 41 x 33 Time Sticker graphic is 116 x 11 Time Font is a solid block of (8 x [12) x 14], equal to 96 x 14 Therefore, timestamp is 116 x 14 altogether Lap Sticker is 80 x 11 Lap flag is 22 x 20 Lap Font is a solid block of (3 x [12) x 14], equal to 36 x 14 Therefore, lapstamp is 80 x 20 altogether Position numbers are 43 x 53 Faces are 32 x 32 Faces draw downscaled at 16 x 16 Therefore, the allocated space for them is 16 x 67 altogether ---- ORIGINAL CZ64 SPLITSCREEN: Item window: if (!splitscreen) { ICONX = 139; ICONY = 20; } else { ICONX = BASEVIDWIDTH-315; ICONY = 60; } Time: 236, STRINGY( 12) Lap: BASEVIDWIDTH-304, STRINGY(BASEVIDHEIGHT-189) */ // Single Screen (defaults) // Item Window ITEM_X = 5; // 5 ITEM_Y = 5; // 5 // Level Timer TIME_X = BASEVIDWIDTH - 148; // 172 TIME_Y = 9; // 9 // Level Laps LAPS_X = 9; // 9 LAPS_Y = BASEVIDHEIGHT - 29; // 171 // Position Number POSI_X = BASEVIDWIDTH - 9; // 268 POSI_Y = BASEVIDHEIGHT - 9; // 138 // Top-Four Faces FACE_X = 9; // 9 FACE_Y = 92; // 92 // Starting countdown STCD_X = BASEVIDWIDTH/2; // 9 STCD_Y = BASEVIDHEIGHT/2; // 92 // CHECK graphic CHEK_Y = BASEVIDHEIGHT; // 200 // Minimap MINI_X = BASEVIDWIDTH - 50; // 270 MINI_Y = (BASEVIDHEIGHT/2)-16; // 84 // Battle WANTED poster WANT_X = BASEVIDWIDTH - 55; // 270 WANT_Y = BASEVIDHEIGHT- 71; // 176 // trick COOL TCOOL_X = (BASEVIDWIDTH)/2; TCOOL_Y = (BASEVIDHEIGHT)/2 -10; if (r_splitscreen) // Splitscreen { ITEM_X = 5; ITEM_Y = 3; LAPS_Y = (BASEVIDHEIGHT/2)-24; POSI_Y = (BASEVIDHEIGHT/2)- 2; STCD_Y = BASEVIDHEIGHT/4; MINI_X -= 16; MINI_Y = (BASEVIDHEIGHT/2); if (r_splitscreen > 1) // 3P/4P Small Splitscreen { // 1P (top left) ITEM_X = -9; ITEM_Y = -8; LAPS_X = 3; LAPS_Y = (BASEVIDHEIGHT/2)-12; POSI_X = 24; POSI_Y = (BASEVIDHEIGHT/2)-26; // 2P (top right) ITEM2_X = (BASEVIDWIDTH/2)-39; ITEM2_Y = -8; LAPS2_X = (BASEVIDWIDTH/2)-43; LAPS2_Y = (BASEVIDHEIGHT/2)-12; POSI2_X = (BASEVIDWIDTH/2)-4; POSI2_Y = (BASEVIDHEIGHT/2)-26; // Reminder that 3P and 4P are just 1P and 2P splitscreen'd to the bottom. STCD_X = BASEVIDWIDTH/4; MINI_X = (3*BASEVIDWIDTH/4); MINI_Y = (3*BASEVIDHEIGHT/4); TCOOL_X = (BASEVIDWIDTH)/4; if (r_splitscreen > 2) // 4P-only { MINI_X = (BASEVIDWIDTH/2); MINI_Y = (BASEVIDHEIGHT/2); } } } } void K_DrawMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, UINT16 map, const UINT8 *colormap) { patch_t *PictureOfLevel = NULL; if (map >= nummapheaders || !mapheaderinfo[map]) { PictureOfLevel = nolvl; } else if (!mapheaderinfo[map]->thumbnailPic) { PictureOfLevel = blanklvl; } else { PictureOfLevel = static_cast<patch_t*>(mapheaderinfo[map]->thumbnailPic); } K_DrawLikeMapThumbnail(x, y, width, flags, PictureOfLevel, colormap); } void K_DrawLikeMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, patch_t *patch, const UINT8 *colormap) { if (flags & V_FLIP) x += width; V_DrawFixedPatch( x, y, FixedDiv(width, (320 << FRACBITS)), flags, patch, colormap ); } // see also K_DrawNameTagItemSpy static void K_drawKartItem(void) { // ITEM_X = BASEVIDWIDTH-50; // 270 // ITEM_Y = 24; // 24 // Why write V_DrawScaledPatch calls over and over when they're all the same? // Set to 'no item' just in case. const UINT8 offset = ((r_splitscreen > 1) ? 1 : 0); patch_t *localpatch[3] = { kp_nodraw, kp_nodraw, kp_nodraw }; UINT8 localamt[3] = {0, 0, 0}; patch_t *localbg = ((offset) ? kp_itembg[2] : kp_itembg[0]); patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]); INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... const INT32 numberdisplaymin = ((!offset && stplyr->itemtype == KITEM_ORBINAUT) ? 5 : 2); INT32 itembar = 0; INT32 maxl = 0; // itembar's normal highest value const INT32 barlength = (offset ? 12 : 26); skincolornum_t localcolor[3] = { static_cast<skincolornum_t>(stplyr->skincolor) }; SINT8 colormode[3] = { TC_RAINBOW }; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff fixed_t rouletteOffset = 0; fixed_t rouletteSpace = ROULETTE_SPACING; vector2_t rouletteCrop = {7, 7}; INT32 i; boolean flashOnOne = false; boolean flashOnTwo = false; if (stplyr->itemRoulette.itemListLen > 0) { // Init with item roulette stuff. for (i = 0; i < 3; i++) { const SINT8 indexOfs = i-1; const size_t index = (stplyr->itemRoulette.itemListLen + (stplyr->itemRoulette.index + indexOfs)) % stplyr->itemRoulette.itemListLen; const SINT8 result = stplyr->itemRoulette.itemList[index]; const SINT8 item = K_ItemResultToType(result); const boolean usingDebugItemAmount = cv_kartdebugitem.value != KITEM_NONE && cv_kartdebugitem.value == item && cv_kartdebugamount.value > 1; const UINT8 amt = usingDebugItemAmount ? cv_kartdebugamount.value : K_ItemResultToAmount(result); switch (item) { case KITEM_INVINCIBILITY: localpatch[i] = localinv; localamt[i] = amt; break; case KITEM_ORBINAUT: localpatch[i] = kp_orbinaut[(offset ? 4 : std::min(amt-1, 3))]; if (amt > 4) localamt[i] = amt; break; default: localpatch[i] = K_GetCachedItemPatch(item, offset); if (item != KITEM_BALLHOG || amt != 5) localamt[i] = amt; break; } } } if (stplyr->itemRoulette.active == true) { rouletteOffset = K_GetRouletteOffset(&stplyr->itemRoulette, rendertimefrac, 0); } else { // I'm doing this a little weird and drawing mostly in reverse order // The only actual reason is to make sneakers line up this way in the code below // This shouldn't have any actual baring over how it functions // Hyudoro is first, because we're drawing it on top of the player's current item localcolor[1] = SKINCOLOR_NONE; rouletteOffset = stplyr->karthud[khud_rouletteoffset]; if (stplyr->stealingtimer < 0) { localpatch[1] = kp_hyudoro[offset]; flashOnTwo = true; } else if ((stplyr->stealingtimer > 0) && (leveltime & 2)) { localpatch[1] = kp_hyudoro[offset]; } else if (stplyr->eggmanexplode > 1) { localpatch[1] = kp_eggman[offset]; flashOnOne = true; } else if (stplyr->ballhogcharge > 0) { // itembar = stplyr->ballhogcharge; // maxl = (((stplyr->itemamount-1) * BALLHOGINCREMENT) + 1); itembar = stplyr->ballhogcharge % BALLHOGINCREMENT; maxl = BALLHOGINCREMENT; localpatch[1] = kp_ballhog[offset]; flashOnOne = true; } else if (stplyr->rocketsneakertimer > 1) { itembar = stplyr->rocketsneakertimer; maxl = (itemtime*3) - barlength; localpatch[1] = kp_rocketsneaker[offset]; flashOnOne = true; } else if (stplyr->sadtimer > 0) { localpatch[1] = kp_sadface[offset]; flashOnTwo = true; } else if (stplyr->itemRoulette.reserved > 0) { localpatch[1] = kp_nodraw; } else { if (stplyr->itemamount <= 0) return; switch (stplyr->itemtype) { case KITEM_INVINCIBILITY: localpatch[1] = localinv; localbg = kp_itembg[offset+1]; break; case KITEM_ORBINAUT: localpatch[1] = kp_orbinaut[(offset ? 4 : std::min(stplyr->itemamount-1, 3))]; break; case KITEM_SPB: case KITEM_LIGHTNINGSHIELD: case KITEM_BUBBLESHIELD: case KITEM_FLAMESHIELD: localbg = kp_itembg[offset+1]; /*FALLTHRU*/ default: localpatch[1] = K_GetCachedItemPatch(stplyr->itemtype, offset); if (localpatch[1] == NULL) localpatch[1] = kp_nodraw; // diagnose underflows break; } if ((stplyr->itemflags & IF_ITEMOUT)) flashOnOne = true; } if (!cv_reducevfx.value) { if (flashOnOne && !(leveltime & 1)) localpatch[1] = kp_nodraw; else if (flashOnTwo && !(leveltime & 2)) localpatch[1] = kp_nodraw; } if (stplyr->karthud[khud_itemblink] && (leveltime & 1) && !(cv_reducevfx.value)) { colormode[1] = TC_BLINK; switch (stplyr->karthud[khud_itemblinkmode]) { case 2: localcolor[1] = static_cast<skincolornum_t>(K_RainbowColor(leveltime)); break; case 1: localcolor[1] = SKINCOLOR_RED; break; default: localcolor[1] = SKINCOLOR_WHITE; break; } } else { // Hide the other items. // Effectively lets the other roulette items // show flicker away after you select. localpatch[0] = localpatch[2] = kp_nodraw; } } // pain and suffering defined below if (offset) { if (!(R_GetViewNumber() & 1)) // If we are P1 or P3... { fx = ITEM_X; fy = ITEM_Y; fflags = V_SNAPTOLEFT|V_SNAPTOTOP|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { fx = ITEM2_X; fy = ITEM2_Y; fflags = V_SNAPTORIGHT|V_SNAPTOTOP|V_SPLITSCREEN; flipamount = true; } rouletteSpace = ROULETTE_SPACING_SPLITSCREEN; rouletteOffset = FixedMul(rouletteOffset, FixedDiv(ROULETTE_SPACING_SPLITSCREEN, ROULETTE_SPACING)); rouletteCrop.x = 16; rouletteCrop.y = 15; } else { fx = ITEM_X; fy = ITEM_Y; fflags = V_SNAPTOTOP|V_SNAPTOLEFT|V_SPLITSCREEN; } if (r_splitscreen == 1) { fy -= 5; } V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|fflags, localbg); // Need to draw these in a particular order, for sorting. V_SetClipRect( (fx + rouletteCrop.x) << FRACBITS, (fy + rouletteCrop.y) << FRACBITS, rouletteSpace, rouletteSpace, V_SLIDEIN|fflags ); auto draw_item = [&](fixed_t y, int i) { const UINT8 *colormap = (localcolor[i] ? R_GetTranslationColormap(colormode[i], localcolor[i], GTC_CACHE) : NULL); V_DrawFixedPatch( fx<<FRACBITS, (fy<<FRACBITS) + rouletteOffset + y, FRACUNIT, V_HUDTRANS|V_SLIDEIN|fflags, localpatch[i], colormap ); if (localamt[i] > 1) { using srb2::Draw; Draw( fx + rouletteCrop.x + FixedToFloat(rouletteSpace/2), fy + rouletteCrop.y + FixedToFloat(rouletteOffset + y + rouletteSpace) - (r_splitscreen > 1 ? 15 : 33)) .font(r_splitscreen > 1 ? Draw::Font::kRollingNum4P : Draw::Font::kRollingNum) .align(Draw::Align::kCenter) .flags(V_HUDTRANS|V_SLIDEIN|fflags) .colormap(colormap) .text("{}", localamt[i]); } }; draw_item(rouletteSpace, 0); draw_item(-rouletteSpace, 2); if (stplyr->itemRoulette.active == true) { // Draw the item underneath the box. draw_item(0, 1); V_ClearClipRect(); } else { // Draw the item above the box. V_ClearClipRect(); // A little goofy, but helps with ballhog charge conveyance—you're "loading" them. UINT8 fakeitemamount = stplyr->itemamount - (stplyr->ballhogcharge / BALLHOGINCREMENT); boolean transflag = V_HUDTRANS; if (cv_reducevfx.value && (flashOnOne || flashOnTwo)) { transflag = V_HUDTRANSHALF; } if (fakeitemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) { // Then, the numbers: V_DrawScaledPatch( fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset] ); // flip this graphic for p2 and p4 in split and shift it. V_DrawFixedPatch( fx<<FRACBITS, (fy<<FRACBITS) + rouletteOffset, FRACUNIT, transflag|V_SLIDEIN|fflags, localpatch[1], (localcolor[1] ? R_GetTranslationColormap(colormode[1], localcolor[1], GTC_CACHE) : NULL) ); if (offset) { if (flipamount) // reminder that this is for 3/4p's right end of the screen. V_DrawString(fx+2, fy+31, V_HUDTRANS|V_SLIDEIN|fflags, va("x%d", fakeitemamount)); else V_DrawString(fx+24, fy+31, V_HUDTRANS|V_SLIDEIN|fflags, va("x%d", fakeitemamount)); } else { V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|V_SLIDEIN|fflags, kp_itemx); V_DrawTimerString(fx+38, fy+36, V_HUDTRANS|V_SLIDEIN|fflags, va("%d", fakeitemamount)); } } else { V_DrawFixedPatch( fx<<FRACBITS, (fy<<FRACBITS) + rouletteOffset, FRACUNIT, transflag|V_SLIDEIN|fflags, localpatch[1], (localcolor[1] ? R_GetTranslationColormap(colormode[1], localcolor[1], GTC_CACHE) : NULL) ); } } // Extensible meter, currently only used for rocket sneaker... if (itembar) { const INT32 fill = ((itembar*barlength)/maxl); const INT32 length = std::min(barlength, fill); const INT32 height = (offset ? 1 : 2); const INT32 x = (offset ? 17 : 11), y = (offset ? 27 : 35); V_DrawScaledPatch(fx+x, fy+y, V_HUDTRANS|V_SLIDEIN|fflags, kp_itemtimer[offset]); // The left dark "AA" edge V_DrawFill(fx+x+1, fy+y+1, (length == 2 ? 2 : 1), height, 12|fflags); // The bar itself if (length > 2) { V_DrawFill(fx+x+length, fy+y+1, 1, height, 12|fflags); // the right one if (height == 2) V_DrawFill(fx+x+2, fy+y+2, length-2, 1, 8|fflags); // the dulled underside V_DrawFill(fx+x+2, fy+y+1, length-2, 1, 0|fflags); // the shine } } // Quick Eggman numbers if (stplyr->eggmanexplode > 1) V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|V_SLIDEIN|fflags, kp_eggnum[std::min<INT32>(5, G_TicsToSeconds(stplyr->eggmanexplode))]); if (stplyr->itemtype == KITEM_FLAMESHIELD && stplyr->flamelength > 0) { INT32 numframes = FLAMESHIELD_MAX; INT32 absolutemax = numframes; INT32 flamemax = stplyr->flamelength; INT32 flamemeter = std::min(static_cast<INT32>(stplyr->flamemeter), flamemax); INT32 bf = numframes - stplyr->flamelength; INT32 ff = numframes - ((flamemeter * numframes) / absolutemax); INT32 xo = 6, yo = 4; INT32 flip = 0; if (offset) { xo++; if (!(R_GetViewNumber() & 1)) // Flip for P1 and P3 (yes, that's correct) { xo -= 62; flip = V_FLIP; } } /* INT32 fmin = (8 * (bf-1)); if (ff < fmin) ff = fmin; */ if (bf >= 0 && bf < numframes) V_DrawScaledPatch(fx-xo, fy-yo, V_HUDTRANS|V_SLIDEIN|fflags|flip, kp_flameshieldmeter_bg[bf][offset]); if (ff >= 0 && ff < numframes && stplyr->flamemeter > 0) { if ((stplyr->flamemeter > flamemax) && (leveltime & 1)) { UINT8 *fsflash = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); V_DrawMappedPatch(fx-xo, fy-yo, V_HUDTRANS|V_SLIDEIN|fflags|flip, kp_flameshieldmeter[ff][offset], fsflash); } else { V_DrawScaledPatch(fx-xo, fy-yo, V_HUDTRANS|V_SLIDEIN|fflags|flip, kp_flameshieldmeter[ff][offset]); } } } } static void K_drawKartSlotMachine(void) { // ITEM_X = BASEVIDWIDTH-50; // 270 // ITEM_Y = 24; // 24 // Why write V_DrawScaledPatch calls over and over when they're all the same? // Set to 'no item' just in case. const UINT8 offset = ((r_splitscreen > 1) ? 1 : 0); patch_t *localpatch[3] = { kp_nodraw, kp_nodraw, kp_nodraw }; patch_t *localbg = offset ? kp_ringbg[1] : kp_ringbg[0]; // == SHITGARBAGE UNLIMITED 2: RISE OF MY ASS == // FIVE LAYERS OF BULLSHIT PER-PIXEL SHOVING BECAUSE THE PATCHES HAVE DIFFERENT OFFSETS // IF YOU ARE HERE TO ADJUST THE RINGBOX HUD TURN OFF YOUR COMPUTER AND GO TO YOUR LOCAL PARK INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... INT32 boxoffx = 0; INT32 boxoffy = -6; INT32 vstretch = 0; INT32 hstretch = 3; INT32 splitbsx = 0, splitbsy = 0; skincolornum_t localcolor[3] = { static_cast<skincolornum_t>(stplyr->skincolor) }; SINT8 colormode[3] = { TC_RAINBOW }; fixed_t rouletteOffset = 0; fixed_t rouletteSpace = SLOT_SPACING; vector2_t rouletteCrop = {10, 10}; INT32 i; if (stplyr->itemRoulette.itemListLen > 0) { // Init with item roulette stuff. for (i = 0; i < 3; i++) { const SINT8 indexOfs = i-1; const size_t index = (stplyr->itemRoulette.itemListLen + (stplyr->itemRoulette.index + indexOfs)) % stplyr->itemRoulette.itemListLen; const SINT8 result = stplyr->itemRoulette.itemList[index]; localpatch[i] = K_GetCachedSlotMachinePatch(result, offset); } } if (stplyr->itemRoulette.active == true) { rouletteOffset = K_GetSlotOffset(&stplyr->itemRoulette, rendertimefrac, 0); } else { rouletteOffset = stplyr->karthud[khud_rouletteoffset]; if (!stplyr->ringboxdelay) { return; } } if (stplyr->karthud[khud_itemblink] && (leveltime & 1) && !cv_reducevfx.value) { colormode[1] = TC_BLINK; localcolor[1] = SKINCOLOR_WHITE; // This looks kinda wild with the white-background patch. /* switch (stplyr->ringboxaward) { case 5: // JACKPOT! localcolor[1] = K_RainbowColor(leveltime); break; default: localcolor[1] = SKINCOLOR_WHITE; break; } */ } // pain and suffering defined below if (offset) { boxoffx -= 4; if (!(R_GetViewNumber() & 1)) // If we are P1 or P3... { fx = ITEM_X + 10; fy = ITEM_Y + 10; fflags = V_SNAPTOLEFT|V_SNAPTOTOP|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { fx = ITEM2_X + 7; fy = ITEM2_Y + 10; fflags = V_SNAPTORIGHT|V_SNAPTOTOP|V_SPLITSCREEN; } rouletteSpace = SLOT_SPACING_SPLITSCREEN; rouletteOffset = FixedMul(rouletteOffset, FixedDiv(SLOT_SPACING_SPLITSCREEN, SLOT_SPACING)); rouletteCrop.x = 16; rouletteCrop.y = 13; splitbsx = -6; splitbsy = -6; boxoffy += 2; hstretch = 0; } else { fx = ITEM_X; fy = ITEM_Y; fflags = V_SNAPTOTOP|V_SNAPTOLEFT|V_SPLITSCREEN; } if (r_splitscreen == 1) { fy -= 5; } V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|fflags, localbg); V_SetClipRect( ((fx + rouletteCrop.x + boxoffx + splitbsx) << FRACBITS), ((fy + rouletteCrop.y + boxoffy - vstretch + splitbsy) << FRACBITS), rouletteSpace + (hstretch<<FRACBITS), rouletteSpace + (vstretch<<FRACBITS), V_SLIDEIN|fflags ); // item box has special layering, transparency, different sized patches, other fucked up shit // ring box is evenly spaced and easy rouletteOffset += rouletteSpace; for (i = 0; i < 3; i++) { V_DrawFixedPatch( ((fx)<<FRACBITS), ((fy)<<FRACBITS) + rouletteOffset, FRACUNIT, V_HUDTRANS|V_SLIDEIN|fflags, localpatch[i], (localcolor[i] ? R_GetTranslationColormap(colormode[i], localcolor[i], GTC_CACHE) : NULL) ); rouletteOffset -= rouletteSpace; } V_ClearClipRect(); } tic_t K_TranslateTimer(tic_t drawtime, UINT8 mode, INT32 *return_jitter) { INT32 jitter = 0; if (!mode && drawtime != UINT32_MAX) { if (timelimitintics > 0) { if (drawtime >= timelimitintics) { jitter = 2; if (drawtime & 2) jitter = -jitter; drawtime = 0; } else { drawtime = timelimitintics - drawtime; if (secretextratime) ; else if (extratimeintics) { jitter = 2; if (leveltime & 1) jitter = -jitter; } else if (drawtime <= 5*TICRATE) { jitter = ((drawtime <= 3*TICRATE) && (((drawtime-1) % TICRATE) >= TICRATE-2)) ? 3 : 1; if (drawtime & 2) jitter = -jitter; } } } } if (return_jitter) { *return_jitter = jitter; } return drawtime; } INT32 K_drawKartMicroTime(const char *todrawtext, INT32 workx, INT32 worky, INT32 splitflags) { using srb2::Draw; Draw::TextElement text(todrawtext); text.flags(splitflags); text.font(Draw::Font::kZVote); INT32 result = text.width(); Draw(workx - result, worky).text(text); return result; } void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT32 splitflags, UINT8 mode) { // TIME_X = BASEVIDWIDTH-124; // 196 // TIME_Y = 6; // 6 INT32 jitter = 0; drawtime = K_TranslateTimer(drawtime, mode, &jitter); V_DrawScaledPatch(TX, TY, splitflags, ((mode == 2) ? kp_lapstickerwide : kp_timestickerwide)); TX += 33; if (drawtime == UINT32_MAX) ; else if (mode && !drawtime) { // apostrophe location _'__ __ V_DrawTimerString(TX+24, TY+3, splitflags, va("'")); // quotation mark location _ __"__ V_DrawTimerString(TX+60, TY+3, splitflags, va("\"")); } else { tic_t worktime = drawtime/(60*TICRATE); if (worktime >= 100) { jitter = (drawtime & 1 ? 1 : -1); worktime = 99; drawtime = (100*(60*TICRATE))-1; } // minutes time 00 __ __ V_DrawTimerString(TX, TY+3+jitter, splitflags, va("%d", worktime/10)); V_DrawTimerString(TX+12, TY+3-jitter, splitflags, va("%d", worktime%10)); // apostrophe location _'__ __ V_DrawTimerString(TX+24, TY+3, splitflags, va("'")); worktime = (drawtime/TICRATE % 60); // seconds time _ 00 __ V_DrawTimerString(TX+36, TY+3+jitter, splitflags, va("%d", worktime/10)); V_DrawTimerString(TX+48, TY+3-jitter, splitflags, va("%d", worktime%10)); // quotation mark location _ __"__ V_DrawTimerString(TX+60, TY+3, splitflags, va("\"")); worktime = G_TicsToCentiseconds(drawtime); // tics _ __ 00 V_DrawTimerString(TX+72, TY+3+jitter, splitflags, va("%d", worktime/10)); V_DrawTimerString(TX+84, TY+3-jitter, splitflags, va("%d", worktime%10)); } // Medal data! if ((modeattacking || (mode == 1)) && !demo.playback) { INT32 workx = TX + 96, worky = TY+18; UINT8 i = stickermedalinfo.visiblecount; if (stickermedalinfo.targettext[0] != '\0') { if (!mode) { if (stickermedalinfo.jitter) { jitter = stickermedalinfo.jitter+3; if (jitter & 2) workx += jitter/4; else workx -= jitter/4; } if (stickermedalinfo.norecord == true) { splitflags = (splitflags &~ V_HUDTRANS)|V_HUDTRANSHALF; } } workx -= K_drawKartMicroTime(stickermedalinfo.targettext, workx, worky, splitflags); } workx -= (((1 + i - stickermedalinfo.platinumcount)*6) - 1); if (!mode) splitflags = (splitflags &~ V_HUDTRANSHALF)|V_HUDTRANS; while (i > 0) { i--; if (gamedata->collected[(stickermedalinfo.emblems[i]-emblemlocations)]) { V_DrawMappedPatch(workx, worky, splitflags, static_cast<patch_t*>(W_CachePatchName(M_GetEmblemPatch(stickermedalinfo.emblems[i], false), PU_CACHE)), R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(stickermedalinfo.emblems[i]), GTC_CACHE) ); workx += 6; } else if ( stickermedalinfo.emblems[i]->type != ET_TIME || stickermedalinfo.emblems[i]->tag != AUTOMEDAL_PLATINUM ) { V_DrawMappedPatch(workx, worky, splitflags, static_cast<patch_t*>(W_CachePatchName("NEEDIT", PU_CACHE)), NULL ); workx += 6; } } } if (modeattacking & ATTACKING_SPB && stplyr->SPBdistance > 0) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, static_cast<skincolornum_t>(stplyr->skincolor), GTC_CACHE); INT32 ybar = 180; INT32 widthbar = 120, xbar = 160 - widthbar/2, currentx; INT32 barflags = V_SNAPTOBOTTOM|V_SLIDEIN; INT32 transflags = ((6)<<FF_TRANSSHIFT); V_DrawScaledPatch(xbar, ybar - 2, barflags|transflags, kp_wouldyoustillcatchmeifiwereaworm); V_DrawMappedPatch(160 + widthbar/2 - 7, ybar - 7, barflags, faceprefix[stplyr->skin][FACE_MINIMAP], colormap); // vibes-based math currentx = (stplyr->SPBdistance/mapobjectscale - mobjinfo[MT_SPB].radius/FRACUNIT - mobjinfo[MT_PLAYER].radius/FRACUNIT) * 6; if (currentx > 0) { currentx = sqrt(currentx); if (currentx > widthbar) currentx = widthbar; } else { currentx = 0; } V_DrawScaledPatch(160 + widthbar/2 - currentx - 5, ybar - 7, barflags, kp_spbminimap); } } static fixed_t K_DrawKartPositionNumPatch(UINT8 num, UINT8 splitIndex, UINT8 *color, fixed_t x, fixed_t y, fixed_t scale, INT32 flags) { fixed_t w = FRACUNIT; fixed_t h = FRACUNIT; INT32 overlayFlags[2]; INT32 i; if (num > 9) { return x; // invalid input } if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM) { overlayFlags[0] = V_SUBTRACT; overlayFlags[1] = V_ADD; } else { overlayFlags[0] = V_ADD; overlayFlags[1] = V_SUBTRACT; } w = SHORT(kp_positionnum[num][0][splitIndex]->width) * scale; h = SHORT(kp_positionnum[num][0][splitIndex]->height) * scale; x -= w; if (flags & V_SNAPTOBOTTOM) { y -= h; } for (i = 1; i >= 0; i--) { V_DrawFixedPatch( x, y, scale, flags | overlayFlags[i], kp_positionnum[num][i][splitIndex], color ); } return x; } void K_DrawKartPositionNumXY( UINT8 num, UINT8 splitIndex, fixed_t fx, fixed_t fy, fixed_t scale, INT32 fflags, tic_t counter, boolean subtract, boolean exit, boolean lastLap, boolean losing ) { if (cv_reducevfx.value != 0) { // Reduce the flashing rate counter /= 4; } counter /= 3; // Alternate colors every three frames UINT8 *color = NULL; if (exit && num == 1) { // 1st place winner? You get rainbows!! color = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(SKINCOLOR_POSNUM_BEST1 + (counter % 6)), GTC_CACHE); } else if (exit || lastLap) { // On the final lap, or already won. boolean useRedNums = losing; if (subtract) { // Subtracting RED will look BLUE, and vice versa. useRedNums = !useRedNums; } if (useRedNums == true) { color = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(SKINCOLOR_POSNUM_LOSE1 + (counter % 3)), GTC_CACHE); } else { color = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(SKINCOLOR_POSNUM_WIN1 + (counter % 3)), GTC_CACHE); } } else { color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM, GTC_CACHE); } if ((fflags & V_SNAPTORIGHT) == 0) { UINT8 adjustNum = num; do { fixed_t w = SHORT(kp_positionnum[adjustNum % 10][0][splitIndex]->width) * scale; fx += w; adjustNum /= 10; } while (adjustNum); } // Draw the number do { fx = K_DrawKartPositionNumPatch( (num % 10), splitIndex, color, fx, fy, scale, V_SPLITSCREEN|fflags ); num /= 10; } while (num); } static void K_DrawKartPositionNum(UINT8 num) { UINT8 splitIndex = (r_splitscreen > 0) ? 1 : 0; fixed_t scale = FRACUNIT; fixed_t fx = 0, fy = 0; transnum_t trans = static_cast<transnum_t>(0); INT32 fflags = 0; if (stplyr->lives <= 0 && stplyr->playerstate == PST_DEAD) { return; } if (leveltime < (starttime + NUMTRANSMAPS)) { trans = static_cast<transnum_t>((starttime + NUMTRANSMAPS) - leveltime); } if (trans >= NUMTRANSMAPS) { return; } if (stplyr->positiondelay > 0 || K_PlayerTallyActive(stplyr) == true) { const UINT8 delay = (stplyr->exiting) ? POS_DELAY_TIME : stplyr->positiondelay; const fixed_t add = (scale * 3) >> ((r_splitscreen == 1) ? 1 : 2); scale += std::min((add * (delay * delay)) / (POS_DELAY_TIME * POS_DELAY_TIME), add); } // pain and suffering defined below if (!r_splitscreen) { fx = BASEVIDWIDTH << FRACBITS; fy = BASEVIDHEIGHT << FRACBITS; fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; } else if (r_splitscreen == 1) // for this splitscreen, we'll use case by case because it's a bit different. { fx = BASEVIDWIDTH << FRACBITS; if (R_GetViewNumber() == 0) { // for player 1: display this at the top right, above the minimap. fy = 0; fflags = V_SNAPTOTOP|V_SNAPTORIGHT; } else { // if we're not p1, that means we're p2. display this at the bottom right, below the minimap. fy = BASEVIDHEIGHT << FRACBITS; fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; } fy >>= 1; } else { fy = BASEVIDHEIGHT << FRACBITS; if (!(R_GetViewNumber() & 1)) // If we are P1 or P3... { // If we are P1 or P3... fx = 0; fflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM; } else { // else, that means we're P2 or P4. fx = BASEVIDWIDTH << FRACBITS; fflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM; } fx >>= 1; fy >>= 1; // We're putting it in the same corner as // the rest of our HUD, so it needs raised. fy -= (21 << FRACBITS); } if (trans > 0) { fflags |= (trans << V_ALPHASHIFT); } K_DrawKartPositionNumXY( num, splitIndex, fx, fy, scale, fflags, leveltime, ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM), stplyr->exiting, (stplyr->laps >= numlaps), K_IsPlayerLosing(stplyr) ); } struct PositionFacesInfo { INT32 ranklines = 0; INT32 strank = -1; INT32 numplayersingame = 0; INT32 rankplayer[MAXPLAYERS] = {}; PositionFacesInfo(); void draw_1p(); void draw_4p_battle(int x, int y, INT32 flags); player_t* top() const { return &players[rankplayer[0]]; } UINT32 top_score() const { return top()->roundscore; } bool near_goal() const { if (g_pointlimit == 0) return false; constexpr tic_t kThreshold = 5; return std::max(kThreshold, g_pointlimit) - kThreshold <= top_score(); } skincolornum_t vomit_color() const { if (!near_goal()) { return static_cast<skincolornum_t>(top()->skincolor); } constexpr int kCycleSpeed = 4; constexpr std::array<skincolornum_t, 6> kColors = { SKINCOLOR_RED, SKINCOLOR_VOMIT, SKINCOLOR_YELLOW, SKINCOLOR_GREEN, SKINCOLOR_JET, SKINCOLOR_MOONSET, }; return kColors[leveltime / kCycleSpeed % kColors.size()]; } }; PositionFacesInfo::PositionFacesInfo() { INT32 i, j; for (i = 0; i < MAXPLAYERS; i++) { rankplayer[i] = -1; if (!playeringame[i] || players[i].spectator || !players[i].mo) continue; numplayersingame++; } if (numplayersingame <= 1) return; boolean completed[MAXPLAYERS] = {}; for (j = 0; j < numplayersingame; j++) { UINT8 lowestposition = MAXPLAYERS+1; for (i = 0; i < MAXPLAYERS; i++) { if (completed[i] || !playeringame[i] || players[i].spectator || !players[i].mo) continue; if (players[i].position >= lowestposition) continue; rankplayer[ranklines] = i; lowestposition = players[i].position; } i = rankplayer[ranklines]; completed[i] = true; if (players+i == stplyr) strank = ranklines; //if (ranklines == 5) //break; // Only draw the top 5 players -- we do this a different way now... ranklines++; } } void PositionFacesInfo::draw_1p() { // FACE_X = 15; // 15 // FACE_Y = 72; // 72 INT32 Y = FACE_Y-9; // -9 to offset where it's being drawn if there are more than one INT32 i, j; INT32 bumperx, emeraldx; INT32 xoff, yoff, flipflag = 0; UINT8 workingskin; UINT8 *colormap; UINT32 skinflags; if (gametyperules & GTR_POINTLIMIT) // playing battle { Y += 40; if (ranklines < 3) Y -= 18; } else if (ranklines < 5) Y += (9*ranklines); else Y += (9*5); ranklines--; i = ranklines; if (gametyperules & GTR_POINTLIMIT) // playing battle { // 3 lines max in Battle if (i > 2) i = 2; ranklines = 0; // You must appear on the leaderboard, even if you don't rank top 3 if (strank > i) { strank = i; rankplayer[strank] = stplyr - players; } // Draw GOAL bool skull = g_pointlimit && (g_pointlimit <= stplyr->roundscore); INT32 height = i*18; INT32 GOAL_Y = Y-height; colormap = nullptr; if (skincolornum_t vomit = vomit_color()) { colormap = R_GetTranslationColormap(TC_DEFAULT, vomit, GTC_CACHE); } V_DrawMappedPatch(FACE_X-5, GOAL_Y-32, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_goal[skull][0], colormap); // Flashing KO if (skull) { if (leveltime % 16 < 8) V_DrawScaledPatch(FACE_X-5, GOAL_Y-32, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_goaltext1p); } else if (g_pointlimit) { using srb2::Draw; Draw(FACE_X+8.5, GOAL_Y-15) .font(Draw::Font::kZVote) .align(Draw::Align::kCenter) .flags(V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT) .text("{:02}", g_pointlimit); } // Line cutting behind rank faces V_DrawScaledPatch(FACE_X+6, GOAL_Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_goalrod[0]); } else if (strank <= 2) // too close to the top, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) { if (i > 4) // could be both... i = 4; ranklines = 0; } else if (strank+2 >= ranklines) // too close to the bottom? { ranklines -= 4; if (ranklines < 0) ranklines = 0; } else { i = strank+2; ranklines = strank-2; } for (; i >= ranklines; i--) { if (!playeringame[rankplayer[i]]) continue; if (players[rankplayer[i]].spectator) continue; if (!players[rankplayer[i]].mo) continue; bumperx = FACE_X+19; emeraldx = FACE_X+16; skinflags = (demo.playback) ? demo.skinlist[demo.currentskinid[rankplayer[i]]].flags : skins[players[rankplayer[i]].skin].flags; // Flip SF_IRONMAN portraits, but only if they're transformed if (skinflags & SF_IRONMAN && !(players[rankplayer[i]].charflags & SF_IRONMAN) ) { flipflag = V_FLIP|V_VFLIP; // blonic flip xoff = yoff = 16; } else { flipflag = 0; xoff = yoff = 0; } if (players[rankplayer[i]].mo->color) { if ((skin_t*)players[rankplayer[i]].mo->skin) workingskin = (skin_t*)players[rankplayer[i]].mo->skin - skins; else workingskin = players[rankplayer[i]].skin; colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[rankplayer[i]].mo->color), GTC_CACHE); if (players[rankplayer[i]].mo->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(players[rankplayer[i]].mo->color), GTC_CACHE); else colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[rankplayer[i]].mo->color), GTC_CACHE); V_DrawMappedPatch(FACE_X + xoff, Y + yoff, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT|flipflag, faceprefix[workingskin][FACE_RANK], colormap); if (LUA_HudEnabled(hud_battlebumpers)) { const UINT8 bumpers = K_Bumpers(&players[rankplayer[i]]); if (bumpers > 0) { V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_tinybumper[0], colormap); for (j = 1; j < bumpers; j++) { bumperx += 5; V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_tinybumper[1], colormap); } } } } for (j = 0; j < 7; j++) { UINT32 emeraldFlag = (1 << j); skincolornum_t emeraldColor = static_cast<skincolornum_t>(SKINCOLOR_CHAOSEMERALD1 + j); if (players[rankplayer[i]].emeralds & emeraldFlag) { colormap = R_GetTranslationColormap(TC_DEFAULT, emeraldColor, GTC_CACHE); V_DrawMappedPatch(emeraldx, Y+7, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_rankemerald, colormap); emeraldx += 7; } } if (i == strank) V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); if ((gametyperules & GTR_BUMPERS) && (players[rankplayer[i]].pflags & PF_ELIMINATED)) V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_ranknobumpers); else if (K_Cooperative()) ; else if (gametyperules & GTR_CIRCUIT) { INT32 pos = players[rankplayer[i]].position; if (pos < 0 || pos > MAXPLAYERS) pos = 0; // Draws the little number over the face V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); } else if (gametyperules & GTR_POINTLIMIT) { INT32 flags = V_HUDTRANS | V_SLIDEIN | V_SNAPTOLEFT; colormap = NULL; if (g_pointlimit && g_pointlimit <= players[rankplayer[i]].roundscore) { if (leveltime % 8 < 4) { colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_TANGERINE, GTC_CACHE); } flags |= V_STRINGDANCE; } V_DrawStringScaled( (FACE_X - 5) * FRACUNIT, (Y + 10) * FRACUNIT, FRACUNIT, FRACUNIT, FRACUNIT, flags, colormap, PINGF_FONT, va("%d", players[rankplayer[i]].roundscore) ); } Y -= 18; } } void PositionFacesInfo::draw_4p_battle(int x, int y, INT32 flags) { using srb2::Draw; Draw row = Draw(x, y).flags(V_HUDTRANS | V_SLIDEIN | flags).font(Draw::Font::kPing); UINT8 skull = [] { if (g_pointlimit == 0) return 0; int party = G_PartySize(consoleplayer); for (int i = 0; i < party; ++i) { // Is any party member about to win? if (g_pointlimit <= players[G_PartyMember(consoleplayer, i)].roundscore) { return 1; } } return 0; }(); skincolornum_t vomit = vomit_color(); (vomit ? row.colormap(vomit) : row).patch(kp_goal[skull][1]); if (!skull && g_pointlimit) { row.xy(8.5, 5).align(Draw::Align::kCenter).text("{:02}", g_pointlimit); } row.xy(7, 18).patch(kp_goalrod[1]); auto head = [&](Draw col, int i) { const player_t& p = players[rankplayer[i]]; col.colormap(p.skin, static_cast<skincolornum_t>(p.skincolor)).patch(faceprefix[p.skin][FACE_MINIMAP]); bool dance = g_pointlimit && (g_pointlimit <= p.roundscore); bool flash = dance && leveltime % 8 < 4; ( flash ? col.xy(8, 6).colorize(SKINCOLOR_TANGERINE).flags(V_STRINGDANCE) : col.xy(8, 6).flags(dance ? V_STRINGDANCE : 0) ).text("{:02}", p.roundscore); }; // Draw top 2 players head(row.xy(2, 31), 1); head(row.xy(2, 18), 0); } static boolean K_drawKartPositionFaces(void) { PositionFacesInfo state{}; if (state.numplayersingame <= 1) return true; if (!LUA_HudEnabled(hud_minirankings)) return false; // Don't proceed but still return true for free play above if HUD is disabled. switch (r_splitscreen) { case 0: state.draw_1p(); break; case 1: state.draw_4p_battle(292, 78, V_SNAPTORIGHT); break; case 2: case 3: state.draw_4p_battle(152, 9, V_SNAPTOTOP); state.draw_4p_battle(152, 147, V_SNAPTOBOTTOM); break; } return false; } static void K_drawBossHealthBar(void) { UINT8 i = 0, barstatus = 1, randlen = 0, darken = 0; const INT32 startx = BASEVIDWIDTH - 23; INT32 starty = BASEVIDHEIGHT - 25; INT32 rolrand = 0, randtemp = 0; boolean randsign = false; if (bossinfo.barlen <= 1) return; // Entire bar juddering! if (lt_exitticker < (TICRATE/2)) ; else if (bossinfo.visualbarimpact) { INT32 mag = std::min<UINT32>((bossinfo.visualbarimpact/4) + 1, 8u); if (bossinfo.visualbarimpact & 1) starty -= mag; else starty += mag; } if ((lt_ticker >= lt_endtime) && bossinfo.enemyname) { if (lt_exitticker == 0) { rolrand = 5; } else if (lt_exitticker == 1) { rolrand = 7; } else { rolrand = 10; } V_DrawRightAlignedThinString(startx, starty-rolrand, V_FORCEUPPERCASE|V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, bossinfo.enemyname); rolrand = 0; } // Used for colour and randomisation. if (bossinfo.healthbar <= (bossinfo.visualdiv/FRACUNIT)) { barstatus = 3; } else if (bossinfo.healthbar <= bossinfo.healthbarpinch) { barstatus = 2; } randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); if (randtemp > 0) randlen = P_RandomKey(PR_INTERPHUDRANDOM, randtemp)+1; randsign = P_RandomChance(PR_INTERPHUDRANDOM, FRACUNIT/2); // Right wing. V_DrawScaledPatch(startx-1, starty, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_FLIP, kp_bossbar[0]); // Draw the bar itself... while (i < bossinfo.barlen) { V_DrawScaledPatch(startx-i, starty, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[1]); if (i < bossinfo.visualbar) { randlen--; if (!randlen) { randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); if (randtemp > 0) randlen = P_RandomKey(PR_INTERPHUDRANDOM, randtemp)+1; if (barstatus > 1) { rolrand = P_RandomKey(PR_INTERPHUDRANDOM, barstatus)+1; } else { rolrand = 1; } if (randsign) { rolrand = -rolrand; } randsign = !randsign; } else { rolrand = 0; } if (lt_exitticker < (TICRATE/2)) ; else if ((bossinfo.visualbar - i) < (INT32)(bossinfo.visualbarimpact/8)) { if (bossinfo.visualbarimpact & 1) rolrand += (bossinfo.visualbar - i); else rolrand -= (bossinfo.visualbar - i); } if (bossinfo.visualdiv) { fixed_t work = 0; if ((i+1) == bossinfo.visualbar) darken = 1; else { darken = 0; // a hybrid fixed-int modulo... while ((work/FRACUNIT) < bossinfo.visualbar) { if (work/FRACUNIT != i) { work += bossinfo.visualdiv; continue; } darken = 1; break; } } } V_DrawScaledPatch(startx-i, starty+rolrand, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[(2*barstatus)+darken]); } i++; } // Left wing. V_DrawScaledPatch(startx-i, starty, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[0]); } static void K_drawKartEmeralds(void) { static const INT32 emeraldOffsets[7][3] = { {34, 0, 15}, {25, 8, 11}, {43, 8, 19}, {16, 0, 7}, {52, 0, 23}, { 7, 8, 3}, {61, 8, 27} }; INT32 splitflags = V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN; INT32 startx = BASEVIDWIDTH - 77; INT32 starty = BASEVIDHEIGHT - 29; INT32 i = 0, xindex = 0; { if (r_splitscreen) { starty = (starty/2) - 8; } starty -= 8; if (r_splitscreen < 2) { startx -= 8; if (r_splitscreen == 1 && R_GetViewNumber() == 0) { starty = 1; } V_DrawScaledPatch(startx, starty, V_HUDTRANS|splitflags, kp_rankemeraldback); } else { xindex = 2; starty -= 15; if (!(R_GetViewNumber() & 1)) // If we are P1 or P3... { startx = LAPS_X; splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { startx = LAPS2_X + 1; splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } } } for (i = 0; i < 7; i++) { UINT32 emeraldFlag = (1 << i); skincolornum_t emeraldColor = static_cast<skincolornum_t>(SKINCOLOR_CHAOSEMERALD1 + i); if (stplyr->emeralds & emeraldFlag) { boolean whiteFlash = (leveltime & 1); UINT8 *colormap; if (i & 1) { whiteFlash = !whiteFlash; } colormap = R_GetTranslationColormap(TC_DEFAULT, emeraldColor, GTC_CACHE); V_DrawMappedPatch( startx + emeraldOffsets[i][xindex], starty + emeraldOffsets[i][1], V_HUDTRANS|splitflags, kp_rankemerald, colormap ); if (whiteFlash == true) { V_DrawScaledPatch( startx + emeraldOffsets[i][xindex], starty + emeraldOffsets[i][1], V_HUDTRANSHALF|splitflags, kp_rankemeraldflash ); } } } } static void K_drawKartLaps(void) { INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; if (r_splitscreen > 1) { INT32 fx = 0, fy = 0; INT32 flipflag = 0; // pain and suffering defined below if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = LAPS_X; fy = LAPS_Y; } else { if (!(R_GetViewNumber() & 1)) // If we are P1 or P3... { fx = LAPS_X; fy = LAPS_Y; splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { fx = LAPS2_X; fy = LAPS2_Y; splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; flipflag = V_FLIP; // make the string right aligned and other shit } } // Laps V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_splitlapflag); V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|V_SLIDEIN|splitflags, frameslash); if (numlaps >= 10) { UINT8 ln[2]; ln[0] = ((stplyr->laps / 10) % 10); ln[1] = (stplyr->laps % 10); V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); ln[0] = ((numlaps / 10) % 10); ln[1] = (numlaps % 10); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); } else { V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->laps) % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(numlaps) % 10]); } } else { // Laps V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_lapsticker); V_DrawTimerString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", std::min(stplyr->laps, numlaps), numlaps)); } } #define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) static void K_DrawLivesDigits(INT32 x, INT32 y, INT32 width, INT32 flags, patch_t *font[10]) { const SINT8 tens = stplyr->lives / 10; if (tens) { V_DrawScaledPatch(x, y, flags, font[tens % 10]); x += width; } V_DrawScaledPatch(x, y, flags, font[stplyr->lives % 10]); } static void K_drawRingCounter(boolean gametypeinfoshown) { const boolean uselives = G_GametypeUsesLives(); SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; UINT8 rn[2]; INT32 ringflip = 0; UINT8 *ringmap = NULL; boolean colorring = false; INT32 ringx = 0, fy = 0; rn[0] = ((abs(stplyr->hudrings) / 10) % 10); rn[1] = (abs(stplyr->hudrings) % 10); if (stplyr->hudrings <= 0 && stplyr->ringvisualwarning > 1) { colorring = true; if ((leveltime/2 & 1)) { ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); } else { ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_WHITE, GTC_CACHE); } } else if (stplyr->hudrings <= 0 && (leveltime/5 & 1)) // In debt { ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); colorring = true; } else if (stplyr->hudrings >= 20) // Maxed out ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) { ringflip = V_FLIP; ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; ringx += SHORT((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); } if (r_splitscreen > 1) { INT32 fx = 0, fr = 0; INT32 flipflag = 0; // pain and suffering defined below if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = LAPS_X; fy = LAPS_Y; } else { if (!(R_GetViewNumber() & 1)) // If we are P1 or P3... { fx = LAPS_X; fy = LAPS_Y; splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { fx = LAPS2_X; fy = LAPS2_Y; splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; flipflag = V_FLIP; // make the string right aligned and other shit } } fr = fx; if (gametypeinfoshown) { fy -= 10; } // Rings if (!uselives) { V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[1]); if (flipflag) fr += 15; } else V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[0]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); V_DrawMappedPatch(fr+ringx, fy-3, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_smallring[ringanim_realframe], (colorring ? ringmap : NULL)); if (stplyr->hudrings < 0) // Draw the minus for ring debt { V_DrawMappedPatch(fr+11, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminussmall, ringmap); V_DrawMappedPatch(fr+15, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap); V_DrawMappedPatch(fr+19, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap); } else { V_DrawMappedPatch(fr+11, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap); V_DrawMappedPatch(fr+15, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap); } // SPB ring lock if (stplyr->pflags & PF_RINGLOCK) V_DrawScaledPatch(fr-12, fy-13, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]); // Lives if (uselives) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, static_cast<skincolornum_t>(stplyr->skincolor), GTC_CACHE); V_DrawMappedPatch(fr+21, fy-3, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_MINIMAP], colormap); if (stplyr->lives >= 0) K_DrawLivesDigits(fr+34, fy, 4, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font); } } else { fy = LAPS_Y; if (gametypeinfoshown) { fy -= 11; if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) fy -= 4; } else { fy += 9; } // Rings using srb2::Draw; Draw(LAPS_X+7, fy+1) .flags(V_HUDTRANS|V_SLIDEIN|splitflags) .align(Draw::Align::kCenter) .width(uselives ? (stplyr->lives >= 10 ? 70 : 64) : 33) .small_sticker(); V_DrawMappedPatch(LAPS_X+ringx+7, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); // "Why fy-4? Why LAPS_X+29+1?" // "use magic numbers" - jartha 2024-03-05 if (stplyr->hudrings < 0) // Draw the minus for ring debt { V_DrawMappedPatch(LAPS_X+23-1, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminus, ringmap); using srb2::Draw; Draw row = Draw(LAPS_X+29+0, fy-4).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kThinTimer).colormap(ringmap); row.text("{:02}", abs(stplyr->hudrings)); // V_DrawMappedPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[TALLNUM_FONT].font[rn[0]], ringmap); // V_DrawMappedPatch(LAPS_X+35, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[TALLNUM_FONT].font[rn[1]], ringmap); } else { using srb2::Draw; Draw row = Draw(LAPS_X+23+3, fy-4).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kThinTimer).colormap(ringmap); row.text("{:02}", abs(stplyr->hudrings)); // V_DrawMappedPatch(LAPS_X+23, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[TALLNUM_FONT].font[rn[0]], ringmap); // V_DrawMappedPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[TALLNUM_FONT].font[rn[1]], ringmap); } // SPB ring lock if (stplyr->pflags & PF_RINGLOCK) V_DrawScaledPatch(LAPS_X-5, fy-17, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); // Lives if (uselives) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, static_cast<skincolornum_t>(stplyr->skincolor), GTC_CACHE); V_DrawMappedPatch(LAPS_X+46, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_RANK], colormap); SINT8 livescount = 0; if (stplyr->lives > 0) { livescount = stplyr->lives; if (livescount > 10) livescount = 10; } using srb2::Draw; Draw row = Draw(LAPS_X+65, fy-4).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kThinTimer); row.text("{}", livescount); } } } #undef RINGANIM_FLIPFRAME static void K_drawKartAccessibilityIcons(boolean gametypeinfoshown, INT32 fx) { INT32 fy = LAPS_Y-14; INT32 splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; boolean mirror = false; fx += LAPS_X; if (r_splitscreen < 2) // adjust to speedometer height { if (battleprisons) { fy -= 2; } if (gametypeinfoshown) { fy -= 11; if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) fy -= 4; } else { fy += 9; } } else { fx = LAPS_X+44; fy = LAPS_Y; if (R_GetViewNumber() & 1) // If we are not P1 or P3... { splitflags ^= (V_SNAPTOLEFT|V_SNAPTORIGHT); fx = (BASEVIDWIDTH/2) - fx; mirror = true; } } // Kickstart Accel if (stplyr->pflags & PF_KICKSTARTACCEL) { if (mirror) fx -= 10; SINT8 col = 0, wid, fil, ofs; UINT8 i = 7; ofs = (stplyr->kickstartaccel == ACCEL_KICKSTART) ? 1 : 0; fil = i-(stplyr->kickstartaccel*i)/ACCEL_KICKSTART; V_DrawFill(fx+4, fy+ofs-1, 2, 1, 31|V_SLIDEIN|splitflags); V_DrawFill(fx, (fy+ofs-1)+8, 10, 1, 31|V_SLIDEIN|splitflags); while (i--) { wid = (i/2)+1; V_DrawFill(fx+4-wid, fy+ofs+i, 2+(wid*2), 1, 31|V_SLIDEIN|splitflags); if (fil > 0) { if (i < fil) col = 23; else if (i == fil) col = 3; else col = 5 + (i-fil)*2; } else if ((leveltime % 7) == i) col = 0; else col = 3; V_DrawFill(fx+5-wid, fy+ofs+i, (wid*2), 1, col|V_SLIDEIN|splitflags); } if (mirror) fx--; else fx += 10 + 1; } // Auto Roulette if (stplyr->pflags & PF_AUTOROULETTE) { if (mirror) fx -= 12; V_DrawScaledPatch(fx, fy-1, V_SLIDEIN|splitflags, kp_autoroulette); if (mirror) fx--; else fx += 12 + 1; } if (stplyr->pflags & PF_AUTORING) { if (mirror) fx -= 14; V_DrawScaledPatch(fx, fy-1, V_SLIDEIN|splitflags, kp_autoring); if (mirror) fx--; else fx += 14 + 1; } } static void K_drawKartSpeedometer(boolean gametypeinfoshown) { static fixed_t convSpeed; UINT8 labeln = 0; UINT8 numbers[3]; INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; INT32 fy = LAPS_Y-14; if (battleprisons) { fy -= 2; } if (!stplyr->exiting) // Keep the same speed value as when you crossed the finish line! { switch (cv_kartspeedometer.value) { case 1: // Sonic Drift 2 style percentage default: convSpeed = (stplyr->speed * 100) / K_GetKartSpeed(stplyr, false, true); // Based on top speed! labeln = 0; break; case 2: // Kilometers convSpeed = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale) / FRACUNIT; // 2.172409058 labeln = 1; break; case 3: // Miles convSpeed = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale) / FRACUNIT; // 1.349868774 labeln = 2; break; case 4: // Fracunits convSpeed = FixedDiv(stplyr->speed, mapobjectscale) / FRACUNIT; // 1.0. duh. labeln = 3; break; } } // Don't overflow // (negative speed IS really high speed :V) if (convSpeed > 999 || convSpeed < 0) convSpeed = 999; numbers[0] = ((convSpeed / 100) % 10); numbers[1] = ((convSpeed / 10) % 10); numbers[2] = (convSpeed % 10); if (gametypeinfoshown) { fy -= 11; if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) fy -= 4; } else { fy += 9; } using srb2::Draw; Draw(LAPS_X+7, fy+1).flags(V_HUDTRANS|V_SLIDEIN|splitflags).align(Draw::Align::kCenter).width(42).small_sticker(); V_DrawScaledPatch(LAPS_X+7, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[0]]); V_DrawScaledPatch(LAPS_X+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[1]]); V_DrawScaledPatch(LAPS_X+19, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[2]]); V_DrawScaledPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometerlabel[labeln]); K_drawKartAccessibilityIcons(gametypeinfoshown, 56); } static void K_drawBlueSphereMeter(boolean gametypeinfoshown) { const UINT8 maxBars = 4; // see also K_DrawNameTagSphereMeter const UINT8 segColors[] = {73, 64, 52, 54, 55, 35, 34, 33, 202, 180, 181, 182, 164, 165, 166, 153, 152}; const UINT8 sphere = std::clamp(static_cast<int>(stplyr->spheres), 0, 40); UINT8 numBars = std::min((sphere / 10), +maxBars); UINT8 colorIndex = (sphere * sizeof(segColors)) / (40 + 1); INT32 fx, fy; UINT8 i; INT32 splitflags = V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; INT32 flipflag = 0; INT32 xstep = 15; // pain and suffering defined below if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = LAPS_X; fy = LAPS_Y-4; if (battleprisons) { if (r_splitscreen == 1) { fy -= 8; } else { fy -= 5; } } else if (r_splitscreen == 1) { fy -= 5; } if (gametypeinfoshown) { fy -= 11 + 4; } else { fy += 9; } V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_spheresticker); } else { xstep = 8; if (!(R_GetViewNumber() & 1)) // If we are P1 or P3... { fx = LAPS_X-2; fy = LAPS_Y; } else // else, that means we're P2 or P4. { fx = LAPS2_X+(SHORT(kp_splitspheresticker->width) - 10); fy = LAPS2_Y; splitflags ^= V_SNAPTOLEFT|V_SNAPTORIGHT; flipflag = V_FLIP; // make the string right aligned and other shit xstep = -xstep; } if (battleprisons) { fy -= 5; } if (gametypeinfoshown) { fy -= 16; } V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_splitspheresticker); } if (r_splitscreen < 2) { fx += 25; } else { fx += (flipflag) ? -18 : 13; } for (i = 0; i <= numBars; i++) { UINT8 segLen = (r_splitscreen < 2) ? 10 : 5; if (i == numBars) { segLen = (sphere % 10); if (r_splitscreen < 2) ; else { segLen = (segLen+1)/2; // offset so nonzero spheres shows up IMMEDIATELY if (!segLen) break; if (flipflag) fx += (5-segLen); } } if (r_splitscreen < 2) { V_DrawFill(fx, fy + 6, segLen, 3, segColors[std::max(colorIndex-1, 0)] | splitflags); V_DrawFill(fx, fy + 7, segLen, 1, segColors[std::max(colorIndex-2, 0)] | splitflags); V_DrawFill(fx, fy + 9, segLen, 3, segColors[colorIndex] | splitflags); } else { V_DrawFill(fx, fy + 5, segLen, 1, segColors[std::max(colorIndex-1, 0)] | splitflags); V_DrawFill(fx, fy + 6, segLen, 1, segColors[std::max(colorIndex-2, 0)] | splitflags); V_DrawFill(fx, fy + 7, segLen, 2, segColors[colorIndex] | splitflags); } fx += xstep; } } static void K_drawKartBumpersOrKarma(void) { UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(stplyr->skincolor), GTC_CACHE); INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; if (r_splitscreen > 1) { INT32 fx = 0, fy = 0; INT32 flipflag = 0; // pain and suffering defined below if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = LAPS_X; fy = LAPS_Y; } else { if (!(R_GetViewNumber() & 1)) // If we are P1 or P3... { fx = LAPS_X; fy = LAPS_Y; splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { fx = LAPS2_X; fy = LAPS2_Y; splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; flipflag = V_FLIP; // make the string right aligned and other shit } } { using srb2::Draw; int width = 39; if (!battleprisons) { constexpr int kPad = 16; if (flipflag) fx -= kPad; width += kPad; } Draw(fx-1 + (flipflag ? width + 3 : 0), fy+1) .flags(V_HUDTRANS|V_SLIDEIN|splitflags) .align(flipflag ? Draw::Align::kRight : Draw::Align::kLeft) .width(width) .small_sticker(); } fx += 2; if (battleprisons) { V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|V_SLIDEIN|splitflags, frameslash); V_DrawMappedPatch(fx-1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankcapsule, NULL); if (numtargets > 9 || maptargets > 9) { UINT8 ln[2]; ln[0] = ((numtargets / 10) % 10); ln[1] = (numtargets % 10); V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); ln[0] = ((maptargets / 10) % 10); ln[1] = (maptargets % 10); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); } else { V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numtargets % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[maptargets % 10]); } } else { const UINT8 bumpers = K_Bumpers(stplyr); const bool dance = g_pointlimit && (g_pointlimit <= stplyr->roundscore); V_DrawMappedPatch(fx-1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankbumper, colormap); using srb2::Draw; Draw row = Draw(fx+12, fy).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kPing); row.text("{:02}", bumpers); if (dance && leveltime % 8 < 4) { row = row.colorize(SKINCOLOR_TANGERINE); } row.xy(10, -2).patch(kp_pts[1]); row .x(31) .flags(dance ? V_STRINGDANCE : 0) .text("{:02}", stplyr->roundscore); } } else { INT32 fy = r_splitscreen == 1 ? LAPS_Y-3 : LAPS_Y; if (battleprisons) { if (numtargets > 9 && maptargets > 9) V_DrawMappedPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_capsulestickerwide, NULL); else V_DrawMappedPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_capsulesticker, NULL); V_DrawTimerString(LAPS_X+47, fy+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", numtargets, maptargets)); } else { const UINT8 bumpers = K_Bumpers(stplyr); const bool dance = g_pointlimit && (g_pointlimit <= stplyr->roundscore); if (r_splitscreen == 0) { fy += 2; } K_DrawSticker(LAPS_X+12, fy+5, 75, V_HUDTRANS|V_SLIDEIN|splitflags, false); V_DrawMappedPatch(LAPS_X+12, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bigbumper, colormap); using srb2::Draw; Draw row = Draw(LAPS_X+12+23+1, fy+3).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kThinTimer); row.text("{:02}", bumpers); if (dance && leveltime % 8 < 4) { row = row.colorize(SKINCOLOR_TANGERINE); } row.xy(12, -2).patch(kp_pts[0]); row .x(12+27) .flags(dance ? V_STRINGDANCE : 0) .text("{:02}", stplyr->roundscore); } } } #if 0 static void K_drawKartWanted(void) { UINT8 i, numwanted = 0; UINT8 *colormap = NULL; INT32 basex = 0, basey = 0; if (!splitscreen) return; if (stplyr != &players[displayplayers[0]]) return; for (i = 0; i < 4; i++) { if (battlewanted[i] == -1) break; numwanted++; } if (numwanted <= 0) return; // set X/Y coords depending on splitscreen. if (r_splitscreen < 3) // 1P and 2P use the same code. { basex = WANT_X; basey = WANT_Y; if (r_splitscreen == 2) { basey += 16; // slight adjust for 3P basex -= 6; } } else if (r_splitscreen == 3) // 4P splitscreen... { basex = BASEVIDWIDTH/2 - (SHORT(kp_wantedsplit->width)/2); // center on screen basey = BASEVIDHEIGHT - 55; //basey2 = 4; } if (battlewanted[0] != -1) colormap = R_GetTranslationColormap(TC_DEFAULT, players[battlewanted[0]].skincolor, GTC_CACHE); V_DrawFixedPatch(basex<<FRACBITS, basey<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SLIDEIN|(r_splitscreen < 3 ? V_SNAPTORIGHT : 0)|V_SNAPTOBOTTOM, (r_splitscreen > 1 ? kp_wantedsplit : kp_wanted), colormap); /*if (basey2) V_DrawFixedPatch(basex<<FRACBITS, basey2<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, (splitscreen == 3 ? kp_wantedsplit : kp_wanted), colormap); // < used for 4p splits.*/ for (i = 0; i < numwanted; i++) { INT32 x = basex+(r_splitscreen > 1 ? 13 : 8), y = basey+(r_splitscreen > 1 ? 16 : 21); fixed_t scale = FRACUNIT/2; player_t *p = &players[battlewanted[i]]; if (battlewanted[i] == -1) break; if (numwanted == 1) scale = FRACUNIT; else { if (i & 1) x += 16; if (i > 1) y += 16; } if (players[battlewanted[i]].skincolor) { colormap = R_GetTranslationColormap(TC_RAINBOW, p->skincolor, GTC_CACHE); V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SLIDEIN|(r_splitscreen < 3 ? V_SNAPTORIGHT : 0)|V_SNAPTOBOTTOM, (scale == FRACUNIT ? faceprefix[p->skin][FACE_WANTED] : faceprefix[p->skin][FACE_RANK]), colormap); /*if (basey2) // again with 4p stuff V_DrawFixedPatch(x<<FRACBITS, (y - (basey-basey2))<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, (scale == FRACUNIT ? faceprefix[p->skin][FACE_WANTED] : faceprefix[p->skin][FACE_RANK]), colormap);*/ } } } #endif //if 0 static void K_drawKartPlayerCheck(void) { const fixed_t maxdistance = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); UINT8 i; INT32 splitflags = V_SNAPTOBOTTOM|V_SPLITSCREEN; fixed_t y = CHEK_Y * FRACUNIT; if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) { return; } if (stplyr->spectator || stplyr->awayview.tics) { return; } if (stplyr->cmd.buttons & BT_LOOKBACK) { return; } for (i = 0; i < MAXPLAYERS; i++) { player_t *checkplayer = &players[i]; fixed_t distance = maxdistance+1; UINT8 *colormap = NULL; UINT8 pnum = 0; vector3_t v; vector3_t pPos; trackingResult_t result; if (!playeringame[i] || checkplayer->spectator) { // Not in-game continue; } if (checkplayer->mo == NULL || P_MobjWasRemoved(checkplayer->mo)) { // No object continue; } if (checkplayer == stplyr) { // This is you! continue; } v.x = R_InterpolateFixed(checkplayer->mo->old_x, checkplayer->mo->x); v.y = R_InterpolateFixed(checkplayer->mo->old_y, checkplayer->mo->y); v.z = R_InterpolateFixed(checkplayer->mo->old_z, checkplayer->mo->z); pPos.x = R_InterpolateFixed(stplyr->mo->old_x, stplyr->mo->x); pPos.y = R_InterpolateFixed(stplyr->mo->old_y, stplyr->mo->y); pPos.z = R_InterpolateFixed(stplyr->mo->old_z, stplyr->mo->z); distance = R_PointToDist2(pPos.x, pPos.y, v.x, v.y); if (distance > maxdistance) { // Too far away continue; } if ((checkplayer->invincibilitytimer <= 0) && (leveltime & 2)) { pnum++; // white frames } if (checkplayer->itemtype == KITEM_GROW || checkplayer->growshrinktimer > 0) { pnum += 4; } else if (checkplayer->itemtype == KITEM_INVINCIBILITY || checkplayer->invincibilitytimer) { pnum += 2; } K_ObjectTracking(&result, &v, true); if (result.onScreen == true) { colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(checkplayer->mo->color), GTC_CACHE); V_DrawFixedPatch(result.x, y, FRACUNIT, V_HUDTRANS|V_SPLITSCREEN|splitflags, kp_check[pnum], colormap); } } } static boolean K_ShowPlayerNametag(player_t *p) { if (cv_seenames.value == 0) { return false; } if (demo.playback == true && camera[R_GetViewNumber()].freecam == true) { return true; } if (stplyr == p) { return false; } if (gametyperules & GTR_CIRCUIT) { if ((p->position == 0) || (stplyr->position == 0) || (p->position < stplyr->position-2) || (p->position > stplyr->position+2)) { return false; } } return true; } static void K_DrawLocalTagForPlayer(fixed_t x, fixed_t y, player_t *p, UINT8 id, UINT32 flags) { UINT8 blink = ((leveltime / 7) & 1); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(p->skincolor), GTC_CACHE); V_DrawFixedPatch(x, y, FRACUNIT, flags, kp_localtag[id][blink], colormap); } static void K_DrawRivalTagForPlayer(fixed_t x, fixed_t y, UINT32 flags) { UINT8 blink = ((leveltime / 7) & 1); V_DrawFixedPatch(x, y, FRACUNIT, flags, kp_rival[blink], NULL); } static void K_DrawTypingDot(fixed_t x, fixed_t y, UINT8 duration, player_t *p, INT32 flags) { if (p->typing_duration > duration) { V_DrawFixedPatch(x, y, FRACUNIT, flags, kp_typdot, NULL); } } static void K_DrawTypingNotifier(fixed_t x, fixed_t y, player_t *p, INT32 flags) { if (p->cmd.flags & TICCMD_TYPING) { V_DrawFixedPatch(x, y, FRACUNIT, V_SPLITSCREEN|flags, kp_talk, NULL); /* spacing closer with the last two looks a better most of the time */ K_DrawTypingDot(x + 3*FRACUNIT, y, 15, p, flags); K_DrawTypingDot(x + 6*FRACUNIT - FRACUNIT/3, y, 31, p, flags); K_DrawTypingDot(x + 9*FRACUNIT - FRACUNIT/3, y, 47, p, flags); } } // see also K_drawKartItem static void K_DrawNameTagItemSpy(INT32 x, INT32 y, player_t *p, INT32 flags) { using srb2::Draw; bool tiny = r_splitscreen > 1; SINT8 flip = 1, flipboxoffset = 0; if ((flags & V_VFLIP) == V_VFLIP) { // Remove the v_vflip flag - it makes things messy, but we also understand // that we want to make this look okay for flipped players, so simply use this // opportunity to flip vertical offsets accordingly instead. flags &= ~V_VFLIP; flip = P_MobjFlip(p->mo); flipboxoffset = 8; } Draw bar = Draw(x, y).flags(V_NOSCALESTART|flags); Draw box = tiny ? bar.xy(-22 * vid.dupx, (-17+flipboxoffset) * vid.dupy) : bar.xy(-40 * vid.dupx, (-26+flipboxoffset) * vid.dupy); box.colorize(p->skincolor).patch(kp_itembg[tiny ? 4 : 2]); if (!(p->itemflags & IF_ITEMOUT) || (leveltime & 1)) { switch (p->itemtype) { case KITEM_INVINCIBILITY: box.patch(kp_invincibility[((leveltime % (6*3)) / 3) + (tiny ? 13 : 7)]); break; case KITEM_ORBINAUT: box.patch(kp_orbinaut[4 + tiny]); break; default: if (patch_t *ico = K_GetCachedItemPatch(p->itemtype, 1 + tiny)) { box.patch(ico); } } } if (p->itemamount > 1) { (tiny ? bar.xy(-3 * vid.dupx, (-4*flip) * vid.dupy).font(Draw::Font::kPing) : bar.xy(-4 * vid.dupx, (-2*flip) * vid.dupy).font(Draw::Font::kThinTimer) ) .align(Draw::Align::kRight) .text("{}", p->itemamount); } } static void K_DrawNameTagSphereMeter(INT32 x, INT32 y, INT32 width, INT32 spheres, INT32 flags) { using srb2::Draw; Draw bar = Draw(x + vid.dupx, y).flags(V_NOSCALESTART).height(vid.dupy); // see also K_drawBlueSphereMeter const UINT8 segColors[] = {73, 64, 52, 54, 55, 35, 34, 33, 202, 180, 181, 182, 164, 165, 166, 153, 152}; spheres = std::clamp<INT32>(spheres, 0, 40); int colorIndex = (spheres * sizeof segColors) / (40 + 1); int px = r_splitscreen > 1 ? 1 : 2; int b = 10 * px; int m = spheres * px; while (m > 0) { if (b > m) b = m; Draw seg = bar.width(b); seg.fill(segColors[std::max(colorIndex - 1, 0)]); seg.y(vid.dupy).fill(segColors[std::max(colorIndex - 2, 0)]); seg.y(2 * vid.dupy).height(2 * vid.dupy).fill(segColors[colorIndex]); seg.y(4 * vid.dupy).fill(31); bar = bar.x(b + vid.dupx); m -= b; } } static void K_DrawNameTagForPlayer(fixed_t x, fixed_t y, player_t *p, INT32 flags) { const INT32 clr = skincolors[p->skincolor].chatcolor; const INT32 namelen = V_ThinStringWidth(player_names[p - players], 0); UINT8 *colormap = V_GetStringColormap(clr); INT32 barx = 0, bary = 0, barw = 0; INT32 flipped = P_MobjFlip(p->mo), flipfilloffset = 0, flipfontoffset = 0, flipspheresoffset = 0; if (flipped == -1) { flipfilloffset = -3; // You cannot really flip drawfill. flipfontoffset = -9; // Accounts for font height. flipspheresoffset = 2; } UINT8 cnum = R_GetViewNumber(); // Since there's no "V_DrawFixedFill", and I don't feel like making it, // fuck it, we're gonna just V_NOSCALESTART hack it if (r_splitscreen > 1 && cnum & 1) { x += (BASEVIDWIDTH/2) * FRACUNIT; } if ((r_splitscreen == 1 && cnum == 1) || (r_splitscreen > 1 && cnum > 1)) { y += (BASEVIDHEIGHT/2) * FRACUNIT; } barw = (namelen * vid.dupx); barx = (x * vid.dupx) / FRACUNIT; bary = (y * vid.dupy) / FRACUNIT; barx += (6 * vid.dupx); bary -= ((16 + flipfilloffset) * vid.dupx) * flipped; // Center it if necessary if (vid.width != BASEVIDWIDTH * vid.dupx) { barx += (vid.width - (BASEVIDWIDTH * vid.dupx)) / 2; } if (vid.height != BASEVIDHEIGHT * vid.dupy) { bary += (vid.height - (BASEVIDHEIGHT * vid.dupy)) / 2; } // see also K_CullTargetList if ((gametyperules & GTR_ITEMARROWS) && p->itemtype != KITEM_NONE && p->itemamount != 0) { K_DrawNameTagItemSpy(barx, bary, p, flags); } if (gametyperules & GTR_SPHERES) { K_DrawNameTagSphereMeter(barx, bary + (((4 + flipspheresoffset) * vid.dupy) * P_MobjFlip(p->mo)), barw, p->spheres, flags); } // Lat: 10/06/2020: colormap can be NULL on the frame you join a game, just arbitrarily use palette indexes 31 and 0 instead of whatever the colormap would give us instead to avoid crashes. V_DrawFill(barx, bary, barw, (3 * vid.dupy), (colormap ? colormap[31] : 31)|V_NOSCALESTART|flags); V_DrawFill(barx, bary + vid.dupy, barw, vid.dupy, (colormap ? colormap[0] : 0)|V_NOSCALESTART|flags); // END DRAWFILL DUMBNESS // Draw the stem V_DrawFixedPatch(x, y, FRACUNIT, flags, kp_nametagstem, colormap); // Draw the name itself V_DrawThinStringAtFixed(x + (5*FRACUNIT), y - (((26 + flipfontoffset) * FRACUNIT) * P_MobjFlip(p->mo)), clr|flags, player_names[p - players]); } playertagtype_t K_WhichPlayerTag(player_t *p) { UINT8 cnum = R_GetViewNumber(); if (!(demo.playback == true && camera[cnum].freecam == true) && P_IsDisplayPlayer(p) && p != &players[displayplayers[cnum]]) { return PLAYERTAG_LOCAL; } else if (p->bot) { if (p->botvars.rival == true || cv_levelskull.value) { return PLAYERTAG_RIVAL; } } else if (netgame || demo.playback) { if (K_ShowPlayerNametag(p) == true) { return PLAYERTAG_NAME; } } return PLAYERTAG_NONE; } void K_DrawPlayerTag(fixed_t x, fixed_t y, player_t *p, playertagtype_t type, boolean foreground) { INT32 flags = P_IsObjectFlipped(p->mo) ? V_VFLIP : 0; switch (type) { case PLAYERTAG_LOCAL: flags |= V_HUDTRANS|V_SPLITSCREEN; K_DrawLocalTagForPlayer(x, y, p, G_PartyPosition(p - players), flags); break; case PLAYERTAG_RIVAL: flags |= V_HUDTRANS|V_SPLITSCREEN; K_DrawRivalTagForPlayer(x, y, flags); break; case PLAYERTAG_NAME: // We only care about the trans flag here (based?) as well as V_VFLIP. flags |= foreground ? 0 : V_60TRANS; K_DrawNameTagForPlayer(x, y, p, flags); K_DrawTypingNotifier(x, y, p, flags); break; default: break; } } typedef struct weakspotdraw_t { UINT8 i; INT32 x; INT32 y; boolean candrawtag; } weakspotdraw_t; static void K_DrawWeakSpot(weakspotdraw_t *ws) { UINT8 *colormap; UINT8 j = (bossinfo.weakspots[ws->i].type == SPOT_BUMP) ? 1 : 0; tic_t flashtime = ~1; // arbitrary high even number if (bossinfo.weakspots[ws->i].time < TICRATE) { if (bossinfo.weakspots[ws->i].time & 1) return; flashtime = bossinfo.weakspots[ws->i].time; } else if (bossinfo.weakspots[ws->i].time > (WEAKSPOTANIMTIME - TICRATE)) flashtime = WEAKSPOTANIMTIME - bossinfo.weakspots[ws->i].time; if (flashtime & 1) colormap = R_GetTranslationColormap(TC_ALLWHITE, SKINCOLOR_NONE, GTC_CACHE); else colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(bossinfo.weakspots[ws->i].color), GTC_CACHE); V_DrawFixedPatch(ws->x, ws->y, FRACUNIT, 0, kp_bossret[j], colormap); if (!ws->candrawtag || flashtime & 1 || flashtime < TICRATE/2) return; V_DrawFixedPatch(ws->x, ws->y, FRACUNIT, 0, kp_bossret[j+1], colormap); } static void K_drawKartNameTags(void) { vector3_t c; UINT8 cnum = R_GetViewNumber(); size_t i, j; if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) { return; } if (stplyr->awayview.tics) { return; } // Crop within splitscreen bounds switch (r_splitscreen) { case 1: V_SetClipRect( 0, cnum == 1 ? (BASEVIDHEIGHT / 2) * FRACUNIT : 0, BASEVIDWIDTH * FRACUNIT, (BASEVIDHEIGHT / 2) * FRACUNIT, 0 ); break; case 2: case 3: V_SetClipRect( cnum & 1 ? (BASEVIDWIDTH / 2) * FRACUNIT : 0, cnum > 1 ? (BASEVIDHEIGHT / 2) * FRACUNIT : 0, (BASEVIDWIDTH / 2) * FRACUNIT, (BASEVIDHEIGHT / 2) * FRACUNIT, 0 ); break; } c.x = viewx; c.y = viewy; c.z = viewz; // Maybe shouldn't be handling this here... but the camera info is too good. if (bossinfo.valid == true) { weakspotdraw_t weakspotdraw[NUMWEAKSPOTS]; UINT8 numdraw = 0; boolean onleft = false; for (i = 0; i < NUMWEAKSPOTS; i++) { trackingResult_t result; vector3_t v; if (bossinfo.weakspots[i].spot == NULL || P_MobjWasRemoved(bossinfo.weakspots[i].spot)) { // No object continue; } if (bossinfo.weakspots[i].time == 0 || bossinfo.weakspots[i].type == SPOT_NONE) { // not visible continue; } v.x = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_x, bossinfo.weakspots[i].spot->x); v.y = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_y, bossinfo.weakspots[i].spot->y); v.z = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_z, bossinfo.weakspots[i].spot->z); v.z += (bossinfo.weakspots[i].spot->height / 2); K_ObjectTracking(&result, &v, false); if (result.onScreen == false) { continue; } weakspotdraw[numdraw].i = i; weakspotdraw[numdraw].x = result.x; weakspotdraw[numdraw].y = result.y; weakspotdraw[numdraw].candrawtag = true; for (j = 0; j < numdraw; j++) { if (abs(weakspotdraw[j].x - weakspotdraw[numdraw].x) > 50*FRACUNIT) { continue; } onleft = (weakspotdraw[j].x < weakspotdraw[numdraw].x); if (abs((onleft ? -5 : 5) + weakspotdraw[j].y - weakspotdraw[numdraw].y) > 18*FRACUNIT) { continue; } if (weakspotdraw[j].x < weakspotdraw[numdraw].x) { weakspotdraw[j].candrawtag = false; break; } weakspotdraw[numdraw].candrawtag = false; break; } numdraw++; } for (i = 0; i < numdraw; i++) { K_DrawWeakSpot(&weakspotdraw[i]); } } K_drawTargetHUD(&c, stplyr); V_ClearClipRect(); } #define PROGRESSION_BAR_WIDTH 120 static INT32 K_getKartProgressionMinimapDistance(UINT32 distancetofinish) { INT32 dist; if (specialstageinfo.maxDist == 0U) { return 0; } dist = specialstageinfo.maxDist/PROGRESSION_BAR_WIDTH; dist = (specialstageinfo.maxDist-distancetofinish)/dist; if (dist > PROGRESSION_BAR_WIDTH) { return PROGRESSION_BAR_WIDTH; } if (dist < 0) { return 0; } return dist; } static void K_drawKartProgressionMinimapIcon(UINT32 distancetofinish, INT32 hudx, INT32 hudy, INT32 flags, patch_t *icon, UINT8 *colormap) { if (distancetofinish == UINT32_MAX) return; hudx += K_getKartProgressionMinimapDistance(distancetofinish); hudx = ((hudx - (SHORT(icon->width)/2))<<FRACBITS); hudy = ((hudy - (SHORT(icon->height)/2))<<FRACBITS); V_DrawFixedPatch(hudx, hudy, FRACUNIT, flags, icon, colormap); } position_t K_GetKartObjectPosToMinimapPos(fixed_t objx, fixed_t objy) { fixed_t amnumxpos, amnumypos; amnumxpos = (FixedMul(objx, minimapinfo.zoom) - minimapinfo.offs_x); amnumypos = -(FixedMul(objy, minimapinfo.zoom) - minimapinfo.offs_y); if (encoremode) amnumxpos = -amnumxpos; return (position_t){amnumxpos, amnumypos}; } static void K_drawKartMinimapIcon(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, patch_t *icon, UINT8 *colormap) { // amnum xpos & ypos are the icon's speed around the HUD. // The number being divided by is for how fast it moves. // The higher the number, the slower it moves. // am xpos & ypos are the icon's starting position. Withouht // it, they wouldn't 'spawn' on the top-right side of the HUD. position_t amnumpos; INT32 amxpos, amypos; amnumpos = K_GetKartObjectPosToMinimapPos(objx, objy); amxpos = amnumpos.x + ((hudx - (SHORT(icon->width))/2)<<FRACBITS); amypos = amnumpos.y + ((hudy - (SHORT(icon->height))/2)<<FRACBITS); V_DrawFixedPatch(amxpos, amypos, FRACUNIT, flags, icon, colormap); } static void K_drawKartMinimapDot(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, UINT8 color, UINT8 size) { position_t amnumpos; INT32 amxpos, amypos; amnumpos = K_GetKartObjectPosToMinimapPos(objx, objy); amxpos = (amnumpos.x / FRACUNIT); amypos = (amnumpos.y / FRACUNIT); if (flags & V_NOSCALESTART) { amxpos *= vid.dupx; amypos *= vid.dupy; } V_DrawFill((amxpos + hudx) - (size / 2), (amypos + hudy) - (size / 2), size, size, flags | color); } static UINT8 K_RankMinimapWaypoint(waypoint_t *wp) { if (wp == stplyr->nextwaypoint) { return 4; } else if (wp->numnextwaypoints == 0 || wp->numprevwaypoints == 0) { return 3; } else if (!K_GetWaypointIsEnabled(wp)) // disabled { return 2; } else if (K_GetWaypointIsShortcut(wp)) // shortcut { return 1; } else { return 0; } } static void K_drawKartMinimapWaypoint(waypoint_t *wp, UINT8 rank, INT32 hudx, INT32 hudy, INT32 flags) { static UINT8 colors[] = { 0x95, // blue (0 - default) 0x20, // pink (1 - shortcut) 0x10, // gray (2 - disabled) 0x40, // yellow (3 - error) 0x70, // green (4 - player) }; UINT8 pal = colors[rank]; // blue UINT8 size = 3; if (rank == 4) { size = 6; } if (!(flags & V_NOSCALESTART)) { hudx *= vid.dupx; hudy *= vid.dupy; } K_drawKartMinimapDot(wp->mobj->x, wp->mobj->y, hudx, hudy, flags | V_NOSCALESTART, pal, size); } INT32 K_GetMinimapTransFlags(const boolean usingProgressBar) { INT32 minimaptrans = 4; boolean dofade = (usingProgressBar && r_splitscreen > 0) || (!usingProgressBar && r_splitscreen >= 1); if (dofade) { minimaptrans = FixedMul(minimaptrans, (st_translucency * FRACUNIT) / 10); // If the minimap is fully transparent, just get your 0 back. Bail out with this. if (!minimaptrans) return minimaptrans; } minimaptrans = ((10-minimaptrans)<<V_ALPHASHIFT); return minimaptrans; } INT32 K_GetMinimapSplitFlags(const boolean usingProgressBar) { INT32 splitflags = 0; if (usingProgressBar) splitflags = (V_SLIDEIN|V_SNAPTOBOTTOM); else { if (r_splitscreen < 1) // 1P right aligned { splitflags = (V_SLIDEIN|V_SNAPTORIGHT); } else // 2/4P splits { if (r_splitscreen == 1) splitflags = V_SNAPTORIGHT; // 2P right aligned // 3P lives in the middle of the bottom right // viewport and shouldn't fade in OR slide } } return splitflags; } #define ICON_DOT_RADIUS (10) static void K_drawKartMinimap(void) { patch_t *workingPic; INT32 i = 0; INT32 x, y; INT32 minimaptrans; INT32 splitflags; UINT8 skin = 0; UINT8 *colormap = NULL; SINT8 localplayers[MAXSPLITSCREENPLAYERS]; SINT8 numlocalplayers = 0; mobj_t *mobj, *next; // for SPB drawing (or any other item(s) we may wanna draw, I dunno!) fixed_t interpx, interpy; boolean doprogressionbar = false; boolean doencore = false; UINT8 minipal; // Draw the HUD only when playing in a level. // hu_stuff needs this, unlike st_stuff. if (gamestate != GS_LEVEL) return; // Only draw for the first player // Maybe move this somewhere else where this won't be a concern? if (R_GetViewNumber() != 0) return; if (specialstageinfo.valid == true) { // future work: maybe make this a unique gametype rule? // I would do this now if it were easier to get the // distancetofinish for an arbitrary object. ~toast 070423 doprogressionbar = true; } minimaptrans = K_GetMinimapTransFlags(doprogressionbar); if (!minimaptrans) return; // Exit early if it wouldn't draw anyway. splitflags = K_GetMinimapSplitFlags(doprogressionbar); if (doprogressionbar == false) { if (minimapinfo.minimap_pic == NULL) { return; // no pic, just get outta here } x = MINI_X; y = MINI_Y; workingPic = minimapinfo.minimap_pic; doencore = encoremode; } else { x = BASEVIDWIDTH/2; if (r_splitscreen > 0) { y = BASEVIDHEIGHT/2; } else { y = 180; } workingPic = kp_wouldyoustillcatchmeifiwereaworm; } // Really looking forward to never writing this loop again UINT8 bestplayer = MAXPLAYERS; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; if (players[i].spectator) continue; if (players[i].position == 1) bestplayer = i; } if (bestplayer == MAXPLAYERS || leveltime < starttime) // POSITION / no players minipal = ((leveltime/10)%2) ? SKINCOLOR_WHITE : SKINCOLOR_BLACK; else if (players[bestplayer].laps >= numlaps) // Final lap minipal = K_RainbowColor(leveltime); else // Standard: color to leader minipal = players[bestplayer].skincolor; if (doencore) { V_DrawFixedPatch( (x + (SHORT(workingPic->width)/2))*FRACUNIT, (y - (SHORT(workingPic->height)/2))*FRACUNIT, FRACUNIT, splitflags|minimaptrans|V_FLIP, workingPic, R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(minipal), GTC_CACHE) ); } else { V_DrawFixedPatch( (x - (SHORT(workingPic->width)/2))*FRACUNIT, (y - (SHORT(workingPic->height)/2))*FRACUNIT, FRACUNIT, splitflags|minimaptrans, workingPic, R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(minipal), GTC_CACHE) ); } // most icons will be rendered semi-ghostly. splitflags |= V_HUDTRANSHALF; // let offsets transfer to the heads, too! if (doencore) x += SHORT(workingPic->leftoffset); else x -= SHORT(workingPic->leftoffset); y -= SHORT(workingPic->topoffset); if (doprogressionbar == true) { x -= PROGRESSION_BAR_WIDTH/2; } // Draw the super item in Battle if (doprogressionbar == false && (gametyperules & GTR_OVERTIME) && battleovertime.enabled) { if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) { const INT32 prevsplitflags = splitflags; splitflags &= ~V_HUDTRANSHALF; splitflags |= V_HUDTRANS; colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(K_RainbowColor(leveltime)), GTC_CACHE); K_drawKartMinimapIcon(battleovertime.x, battleovertime.y, x, y, splitflags, kp_itemminimap, colormap); splitflags = prevsplitflags; } } // initialize for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) localplayers[i] = -1; // Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen) if (ghosts && doprogressionbar == false) // future work: show ghosts on progression bar { demoghost *g = ghosts; while (g) { if (g->mo && !P_MobjWasRemoved(g->mo) && g->mo->skin) { skin = ((skin_t*)g->mo->skin)-skins; workingPic = R_CanShowSkinInDemo(skin) ? faceprefix[skin][FACE_MINIMAP] : kp_unknownminimap; if (g->mo->color) { if (g->mo->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(g->mo->color), GTC_CACHE); else colormap = R_GetTranslationColormap(skin, static_cast<skincolornum_t>(g->mo->color), GTC_CACHE); } else colormap = NULL; interpx = R_InterpolateFixed(g->mo->old_x, g->mo->x); interpy = R_InterpolateFixed(g->mo->old_y, g->mo->y); K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap); } g = g->next; } } { for (i = MAXPLAYERS-1; i >= 0; i--) { if (!playeringame[i]) continue; if (!players[i].mo || players[i].spectator || !players[i].mo->skin || (doprogressionbar == false && players[i].exiting)) continue; // This player is out of the game! if ((gametyperules & GTR_BUMPERS) && (players[i].pflags & PF_ELIMINATED)) continue; // This gets set for a player who has GAME OVER'd if (P_MobjIsReappearing(players[i].mo)) continue; if (i == displayplayers[0] || i == displayplayers[1] || i == displayplayers[2] || i == displayplayers[3]) { // Draw display players on top of everything else localplayers[numlocalplayers++] = i; continue; } if (players[i].hyudorotimer > 0) { if (!((players[i].hyudorotimer < TICRATE/2 || players[i].hyudorotimer > hyudorotime-(TICRATE/2)) && !(leveltime & 1))) continue; } mobj = players[i].mo; if (mobj->health <= 0 && (players[i].pflags & PF_NOCONTEST)) { if (P_MobjWasRemoved(mobj->tracer)) { continue; } if (mobj->tracer->renderflags & RF_DONTDRAW) { continue; } workingPic = kp_nocontestminimap; colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(mobj->color), GTC_CACHE); mobj = mobj->tracer; } else { skin = ((skin_t*)mobj->skin)-skins; workingPic = R_CanShowSkinInDemo(skin) ? faceprefix[skin][FACE_MINIMAP] : kp_unknownminimap; if (mobj->color) { if (mobj->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(mobj->color), GTC_CACHE); else colormap = R_GetTranslationColormap(skin, static_cast<skincolornum_t>(mobj->color), GTC_CACHE); } else colormap = NULL; } if (doprogressionbar == false) { interpx = R_InterpolateFixed(mobj->old_x, mobj->x); interpy = R_InterpolateFixed(mobj->old_y, mobj->y); K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap); // Target reticule if (((gametyperules & GTR_CIRCUIT) && players[i].position == spbplace) || ((gametyperules & (GTR_BOSS|GTR_POINTLIMIT)) == GTR_POINTLIMIT && K_IsPlayerWanted(&players[i]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL); } } else { K_drawKartProgressionMinimapIcon(players[i].distancetofinish, x, y, splitflags, workingPic, colormap); } } } // draw minimap-pertinent objects if (doprogressionbar == true) { // future work: support these specific objects on this } else for (mobj = trackercap; mobj; mobj = next) { next = mobj->itnext; workingPic = NULL; colormap = NULL; if (mobj->health <= 0) continue; switch (mobj->type) { case MT_SPB: workingPic = kp_spbminimap; #if 0 if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player && mobj->target->player->skincolor) { colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->target->player->skincolor, GTC_CACHE); } else #endif if (mobj->color) { colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(mobj->color), GTC_CACHE); } break; case MT_BATTLECAPSULE: workingPic = kp_capsuleminimap[(mobj->extravalue1 != 0 ? 1 : 0)]; break; case MT_CDUFO: if (battleprisons) workingPic = kp_capsuleminimap[2]; break; case MT_BATTLEUFO: workingPic = kp_battleufominimap; break; case MT_SUPER_FLICKY: workingPic = kp_superflickyminimap; if (mobj_t* owner = Obj_SuperFlickyOwner(mobj); owner && owner->color) { colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(owner->color), GTC_CACHE); } break; default: break; } if (!workingPic) continue; interpx = R_InterpolateFixed(mobj->old_x, mobj->x); interpy = R_InterpolateFixed(mobj->old_y, mobj->y); K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap); } // draw our local players here, opaque. { splitflags &= ~V_HUDTRANSHALF; splitflags |= V_HUDTRANS; } // ...but first, any boss targets. if (doprogressionbar == true) { if (specialstageinfo.valid == true) { UINT32 distancetofinish = K_GetSpecialUFODistance(); if (distancetofinish > 0 && specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) { colormap = NULL; if (specialstageinfo.ufo->health > 1) { workingPic = kp_catcherminimap; } else { UINT8 emid = 0; if (specialstageinfo.ufo->cvmem > 7) emid = 1; workingPic = kp_emeraldminimap[emid]; if (specialstageinfo.ufo->color) { colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(specialstageinfo.ufo->color), GTC_CACHE); } } K_drawKartProgressionMinimapIcon(distancetofinish, x, y, splitflags, workingPic, colormap); } } // future work: support boss minimap icons on the progression bar } else if (bossinfo.valid == true) { for (i = 0; i < NUMWEAKSPOTS; i++) { // exists at all? if (bossinfo.weakspots[i].spot == NULL || P_MobjWasRemoved(bossinfo.weakspots[i].spot)) continue; // shows on the minimap? if (bossinfo.weakspots[i].minimap == false) continue; // in the flashing period? if ((bossinfo.weakspots[i].time > (WEAKSPOTANIMTIME-(TICRATE/2))) && (bossinfo.weakspots[i].time & 1)) continue; colormap = NULL; if (bossinfo.weakspots[i].color) colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(bossinfo.weakspots[i].color), GTC_CACHE); interpx = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_x, bossinfo.weakspots[i].spot->x); interpy = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_y, bossinfo.weakspots[i].spot->y); // temporary graphic? K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, colormap); } } for (i = 0; i < numlocalplayers; i++) { boolean nocontest = false; if (localplayers[i] == -1) continue; // this doesn't interest us if ((players[localplayers[i]].hyudorotimer > 0) && (leveltime & 1)) continue; mobj = players[localplayers[i]].mo; // This gets set for a player who has GAME OVER'd if (P_MobjIsReappearing(mobj)) continue; if (mobj->health <= 0 && (players[localplayers[i]].pflags & PF_NOCONTEST)) { if (P_MobjWasRemoved(mobj->tracer)) { continue; } if (mobj->tracer->renderflags & RF_DONTDRAW) { continue; } workingPic = kp_nocontestminimap; colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(mobj->color), GTC_CACHE); mobj = mobj->tracer; nocontest = true; } else { skin = ((skin_t*)mobj->skin)-skins; workingPic = R_CanShowSkinInDemo(skin) ? faceprefix[skin][FACE_MINIMAP] : kp_unknownminimap; if (mobj->color) { if (mobj->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(mobj->color), GTC_CACHE); else colormap = R_GetTranslationColormap(skin, static_cast<skincolornum_t>(mobj->color), GTC_CACHE); } else colormap = NULL; } if (doprogressionbar == false) { interpx = R_InterpolateFixed(mobj->old_x, mobj->x); interpy = R_InterpolateFixed(mobj->old_y, mobj->y); K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap); // Target reticule if (((gametyperules & GTR_CIRCUIT) && players[localplayers[i]].position == spbplace) || ((gametyperules & (GTR_BOSS|GTR_POINTLIMIT)) == GTR_POINTLIMIT && K_IsPlayerWanted(&players[localplayers[i]]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL); } if (!nocontest) { angle_t ang = R_InterpolateAngle(mobj->old_angle, mobj->angle); if (encoremode) ang = ANGLE_180 - ang; if (skin && mobj->color && !mobj->colorized // relevant to redo && skins[skin].starttranscolor != skins[0].starttranscolor) // redoing would have an affect { colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(mobj->color), GTC_CACHE); } K_drawKartMinimapIcon( interpx, interpy, x + FixedMul(FCOS(ang), ICON_DOT_RADIUS), y - FixedMul(FSIN(ang), ICON_DOT_RADIUS), splitflags, kp_minimapdot, colormap ); } } else { K_drawKartProgressionMinimapIcon(players[localplayers[i]].distancetofinish, x, y, splitflags, workingPic, colormap); } } if (doprogressionbar == false && cv_kartdebugwaypoints.value != 0) { struct MiniWaypoint { waypoint_t* waypoint; UINT8 rank; MiniWaypoint(waypoint_t* wp) : waypoint(wp), rank(K_RankMinimapWaypoint(wp)) {} bool operator<(const MiniWaypoint& b) const noexcept { return rank < b.rank; } }; std::vector<MiniWaypoint> waypoints; size_t idx; waypoints.reserve(K_GetNumWaypoints()); for (idx = 0; idx < K_GetNumWaypoints(); ++idx) { waypoint_t *wp = K_GetWaypointFromIndex(idx); I_Assert(wp != NULL); waypoints.push_back(wp); } std::sort(waypoints.begin(), waypoints.end()); for (MiniWaypoint& wp : waypoints) { K_drawKartMinimapWaypoint(wp.waypoint, wp.rank, x, y, splitflags); } } } #undef PROGRESSION_BAR_WIDTH static void K_drawKartFinish(boolean finish) { INT32 timer, minsplitstationary, pnum = 0, splitflags = V_SPLITSCREEN; patch_t **kptodraw; if (finish) { if (gametyperules & GTR_SPECIALSTART) return; timer = stplyr->karthud[khud_finish]; kptodraw = kp_racefinish; minsplitstationary = 2; } else { timer = stplyr->karthud[khud_fault]; kptodraw = kp_racefault; minsplitstationary = 1; } if (!timer || timer > 2*TICRATE) return; if ((timer % (2*5)) / 5) // blink pnum = 1; if (r_splitscreen > 0) pnum += (r_splitscreen > 1) ? 2 : 4; if (r_splitscreen >= minsplitstationary) // 3/4p, stationary FIN { V_DrawScaledPatch(STCD_X - (SHORT(kptodraw[pnum]->width)/2), STCD_Y - (SHORT(kptodraw[pnum]->height)/2), splitflags, kptodraw[pnum]); return; } //else -- 1/2p, scrolling FINISH { INT32 x, xval, ox, interpx, pwidth; x = ((vid.width<<FRACBITS)/vid.dupx); xval = (SHORT(kptodraw[pnum]->width)<<FRACBITS); pwidth = std::max(xval, x); x = ((TICRATE - timer) * pwidth) / TICRATE; ox = ((TICRATE - (timer - 1)) * pwidth) / TICRATE; interpx = R_InterpolateFixed(ox, x); if (r_splitscreen && R_GetViewNumber() == 1) interpx = -interpx; V_DrawFixedPatch(interpx + (STCD_X<<FRACBITS) - (pwidth / 2), (STCD_Y<<FRACBITS) - (SHORT(kptodraw[pnum]->height)<<(FRACBITS-1)), FRACUNIT, splitflags, kptodraw[pnum], NULL); } } static void K_drawKartStartBulbs(void) { const UINT8 start_animation[14] = { 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 9, 10, 11, 12 }; const UINT8 loop_animation[4] = { 12, 13, 12, 14 }; const UINT8 chillloop_animation[2] = { 11, 12 }; const UINT8 letters_order[10] = { 0, 1, 2, 3, 4, 3, 1, 5, 6, 6 }; const UINT8 letters_transparency[40] = { 0, 2, 4, 6, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8, 6, 4, 2 }; fixed_t spacing = 24*FRACUNIT; fixed_t startx = (BASEVIDWIDTH/2)*FRACUNIT; fixed_t starty = 48*FRACUNIT; fixed_t x, y; UINT8 numperrow = numbulbs/2; UINT8 i; if (r_splitscreen >= 1) { spacing /= 2; starty /= 3; if (r_splitscreen > 1) { startx /= 2; } } startx += (spacing/2); if (numbulbs <= 10) { // No second row numperrow = numbulbs; } else { if (numbulbs & 1) { numperrow++; } starty -= (spacing/2); } startx -= (spacing/2) * numperrow; x = startx; y = starty; for (i = 0; i < numbulbs; i++) { UINT8 patchnum = 0; INT32 bulbtic = (leveltime - introtime - TICRATE) - (bulbtime * i); if (i == numperrow) { y += spacing; x = startx + (spacing/2); } if (bulbtic > 0) { if (bulbtic < 14) { patchnum = start_animation[bulbtic]; } else { const INT32 length = (bulbtime * 3); bulbtic -= 14; // Reduce VFX disables the bulb animation while still presenting this indicator if (bulbtic > length) { bulbtic -= length; if (cv_reducevfx.value != 0) { patchnum = chillloop_animation[0]; } else { patchnum = chillloop_animation[bulbtic % 2]; } } else { if (cv_reducevfx.value != 0) { patchnum = loop_animation[0]; } else { patchnum = loop_animation[bulbtic % 4]; } } } } V_DrawFixedPatch(x, y, FRACUNIT, V_SNAPTOTOP|V_SPLITSCREEN, (r_splitscreen ? kp_prestartbulb_split[patchnum] : kp_prestartbulb[patchnum]), NULL); x += spacing; } x = 70*FRACUNIT; y = starty; if (r_splitscreen == 1) { x = 106*FRACUNIT; } else if (r_splitscreen > 1) { x = 28*FRACUNIT; } if (timeinmap < 16) return; // temporary for current map start behaviour for (i = 0; i < 10; i++) { UINT8 patchnum = letters_order[i]; INT32 transflag = letters_transparency[(leveltime - i) % 40]; patch_t *patch = (r_splitscreen ? kp_prestartletters_split[patchnum] : kp_prestartletters[patchnum]); if (transflag >= 10) ; else { if (transflag != 0) transflag = transflag << FF_TRANSSHIFT; V_DrawFixedPatch(x, y, FRACUNIT, V_SNAPTOTOP|V_SPLITSCREEN|transflag, patch, NULL); } if (i < 9) { x += (SHORT(patch->width)) * FRACUNIT/2; patchnum = letters_order[i+1]; patch = (r_splitscreen ? kp_prestartletters_split[patchnum] : kp_prestartletters[patchnum]); x += (SHORT(patch->width)) * FRACUNIT/2; if (r_splitscreen) x -= FRACUNIT; } } } static void K_drawKartStartCountdown(void) { INT32 pnum = 0; if (leveltime >= introtime && leveltime < starttime-(3*TICRATE)) { if (numbulbs > 1) K_drawKartStartBulbs(); } else { if (leveltime >= starttime-(2*TICRATE)) // 2 pnum++; if (leveltime >= starttime-TICRATE) // 1 pnum++; if (leveltime >= starttime) // GO! { UINT8 i; UINT8 numplayers = 0; pnum++; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator) numplayers++; if (numplayers > 2) break; } if (inDuel == true) { pnum++; // DUEL } } if ((leveltime % (2*5)) / 5) // blink pnum += 5; if (r_splitscreen) // splitscreen pnum += 10; V_DrawScaledPatch(STCD_X - (SHORT(kp_startcountdown[pnum]->width)/2), STCD_Y - (SHORT(kp_startcountdown[pnum]->height)/2), V_SPLITSCREEN, kp_startcountdown[pnum]); } } static void K_drawKartFirstPerson(void) { static INT32 pnum[4], turn[4], drift[4]; const INT16 steerThreshold = KART_FULLTURN / 2; INT32 pn = 0, tn = 0, dr = 0; INT32 target = 0, splitflags = V_SNAPTOBOTTOM|V_SPLITSCREEN; INT32 x = BASEVIDWIDTH/2, y = BASEVIDHEIGHT; fixed_t scale; UINT8 *colmap = NULL; if (stplyr->spectator || !stplyr->mo || (stplyr->mo->renderflags & RF_DONTDRAW || stplyr->mo->state == &states[S_KART_DEAD])) return; { UINT8 view = R_GetViewNumber(); pn = pnum[view]; tn = turn[view]; dr = drift[view]; } if (r_splitscreen) { y >>= 1; if (r_splitscreen > 1) x >>= 1; } { if (stplyr->speed < (20*stplyr->mo->scale) && (leveltime & 1) && !r_splitscreen) y++; if (stplyr->mo->renderflags & RF_TRANSMASK) splitflags |= ((stplyr->mo->renderflags & RF_TRANSMASK) >> RF_TRANSSHIFT) << FF_TRANSSHIFT; else if (stplyr->mo->frame & FF_TRANSMASK) splitflags |= (stplyr->mo->frame & FF_TRANSMASK); } if (stplyr->steering > steerThreshold) // strong left turn target = 2; else if (stplyr->steering < -steerThreshold) // strong right turn target = -2; else if (stplyr->steering > 0) // weak left turn target = 1; else if (stplyr->steering < 0) // weak right turn target = -1; else // forward target = 0; if (encoremode) target = -target; if (pn < target) pn++; else if (pn > target) pn--; if (pn < 0) splitflags |= V_FLIP; // right turn target = abs(pn); if (target > 2) target = 2; x <<= FRACBITS; y <<= FRACBITS; if (tn != stplyr->steering/50) tn -= (tn - (stplyr->steering/50))/8; if (dr != stplyr->drift*16) dr -= (dr - (stplyr->drift*16))/8; if (r_splitscreen == 1) { scale = (2*FRACUNIT)/3; y += FRACUNIT/(vid.dupx < vid.dupy ? vid.dupx : vid.dupy); // correct a one-pixel gap on the screen view (not the basevid view) } else if (r_splitscreen) scale = FRACUNIT/2; else scale = FRACUNIT; if (stplyr->mo) { UINT8 driftcolor = K_DriftSparkColor(stplyr, stplyr->driftcharge); const angle_t ang = R_PointToAngle2(0, 0, stplyr->rmomx, stplyr->rmomy) - stplyr->drawangle; // yes, the following is correct. no, you do not need to swap the x and y. fixed_t xoffs = -P_ReturnThrustY(stplyr->mo, ang, (BASEVIDWIDTH<<(FRACBITS-2))/2); fixed_t yoffs = -P_ReturnThrustX(stplyr->mo, ang, 4*FRACUNIT); // hitlag vibrating if (stplyr->mo->hitlag > 0 && (stplyr->mo->eflags & MFE_DAMAGEHITLAG)) { fixed_t mul = stplyr->mo->hitlag * HITLAGJITTERS; if (r_splitscreen && mul > FRACUNIT) mul = FRACUNIT; if (leveltime & 1) { mul = -mul; } xoffs = FixedMul(xoffs, mul); yoffs = FixedMul(yoffs, mul); } if ((yoffs += 4*FRACUNIT) < 0) yoffs = 0; if (r_splitscreen) xoffs = FixedMul(xoffs, scale); xoffs -= (tn)*scale; xoffs -= (dr)*scale; if (stplyr->drawangle == stplyr->mo->angle) { const fixed_t mag = FixedDiv(stplyr->speed, 10*stplyr->mo->scale); if (mag < FRACUNIT) { xoffs = FixedMul(xoffs, mag); if (!r_splitscreen) yoffs = FixedMul(yoffs, mag); } } if (stplyr->mo->momz > 0) // TO-DO: Draw more of the kart so we can remove this if! yoffs += stplyr->mo->momz/3; if (encoremode) x -= xoffs; else x += xoffs; if (!r_splitscreen) y += yoffs; if ((leveltime & 1) && (driftcolor != SKINCOLOR_NONE)) // drift sparks! colmap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(driftcolor), GTC_CACHE); else if (stplyr->mo->colorized && stplyr->mo->color) // invincibility/grow/shrink! colmap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(stplyr->mo->color), GTC_CACHE); } V_DrawFixedPatch(x, y, scale, splitflags, kp_fpview[target], colmap); { UINT8 view = R_GetViewNumber(); pnum[view] = pn; turn[view] = tn; drift[view] = dr; } } static void K_drawInput(void) { UINT8 viewnum = R_GetViewNumber(); boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam if (!cv_drawinput.value && !modeattacking && gametype != GT_TUTORIAL) return; if (stplyr->spectator || freecam || demo.attract) return; INT32 def[4][3] = { {247, 156, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 1p {247, 56, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 2p {6, 52, V_SNAPTOBOTTOM | V_SNAPTOLEFT}, // 4p left {282 - BASEVIDWIDTH/2, 52, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 4p right }; INT32 k = r_splitscreen <= 1 ? r_splitscreen : 2 + (viewnum & 1); INT32 flags = def[k][2] | V_SPLITSCREEN; char mode = ((stplyr->pflags & PF_ANALOGSTICK) ? '4' : '2') + (r_splitscreen > 1); bool local = !demo.playback && P_IsMachineLocalPlayer(stplyr); fixed_t slide = K_GetDialogueSlide(FRACUNIT); INT32 tallySlide = []() -> INT32 { if (r_splitscreen <= 1) { return 0; } if (!stplyr->tally.active) { return 0; } constexpr INT32 kSlideDown = 22; if (stplyr->tally.state == TALLY_ST_GOTTHRU_SLIDEIN || stplyr->tally.state == TALLY_ST_GAMEOVER_SLIDEIN) { return static_cast<INT32>(Easing_OutQuad(std::min<fixed_t>(stplyr->tally.transition * 2, FRACUNIT), 0, kSlideDown)); } return kSlideDown; }(); if (slide) flags &= ~(V_SNAPTORIGHT); // don't draw underneath the dialogue box in non-green resolutions // Move above the boss health bar. // TODO: boss HUD only works in 1P, so this only works in 1P too. if (LUA_HudEnabled(hud_position) && bossinfo.valid) { constexpr tic_t kDelay = 2u; // See K_drawBossHealthBar tic_t start = lt_endtime - 1u; tic_t t = std::clamp(lt_ticker, start, start + kDelay) - start; def[0][1] -= 24 + Easing_Linear(t * FRACUNIT / kDelay, 0, 7); } K_DrawInputDisplay( def[k][0] - FixedToFloat(34 * slide), def[k][1] - FixedToFloat(51 * slide) + tallySlide, flags, mode, (local ? G_LocalSplitscreenPartyPosition : G_PartyPosition)(stplyr - players), local, stplyr->speed > 0 ); } static void K_drawChallengerScreen(void) { // This is an insanely complicated animation. static UINT8 anim[52] = { 0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13, // frame 1-14, 2 tics: HERE COMES A NEW slides in 14,14,14,14,14,14, // frame 15, 6 tics: pause on the W 15,16,17,18, // frame 16-19, 1 tic: CHALLENGER approaches screen 19,20,19,20,19,20,19,20,19,20, // frame 20-21, 1 tic, 5 alternating: all text vibrates from impact 21,22,23,24 // frame 22-25, 1 tic: CHALLENGER turns gold }; const UINT8 offset = std::min<UINT32>(52-1u, (3*TICRATE)-mapreset); V_DrawFadeScreen(0xFF00, 16); // Fade out V_DrawScaledPatch(0, 0, 0, kp_challenger[anim[offset]]); } static void K_drawLapStartAnim(void) { if (demo.attract == DEMO_ATTRACT_CREDITS) { return; } // This is an EVEN MORE insanely complicated animation. const UINT8 t = stplyr->karthud[khud_lapanimation]; const UINT8 progress = 80 - t; const UINT8 tOld = t + 1; const UINT8 progressOld = 80 - tOld; const tic_t leveltimeOld = leveltime - 1; UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(stplyr->skincolor), GTC_CACHE); fixed_t interpx, interpy, newval, oldval; newval = (BASEVIDWIDTH/2 + (32 * std::max(0, t - 76))) * FRACUNIT; oldval = (BASEVIDWIDTH/2 + (32 * std::max(0, tOld - 76))) * FRACUNIT; interpx = R_InterpolateFixed(oldval, newval); newval = (48 - (32 * std::max(0, progress - 76))) * FRACUNIT; oldval = (48 - (32 * std::max(0, progressOld - 76))) * FRACUNIT; interpy = R_InterpolateFixed(oldval, newval); V_DrawFixedPatch( interpx, interpy, FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, (modeattacking ? kp_lapanim_emblem[1] : kp_lapanim_emblem[0]), colormap); if (stplyr->karthud[khud_laphand] >= 1 && stplyr->karthud[khud_laphand] <= 3) { newval = (4 - abs((signed)((leveltime % 8) - 4))) * FRACUNIT; oldval = (4 - abs((signed)((leveltimeOld % 8) - 4))) * FRACUNIT; interpy += R_InterpolateFixed(oldval, newval); V_DrawFixedPatch( interpx, interpy, FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, kp_lapanim_hand[stplyr->karthud[khud_laphand]-1], NULL); } if (stplyr->latestlap == (UINT8)(numlaps)) { newval = (62 - (32 * std::max(0, progress - 76))) * FRACUNIT; oldval = (62 - (32 * std::max(0, progressOld - 76))) * FRACUNIT; interpx = R_InterpolateFixed(oldval, newval); V_DrawFixedPatch( interpx, // 27 30*FRACUNIT, // 24 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, kp_lapanim_final[std::min(progress/2, 10)], NULL); if (progress/2-12 >= 0) { newval = (188 + (32 * std::max(0, progress - 76))) * FRACUNIT; oldval = (188 + (32 * std::max(0, progressOld - 76))) * FRACUNIT; interpx = R_InterpolateFixed(oldval, newval); V_DrawFixedPatch( interpx, // 194 30*FRACUNIT, // 24 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, kp_lapanim_lap[std::min(progress/2-12, 6)], NULL); } } else { newval = (82 - (32 * std::max(0, progress - 76))) * FRACUNIT; oldval = (82 - (32 * std::max(0, progressOld - 76))) * FRACUNIT; interpx = R_InterpolateFixed(oldval, newval); V_DrawFixedPatch( interpx, // 61 30*FRACUNIT, // 24 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, kp_lapanim_lap[std::min(progress/2, 6)], NULL); if (progress/2-8 >= 0) { newval = (188 + (32 * std::max(0, progress - 76))) * FRACUNIT; oldval = (188 + (32 * std::max(0, progressOld - 76))) * FRACUNIT; interpx = R_InterpolateFixed(oldval, newval); V_DrawFixedPatch( interpx, // 194 30*FRACUNIT, // 24 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, kp_lapanim_number[(((UINT32)stplyr->latestlap) / 10)][std::min(progress/2-8, 2)], NULL); if (progress/2-10 >= 0) { newval = (208 + (32 * std::max(0, progress - 76))) * FRACUNIT; oldval = (208 + (32 * std::max(0, progressOld - 76))) * FRACUNIT; interpx = R_InterpolateFixed(oldval, newval); V_DrawFixedPatch( interpx, // 221 30*FRACUNIT, // 24 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, kp_lapanim_number[(((UINT32)stplyr->latestlap) % 10)][std::min(progress/2-10, 2)], NULL); } } } } // stretch for "COOOOOL" popup. // I can't be fucked to find out any math behind this so have a table lmao static fixed_t stretch[6][2] = { {FRACUNIT/4, FRACUNIT*4}, {FRACUNIT/2, FRACUNIT*2}, {FRACUNIT, FRACUNIT}, {FRACUNIT*4, FRACUNIT/2}, {FRACUNIT*8, FRACUNIT/4}, {FRACUNIT*4, FRACUNIT/2}, }; static void K_drawTrickCool(void) { tic_t timer = TICRATE - stplyr->karthud[khud_trickcool]; if (timer <= 6) { V_DrawStretchyFixedPatch(TCOOL_X<<FRACBITS, TCOOL_Y<<FRACBITS, stretch[timer-1][0], stretch[timer-1][1], V_HUDTRANS|V_SPLITSCREEN, kp_trickcool[splitscreen ? 1 : 0], NULL); } else if (leveltime & 1) { V_DrawFixedPatch(TCOOL_X<<FRACBITS, (TCOOL_Y<<FRACBITS) - (timer-10)*FRACUNIT/2, FRACUNIT, V_HUDTRANS|V_SPLITSCREEN, kp_trickcool[splitscreen ? 1 : 0], NULL); } } void K_drawKartFreePlay(void) { if (!LUA_HudEnabled(hud_freeplay)) return; if (stplyr->spectator == true) return; if (M_NotFreePlay() == true) return; if (lt_exitticker < TICRATE/2) return; if (((leveltime-lt_endtime) % TICRATE) < TICRATE/2) return; INT32 h_snap = r_splitscreen < 2 ? V_SNAPTORIGHT | V_SLIDEIN : V_HUDTRANS; fixed_t x = ((r_splitscreen > 1 ? BASEVIDWIDTH/4 : BASEVIDWIDTH - (LAPS_X+6)) * FRACUNIT); fixed_t y = ((r_splitscreen ? BASEVIDHEIGHT/2 : BASEVIDHEIGHT) - 20) * FRACUNIT; x -= V_StringScaledWidth( FRACUNIT, FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|h_snap|V_SPLITSCREEN, KART_FONT, "FREE PLAY" ) / (r_splitscreen > 1 ? 2 : 1); V_DrawStringScaled( x, y, FRACUNIT, FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|h_snap|V_SPLITSCREEN, NULL, KART_FONT, "FREE PLAY" ); } static void Draw_party_ping (int ss, INT32 snap) { UINT32 ping = playerpingtable[displayplayers[ss]]; UINT32 mindelay = playerdelaytable[displayplayers[ss]]; HU_drawMiniPing(0, 0, ping, mindelay, V_SPLITSCREEN|V_SNAPTOTOP|snap); } static void K_drawMiniPing (void) { UINT32 f = V_SNAPTORIGHT; UINT8 i = R_GetViewNumber(); if (r_splitscreen > 1 && !(i & 1)) { f = V_SNAPTOLEFT; } Draw_party_ping(i, f); } void K_drawButton(fixed_t x, fixed_t y, INT32 flags, patch_t *button[2], boolean pressed) { V_DrawFixedPatch(x, y, FRACUNIT, flags, button[(pressed == true) ? 1 : 0], NULL); } void K_drawButtonAnim(INT32 x, INT32 y, INT32 flags, patch_t *button[2], tic_t animtic) { const UINT8 anim_duration = 16; const boolean anim = ((animtic % (anim_duration * 2)) < anim_duration); K_drawButton(x << FRACBITS, y << FRACBITS, flags, button, anim); } static void K_drawDistributionDebugger(void) { itemroulette_t rouletteData = {0}; const fixed_t scale = (FRACUNIT >> 1); const fixed_t space = 24 * scale; const fixed_t pad = 9 * scale; fixed_t x = -pad; fixed_t y = -pad; size_t i; if (R_GetViewNumber() != 0) // only for p1 { return; } K_FillItemRouletteData(stplyr, &rouletteData, false); for (i = 0; i < rouletteData.itemListLen; i++) { const kartitems_t item = static_cast<kartitems_t>(rouletteData.itemList[i]); UINT8 amount = 1; if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) { x += space; y = -pad; } V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, K_GetSmallStaticCachedItemPatch(item), NULL); // Display amount for multi-items amount = K_ItemResultToAmount(item); if (amount > 1) { V_DrawStringScaled( x + (18 * scale), y + (23 * scale), scale, FRACUNIT, FRACUNIT, V_SNAPTOTOP, NULL, HU_FONT, va("x%d", amount) ); } y += space; } V_DrawString((x >> FRACBITS) + 20, 2, V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); V_DrawString((x >> FRACBITS) + 20, 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); V_DrawString((x >> FRACBITS) + 20, 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); V_DrawString((x >> FRACBITS) + 20, 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); #ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); #endif } static void K_DrawWaypointDebugger(void) { if (cv_kartdebugwaypoints.value == 0) return; if (R_GetViewNumber() != 0) // only for p1 return; constexpr int kH = 8; using srb2::Draw; Draw::TextElement label; label.font(Draw::Font::kThin); label.flags(V_AQUAMAP); Draw line = Draw(8, 110).font(Draw::Font::kMenu); auto put = [&](const char* label_str, auto&&... args) { constexpr int kTabWidth = 48; label.string(label_str); int x = label.width() + kTabWidth; x -= x % kTabWidth; line.size(x + 4, 2).y(7).fill(31); line.text(label); line.x(x).text(args...); line = line.y(kH); }; if (netgame) { line = line.y(-kH); put("Online griefing:", "[{}, {}]", stplyr->griefValue/TICRATE, stplyr->griefStrikes); } put("Current Waypoint ID:", "{}", K_GetWaypointID(stplyr->currentwaypoint)); put("Next Waypoint ID:", "{}{}", K_GetWaypointID(stplyr->nextwaypoint), ((stplyr->pflags & PF_WRONGWAY) ? " (WRONG WAY)" : "")); put("Respawn Waypoint ID:", "{}", K_GetWaypointID(stplyr->respawn.wp)); put("Finishline Distance:", "{}", stplyr->distancetofinish); put("Last Safe Lap:", "{}", stplyr->lastsafelap); if (numcheatchecks > 0) { if (stplyr->cheatchecknum == numcheatchecks) put("Cheat Check:", "{} / {} (Can finish)", stplyr->cheatchecknum, numcheatchecks); else put("Cheat Check:", "{} / {}", stplyr->cheatchecknum, numcheatchecks); put("Last Safe Cheat Check:", "{}", stplyr->lastsafecheatcheck); } if (stplyr->bigwaypointgap) { put("Auto Respawn Timer:", "{}", stplyr->bigwaypointgap); } } static void K_DrawBotDebugger(void) { player_t *bot = NULL; if (cv_kartdebugbots.value == 0) { return; } if (R_GetViewNumber() != 0) // only for p1 { return; } if (stplyr->bot == true) { // we ARE the bot bot = stplyr; } else { // get winning bot size_t i; for (i = 0; i < MAXPLAYERS; i++) { player_t *p = NULL; if (playeringame[i] == false) { continue; } p = &players[i]; if (p->spectator == true || p->bot == false) { continue; } if (bot == NULL || p->distancetofinish < bot->distancetofinish) { bot = p; } } } if (bot == NULL) { // no bot exists? return; } V_DrawSmallString(16, 8, V_YELLOWMAP, va("Bot: %s", player_names[bot - players])); V_DrawSmallString(8, 14, 0, va("Difficulty: %d / %d", bot->botvars.difficulty, MAXBOTDIFFICULTY)); V_DrawSmallString(8, 18, 0, va("Difficulty increase: %d", bot->botvars.diffincrease)); V_DrawSmallString(8, 22, 0, va("Rival: %d", (UINT8)(bot->botvars.rival == true))); V_DrawSmallString(8, 26, 0, va("Rubberbanding: %.02f", FIXED_TO_FLOAT(bot->botvars.rubberband) * 100.0f)); V_DrawSmallString(8, 32, 0, va("Item delay: %d", bot->botvars.itemdelay)); V_DrawSmallString(8, 36, 0, va("Item confirm: %d", bot->botvars.itemconfirm)); V_DrawSmallString(8, 42, 0, va("Turn: %d / %d / %d", -BOTTURNCONFIRM, bot->botvars.turnconfirm, BOTTURNCONFIRM)); V_DrawSmallString(8, 46, 0, va("Spindash: %d / %d", bot->botvars.spindashconfirm, BOTSPINDASHCONFIRM)); V_DrawSmallString(8, 50, 0, va("Respawn: %d / %d", bot->botvars.respawnconfirm, BOTRESPAWNCONFIRM)); V_DrawSmallString(8, 56, 0, va("Item priority: %d", bot->botvars.roulettePriority)); V_DrawSmallString(8, 60, 0, va("Item timeout: %d", bot->botvars.rouletteTimeout)); V_DrawSmallString(8, 66, 0, va("Complexity: %d", K_GetTrackComplexity())); V_DrawSmallString(8, 70, 0, va("Bot modifier: %.2f", FixedToFloat(K_BotMapModifier()))); } static void K_DrawGPRankDebugger(void) { gp_rank_e grade = GRADE_E; char gradeChar = '?'; if (cv_debugrank.value == 0) { return; } if (R_GetViewNumber() != 0) // only for p1 { return; } if (grandprixinfo.gp == false) { return; } grade = K_CalculateGPGrade(&grandprixinfo.rank); V_DrawThinString(0, 0, V_SNAPTOTOP|V_SNAPTOLEFT, va("POS: %d / %d", grandprixinfo.rank.position, RANK_NEUTRAL_POSITION)); V_DrawThinString(0, 10, V_SNAPTOTOP|V_SNAPTOLEFT, va("PTS: %d / %d", grandprixinfo.rank.winPoints, grandprixinfo.rank.totalPoints)); V_DrawThinString(0, 20, V_SNAPTOTOP|V_SNAPTOLEFT, va("LAPS: %d / %d", grandprixinfo.rank.laps, grandprixinfo.rank.totalLaps)); V_DrawThinString(0, 30, V_SNAPTOTOP|V_SNAPTOLEFT, va("CONTINUES: %d", grandprixinfo.rank.continuesUsed)); V_DrawThinString(0, 40, V_SNAPTOTOP|V_SNAPTOLEFT, va("PRISONS: %d / %d", grandprixinfo.rank.prisons, grandprixinfo.rank.totalPrisons)); V_DrawThinString(0, 50, V_SNAPTOTOP|V_SNAPTOLEFT, va("RINGS: %d / %d", grandprixinfo.rank.rings, grandprixinfo.rank.totalRings)); V_DrawThinString(0, 60, V_SNAPTOTOP|V_SNAPTOLEFT, va("EMERALD: %s", (grandprixinfo.rank.specialWon == true) ? "YES" : "NO")); switch (grade) { case GRADE_E: { gradeChar = 'E'; break; } case GRADE_D: { gradeChar = 'D'; break; } case GRADE_C: { gradeChar = 'C'; break; } case GRADE_B: { gradeChar = 'B'; break; } case GRADE_A: { gradeChar = 'A'; break; } case GRADE_S: { gradeChar = 'S'; break; } default: { break; } } V_DrawThinString(0, 90, V_SNAPTOTOP|V_SNAPTOLEFT|V_YELLOWMAP, va(" ** FINAL GRADE: %c", gradeChar)); } typedef enum { MM_IN, MM_HOLD, MM_OUT, } messagemode_t; typedef struct { std::string text; sfxenum_t sound; } message_t; struct messagestate_t { std::deque<std::string> messages; std::string objective = ""; tic_t timer = 0; boolean persist = false; messagemode_t mode = MM_IN; const tic_t speedyswitch = 2*TICRATE; const tic_t lazyswitch = 4*TICRATE; void add(std::string msg) { messages.push_back(msg); } void clear() { messages.clear(); switch_mode(MM_IN); } void switch_mode(messagemode_t nextmode) { mode = nextmode; timer = 0; } void tick() { if (messages.size() == 0) { if (!objective.empty()) restore(); else return; } if (exitcountdown) return; if (timer == 0 && mode == MM_IN) S_StartSound(NULL, sfx_s3k47); timer++; switch (mode) { case MM_IN: if (timer > messages[0].length()) switch_mode(MM_HOLD); break; case MM_HOLD: if (messages.size() > 1 && timer > speedyswitch) // Waiting message, switch to it right away! next(); else if (timer > lazyswitch && !persist) // If there's no pending message, we can chill for a bit. switch_mode(MM_OUT); break; case MM_OUT: if (timer > messages[0].length()) next(); break; } } void restore() { switch_mode(MM_IN); persist = true; messages.clear(); messages.push_front(objective); } void next() { switch_mode(MM_IN); persist = false; if (messages.size() > 0) messages.pop_front(); } }; static std::vector<messagestate_t> messagestates{MAXSPLITSCREENPLAYERS}; void K_AddMessage(const char *msg, boolean interrupt, boolean persist) { for (auto &state : messagestates) { if (interrupt) state.clear(); std::string parsedmsg = srb2::Draw::TextElement().parse(msg).string(); if (persist) state.objective = parsedmsg; else state.add(parsedmsg); } } void K_ClearPersistentMessages() { for (auto &state : messagestates) { state.objective = ""; state.clear(); } } // Return value can be used for "paired" splitscreen messages, true = was displayed void K_AddMessageForPlayer(player_t *player, const char *msg, boolean interrupt, boolean persist) { if (!player) return; if (player && !P_IsDisplayPlayer(player)) return; if (player && K_PlayerUsesBotMovement(player)) return; messagestate_t *state = &messagestates[G_PartyPosition(player - players)]; if (interrupt) state->clear(); std::string parsedmsg = srb2::Draw::TextElement().parse(msg).string(); if (persist) state->objective = parsedmsg; else state->add(parsedmsg); } void K_ClearPersistentMessageForPlayer(player_t *player) { if (!player) return; if (player && !P_IsDisplayPlayer(player)) return; messagestate_t *state = &messagestates[G_PartyPosition(player - players)]; state->objective = ""; } void K_TickMessages() { for (auto &state : messagestates) { state.tick(); } } static void K_DrawMessageFeed(void) { int i; if (exitcountdown) return; for (i = 0; i <= r_splitscreen; i++) { messagestate_t state = messagestates[i]; if (state.messages.size() == 0) continue; std::string msg = state.messages[0]; UINT8 sublen = state.timer; if (state.mode == MM_IN) sublen = state.timer; else if (state.mode == MM_HOLD) sublen = msg.length(); else if (state.mode == MM_OUT) sublen = msg.length() - state.timer; std::string submsg = msg.substr(0, sublen); using srb2::Draw; Draw::TextElement text(submsg); text.font(Draw::Font::kMenu); UINT8 x = 160; UINT8 y = 10; SINT8 shift = 0; if (r_splitscreen >= 2) { text.font(Draw::Font::kThin); shift = -2; x = BASEVIDWIDTH/4; y = 5; if (i % 2) x += BASEVIDWIDTH/2; if (i >= 2) y += BASEVIDHEIGHT / 2; } else if (r_splitscreen >= 1) { y = 5; if (i >= 1) y += BASEVIDHEIGHT / 2; } UINT16 sw = text.width(); K_DrawSticker(x - sw/2, y, sw, 0, true); Draw(x, y+shift).align(Draw::Align::kCenter).text(text); } } void K_drawKartHUD(void) { boolean islonesome = false; UINT8 viewnum = R_GetViewNumber(); boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam // Define the X and Y for each drawn object // This is handled by console/menu values K_initKartHUD(); // Draw that fun first person HUD! Drawn ASAP so it looks more "real". if (!camera[viewnum].chase && !freecam) K_drawKartFirstPerson(); if (mapreset) { // HERE COMES A NEW CHALLENGER if (R_GetViewNumber() == 0) K_drawChallengerScreen(); return; } // Draw full screen stuff that turns off the rest of the HUD if (R_GetViewNumber() == 0) { if (g_emeraldWin) K_drawEmeraldWin(false); } if (!demo.attract) { // Draw the CHECK indicator before the other items, so it's overlapped by everything else if (LUA_HudEnabled(hud_check)) // delete lua when? if (!splitscreen && !players[displayplayers[0]].exiting && !freecam) K_drawKartPlayerCheck(); // nametags if (LUA_HudEnabled(hud_names) && cv_drawpickups.value) K_drawKartNameTags(); // Draw WANTED status #if 0 if (gametype == GT_BATTLE) { if (LUA_HudEnabled(hud_wanted)) K_drawKartWanted(); } #endif if (LUA_HudEnabled(hud_minimap)) K_drawKartMinimap(); } if (demo.attract) ; else if (gametype == GT_TUTORIAL) { islonesome = true; } else if (!r_splitscreen) { // Draw the timestamp if (LUA_HudEnabled(hud_time)) { bool ta = modeattacking && !demo.playback; INT32 flags = V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP|V_SNAPTORIGHT; tic_t realtime = stplyr->realtime; if (stplyr->karthud[khud_lapanimation] && !stplyr->exiting && stplyr->laptime[LAP_LAST] != 0 && stplyr->laptime[LAP_LAST] != UINT32_MAX) { if ((stplyr->karthud[khud_lapanimation] / 5) & 1) { realtime = stplyr->laptime[LAP_LAST]; } else { realtime = UINT32_MAX; } } K_drawKartTimestamp(realtime, TIME_X, TIME_Y + (ta ? 2 : 0), flags, 0); if (modeattacking) { if (ta) { using srb2::Draw; Draw(BASEVIDWIDTH - 19, 2) .flags(flags | V_YELLOWMAP) .align(Draw::Align::kRight) .text("\xBE Restart"); } else { using srb2::Draw; Draw row = Draw(BASEVIDWIDTH - 20, TIME_Y + 18).flags(flags).align(Draw::Align::kRight); auto insert = [&](const char *label, UINT32 tics) { Draw::TextElement text = tics != UINT32_MAX ? Draw::TextElement( "{:02}'{:02}\"{:02}", G_TicsToMinutes(tics, true), G_TicsToSeconds(tics), G_TicsToCentiseconds(tics) ) : Draw::TextElement("--'--\"--"); text.font(Draw::Font::kZVote); row.x(-text.width()).flags(V_ORANGEMAP).text(label); row.y(1).text(text); row = row.y(10); }; if (modeattacking & ATTACKING_TIME) insert("Finish: ", hu_demotime); if (modeattacking & ATTACKING_LAP) insert("Best Lap: ", hu_demolap); } } } islonesome = K_drawKartPositionFaces(); } else { islonesome = M_NotFreePlay() == false; if (r_splitscreen == 1) { if (LUA_HudEnabled(hud_time)) { K_drawKart2PTimestamp(); } if (viewnum == r_splitscreen && gametyperules & GTR_POINTLIMIT) { K_drawKartPositionFaces(); } } else if (viewnum == r_splitscreen) { if (LUA_HudEnabled(hud_time)) { K_drawKart4PTimestamp(); } if (gametyperules & GTR_POINTLIMIT) { K_drawKartPositionFaces(); } } } if (!stplyr->spectator && !freecam) // Bottom of the screen elements, don't need in spectate mode { if (demo.attract) { if (demo.attract == DEMO_ATTRACT_TITLE) // Draw logo on title screen demos { INT32 x = BASEVIDWIDTH - 8, y = BASEVIDHEIGHT-8, snapflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SLIDEIN; patch_t *pat = static_cast<patch_t*>(W_CachePatchName((M_UseAlternateTitleScreen() ? "MTSJUMPR1" : "MTSBUMPR1"), PU_CACHE)); const UINT8 *colormap = nullptr; if (INT32 fade = F_AttractDemoExitFade()) { // TODO: Twodee cannot handle // V_DrawCustomFadeScreen. // However, since the screen fade just // uses a colormap, the same colormap can // be applied on a per-patch basis. // I'm only bothering to apply this // colormap to the attract mode sticker, // since it's the lone HUD element. if (lighttable_t *clm = V_LoadCustomFadeMap("FADEMAP0")) { // This must be statically allocated for Twodee static UINT8 *colormap_storage; const UINT8 *fadetable = V_OffsetIntoFadeMap(clm, fade); if (!colormap_storage) Z_MallocAlign(256, PU_STATIC, &colormap_storage, 8); memcpy(colormap_storage, fadetable, 256); colormap = colormap_storage; Z_Free(clm); } } if (r_splitscreen == 3) { x = BASEVIDWIDTH/2; y = BASEVIDHEIGHT/2; snapflags = 0; } V_DrawMappedPatch(x-(SHORT(pat->width)), y-(SHORT(pat->height)), snapflags, pat, colormap); } } else { boolean gametypeinfoshown = false; if (K_PlayerTallyActive(stplyr) == true) { K_DrawPlayerTally(); } if (LUA_HudEnabled(hud_position)) { if (bossinfo.valid) { K_drawBossHealthBar(); } else if (freecam) ; else if ((gametyperules & GTR_POWERSTONES) && !K_PlayerTallyActive(stplyr)) { if (!battleprisons) K_drawKartEmeralds(); } else if (!islonesome && !K_Cooperative()) K_DrawKartPositionNum(stplyr->position); } if (LUA_HudEnabled(hud_gametypeinfo)) { if (gametyperules & GTR_CIRCUIT) { if (numlaps != 1) { K_drawKartLaps(); gametypeinfoshown = true; } } else if (gametyperules & GTR_BUMPERS) { K_drawKartBumpersOrKarma(); gametypeinfoshown = true; } } // Draw the speedometer and/or accessibility icons if (cv_kartspeedometer.value && !r_splitscreen && (LUA_HudEnabled(hud_speedometer))) { K_drawKartSpeedometer(gametypeinfoshown); } else { K_drawKartAccessibilityIcons(gametypeinfoshown, 0); } if (gametyperules & GTR_SPHERES) { K_drawBlueSphereMeter(gametypeinfoshown); } else { K_drawRingCounter(gametypeinfoshown); } // Draw the item window if (LUA_HudEnabled(hud_item) && !freecam) { if (stplyr->itemRoulette.ringbox && stplyr->itemamount == 0 && stplyr->itemtype == 0) { K_drawKartSlotMachine(); } else { K_drawKartItem(); } } } } // Draw the countdowns after everything else. if (stplyr->lives <= 0 && stplyr->playerstate == PST_DEAD) { ; } else if (stplyr->karthud[khud_fault] != 0 && stplyr->karthud[khud_finish] == 0) { K_drawKartFinish(false); } else if (starttime != introtime && leveltime >= introtime && leveltime < starttime+TICRATE) { K_drawKartStartCountdown(); } else if (racecountdown && (!r_splitscreen || !stplyr->exiting)) { char *countstr = va("%d", racecountdown/TICRATE); if (r_splitscreen > 1) V_DrawCenteredString(BASEVIDWIDTH/4, LAPS_Y+1, V_SPLITSCREEN, countstr); else { INT32 karlen = strlen(countstr)*6; // half of 12 V_DrawTimerString((BASEVIDWIDTH/2)-karlen, LAPS_Y+3, V_SPLITSCREEN, countstr); } } // Race overlays if (!freecam) { if (stplyr->exiting) K_drawKartFinish(true); else if (!(gametyperules & GTR_CIRCUIT)) ; else if (stplyr->karthud[khud_lapanimation] && !r_splitscreen) K_drawLapStartAnim(); } // trick panel cool trick if (stplyr->karthud[khud_trickcool]) K_drawTrickCool(); if ((freecam || stplyr->spectator) && LUA_HudEnabled(hud_textspectator)) { K_drawSpectatorHUD(false); } if (R_GetViewNumber() == 0 && g_emeraldWin) K_drawEmeraldWin(true); if (modeattacking || freecam) // everything after here is MP and debug only { K_drawInput(); goto debug; } if ((gametyperules & GTR_KARMA) && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. K_drawKartFreePlay(); if ((netgame || cv_mindelay.value) && r_splitscreen && Playing()) { K_drawMiniPing(); } K_drawKartPowerUps(); if (K_DirectorIsAvailable(viewnum) == true && LUA_HudEnabled(hud_textspectator)) { K_drawSpectatorHUD(true); } else { K_drawInput(); } if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); if (cv_kartdebugnodes.value) { UINT8 p; for (p = 0; p < MAXPLAYERS; p++) V_DrawString(8, 64+(8*p), V_YELLOWMAP, va("%d - %d (%dl)", p, playernode[p], players[p].cmd.latency)); } if (cv_kartdebugcolorize.value && stplyr->mo && stplyr->mo->skin) { INT32 x = 0, y = 0; UINT16 c; for (c = 0; c < numskincolors; c++) { if (skincolors[c].accessible) { UINT8 *cm = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(c), GTC_CACHE); V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT>>1, 0, faceprefix[stplyr->skin][FACE_WANTED], cm); x += 16; if (x > BASEVIDWIDTH-16) { x = 0; y += 16; } } } } debug: K_DrawWaypointDebugger(); K_DrawBotDebugger(); K_DrawDirectorDebugger(); K_DrawGPRankDebugger(); K_DrawMessageFeed(); } void K_DrawSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean isSmall) { patch_t *stickerEnd; INT32 height; if (isSmall == true) { stickerEnd = static_cast<patch_t*>(W_CachePatchName("K_STIKE2", PU_CACHE)); height = 6; } else { stickerEnd = static_cast<patch_t*>(W_CachePatchName("K_STIKEN", PU_CACHE)); height = 11; } V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, flags, stickerEnd, NULL); V_DrawFill(x, y, width, height, 24|flags); V_DrawFixedPatch((x + width)*FRACUNIT, y*FRACUNIT, FRACUNIT, flags|V_FLIP, stickerEnd, NULL); }