// BLANKART //----------------------------------------------------------------------------- // Copyright (C) 2018-2020 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 "k_hud.h" #include "info.h" #include "k_kart.h" #include "k_battle.h" #include "k_grandprix.h" #include "k_boss.h" #include "k_color.h" #include "k_director.h" #include "k_items.h" #include "p_mobj.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 "g_party.h" #include "h_timers.h" #include "k_bot.h" // K_DrawBotDebugger #include "k_waypoint.h" #define NUMPOSNUMS 10 #define NUMPOSFRAMES 7 // White, three blues, three reds #define NUMWINFRAMES 6 // Red, yellow, green, cyan, blue, purple // Hud offset cvars #define IMPL_HUD_OFFSET_X(name)\ consvar_t cv_##name##_xoffset = CVAR_INIT ("hud_" #name "_xoffset", "0", CV_SAVE, CV_Signed, NULL); #define IMPL_HUD_OFFSET_Y(name)\ consvar_t cv_##name##_yoffset = CVAR_INIT ("hud_" #name "_yoffset", "0", CV_SAVE, CV_Signed, NULL); #define IMPL_HUD_OFFSET(name)\ IMPL_HUD_OFFSET_X(name)\ IMPL_HUD_OFFSET_Y(name) IMPL_HUD_OFFSET(item); // Item box IMPL_HUD_OFFSET(time); // Time IMPL_HUD_OFFSET(laps); // Number of laps IMPL_HUD_OFFSET(rings); // Number of rings IMPL_HUD_OFFSET(dnft); // Countdown (did not finish timer) IMPL_HUD_OFFSET(speed); // Speedometer IMPL_HUD_OFFSET(acce); // Accessibility IMPL_HUD_OFFSET(timers); // Timers IMPL_HUD_OFFSET(draft); // Drafting IMPL_HUD_OFFSET(lives); // Lives and Stats IMPL_HUD_OFFSET(posi); // Position in race IMPL_HUD_OFFSET(face); // Mini rankings IMPL_HUD_OFFSET(stcd); // Starting countdown IMPL_HUD_OFFSET_Y(chek); // Check gfx IMPL_HUD_OFFSET(mini); // Minimap IMPL_HUD_OFFSET(want); // Wanted IMPL_HUD_OFFSET(lapem); // Lap emblem //IMPL_HUD_OFFSET(stat); // Stats #undef IMPL_HUD_OFFSET #undef IMPL_HUD_OFFSET_X #undef IMPL_HUD_OFFSET_Y static CV_PossibleValue_t speedo_cons_t[]= { {0, "Default"}, {1, "Small"}, {2, "P-Meter"}, {0, NULL}}; consvar_t cv_newspeedometer = CVAR_INIT ("newspeedometer", "Small", CV_SAVE, speedo_cons_t, NULL); static CV_PossibleValue_t inputdisplay_cons_t[] = {{0, "Off"}, {1, "Wheel"}, {2, "Stick"}, {3, "SRB2"}, {0, NULL}}; consvar_t cv_showinput = CVAR_INIT ("showinput", "Off", CV_SAVE, inputdisplay_cons_t, NULL); consvar_t cv_colorizedhud = CVAR_INIT ("colorizedhud", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_colorizeditembox = CVAR_INIT ("colorizeditembox", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_darkitembox = CVAR_INIT ("darkitembox", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_showfinishedplayers = CVAR_INIT ("showfinishedplayers", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_smoothposition = CVAR_INIT ("smoothposition", "On", CV_SAVE, CV_OnOff, NULL); static CV_PossibleValue_t driftgauge_cons_t[] = {{0, "Off"}, {1, "Spee"}, {2, "Achii"}, {3, "Wifi"}, {4, "Chaotix"}, {5, "Numbers"}, {6, "Legacy"}, {7, "Legacy Small"}, {8, "Legacy Large Numbers"}, {9, "Large Num"}, {0, NULL}}; consvar_t cv_driftgauge = CVAR_INIT ("kartdriftgauge", "Off", CV_SAVE, driftgauge_cons_t, NULL); consvar_t cv_driftgaugeoffset = CVAR_INIT ("kartdriftgaugeoffset", "-10", CV_SAVE|CV_FLOAT, CV_Signed, NULL); static CV_PossibleValue_t HudColor_cons_t[MAXSKINCOLORS+1]; consvar_t cv_colorizedhudcolor = CVAR_INIT ("colorizedhudcolor", "Skin Color", CV_SAVE, HudColor_cons_t, NULL); consvar_t cv_newtabranking = CVAR_INIT ("newtabranking", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_draftindicator = CVAR_INIT ("draftindicator", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_showstats = CVAR_INIT ("showstats", "On", CV_SAVE, CV_OnOff, NULL); //{ Patch Definitions static patch_t *kp_nodraw; // Stickers static patch_t *kp_timesticker[2]; static patch_t *kp_timestickerwide[2]; static patch_t *kp_lapsticker[2]; static patch_t *kp_lapstickerbig[2]; static patch_t *kp_lapstickerbig2[2]; static patch_t *kp_lapstickerwide[2]; static patch_t *kp_lapstickernarrow[2]; static patch_t *kp_bumpersticker[2]; static patch_t *kp_bumperstickerwide[2]; static patch_t *kp_karmasticker[2]; static patch_t *kp_timeoutsticker[2]; static patch_t *kp_splitlapflag; static patch_t *kp_splitkarmabomb; static patch_t *kp_startcountdown[20]; static patch_t *kp_racefinish[6]; static patch_t *kp_positionnum[NUMPOSNUMS][NUMPOSFRAMES]; static patch_t *kp_winnernum[NUMPOSFRAMES]; patch_t *kp_facenum[MAXPLAYERS+1] = {}; static patch_t *kp_facehighlight[8]; static patch_t *kp_nocontestminimap; static patch_t *kp_itemboxminimap; static patch_t *kp_ringsticker[5]; static patch_t *kp_ringsplitscreen; static patch_t *kp_ringdebtminus; static patch_t *kp_ringdebtminussmall; static patch_t *kp_speedometersticker[2]; static patch_t *kp_speedometerlabel[4]; static patch_t *kp_kartzspeedo[25]; static patch_t *kp_kartzspeedo_smol[25]; static patch_t *kp_driftgauge[14]; static patch_t *kp_driftgaugeparts[5]; static patch_t *kp_flamometer[6]; static patch_t *kp_flamefire[18]; // Frames of animation for the fire #define MAXFLAMOFIRETICS 8 // Rotating Alt. Invin. sparkles static patch_t *kp_altinvinsparkle; static patch_t *kp_rankbumper; static patch_t *kp_tinybumper[2]; static patch_t *kp_ranknobumpers; static patch_t *kp_rankcapsule; 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[8]; static patch_t *kp_itemalt[4]; static patch_t *kp_itemtimer[2]; static patch_t *kp_itemmulsticker[4]; static patch_t *kp_itemx; static patch_t *kp_check[11]; static patch_t *kp_eggnum[4]; 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_bossbar[8]; static patch_t *kp_bossret[4]; static patch_t *joybacking; static patch_t *joyknob; static patch_t *joyshadow; void K_RegisterKartHUDStuff(void) { K_ReloadHUDColorCvar(); #define REG_HUD_OFFSET_X(name)\ CV_RegisterVar(&cv_##name##_xoffset); #define REG_HUD_OFFSET_Y(name)\ CV_RegisterVar(&cv_##name##_yoffset); #define REG_HUD_OFFSET(name)\ REG_HUD_OFFSET_X(name)\ REG_HUD_OFFSET_Y(name) REG_HUD_OFFSET(item); // Item box REG_HUD_OFFSET(time); // Time REG_HUD_OFFSET(laps); // Number of laps REG_HUD_OFFSET(rings); // Number of laps REG_HUD_OFFSET(dnft); // Countdown (did not finish timer) REG_HUD_OFFSET(speed); // Speedometer REG_HUD_OFFSET(acce); // Accessibility REG_HUD_OFFSET(timers); // Item Timers REG_HUD_OFFSET(draft); // Drafting REG_HUD_OFFSET(lives); // Lives and Stats REG_HUD_OFFSET(posi); // Position in race REG_HUD_OFFSET(face); // Mini rankings REG_HUD_OFFSET(stcd); // Starting countdown REG_HUD_OFFSET_Y(chek); // Check gfx REG_HUD_OFFSET(mini); // Minimap REG_HUD_OFFSET(want); // Wanted REG_HUD_OFFSET(lapem); // Lap emblem //REG_HUD_OFFSET(stat); // Stats #undef REG_HUD_OFFSET #undef REG_HUD_OFFSET_X #undef REG_HUD_OFFSET_Y CV_RegisterVar(&cv_newspeedometer); CV_RegisterVar(&cv_showinput); CV_RegisterVar(&cv_colorizedhud); CV_RegisterVar(&cv_colorizedhudcolor); CV_RegisterVar(&cv_colorizeditembox); CV_RegisterVar(&cv_darkitembox); CV_RegisterVar(&cv_showfinishedplayers); CV_RegisterVar(&cv_smoothposition); CV_RegisterVar(&cv_driftgauge); CV_RegisterVar(&cv_driftgaugeoffset); CV_RegisterVar(&cv_newtabranking); CV_RegisterVar(&cv_draftindicator); CV_RegisterVar(&cv_showstats); // k_items.c CV_RegisterVar(&cv_fancyroulette); CV_RegisterVar(&cv_fancyroulettespeed); CV_RegisterVar(&cv_huditemamount); } void K_LoadKartHUDGraphics(void) { INT32 i, j; char buffer[9]; // Null Stuff HU_UpdatePatch(&kp_nodraw, "K_TRNULL"); // Stickers HU_UpdatePatch(&kp_timesticker[0], "K_STTIME"); HU_UpdatePatch(&kp_timestickerwide[0], "K_STTIMW"); HU_UpdatePatch(&kp_lapsticker[0], "K_STLAPS"); HU_UpdatePatch(&kp_lapstickerbig[0], "K_STLAPB"); HU_UpdatePatch(&kp_lapstickerbig2[0], "K_STLA2B"); HU_UpdatePatch(&kp_lapstickerwide[0], "K_STLAPW"); HU_UpdatePatch(&kp_lapstickernarrow[0], "K_STLAPN"); HU_UpdatePatch(&kp_bumpersticker[0], "K_STBALN"); HU_UpdatePatch(&kp_bumperstickerwide[0], "K_STBALW"); HU_UpdatePatch(&kp_karmasticker[0], "K_STKARM"); HU_UpdatePatch(&kp_timeoutsticker[0], "K_STTOUT"); // Colored Stickers HU_UpdatePatch(&kp_timesticker[1], "K_SCTIME"); HU_UpdatePatch(&kp_timestickerwide[1], "K_SCTIMW"); HU_UpdatePatch(&kp_lapsticker[1], "K_SCLAPS"); HU_UpdatePatch(&kp_lapstickerbig[1], "K_SCLAPB"); HU_UpdatePatch(&kp_lapstickerbig2[1], "K_SCLA2B"); HU_UpdatePatch(&kp_lapstickerwide[1], "K_SCLAPW"); HU_UpdatePatch(&kp_lapstickernarrow[1], "K_SCLAPN"); HU_UpdatePatch(&kp_bumpersticker[1], "K_SCBALN"); HU_UpdatePatch(&kp_bumperstickerwide[1], "K_SCBALW"); HU_UpdatePatch(&kp_karmasticker[1], "K_SCKARM"); HU_UpdatePatch(&kp_timeoutsticker[1], "K_SCTOUT"); // Splitscreen HU_UpdatePatch(&kp_splitlapflag, "K_SPTLAP"); HU_UpdatePatch(&kp_splitkarmabomb, "K_SPTKRM"); // 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"); // 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, "K_POSNxx"); for (i = 0; i < NUMPOSNUMS; i++) { buffer[6] = '0'+i; for (j = 0; j < NUMPOSFRAMES; j++) { //sprintf(buffer, "K_POSN%d%d", i, j); buffer[7] = '0'+j; HU_UpdatePatch(&kp_positionnum[i][j], "%s", buffer); } } sprintf(buffer, "K_POSNWx"); for (i = 0; i < NUMWINFRAMES; i++) { buffer[7] = '0'+i; HU_UpdatePatch(&kp_winnernum[i], "%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); kp_facehighlight[i]->pivot.x = kp_facehighlight[i]->width / 2; kp_facehighlight[i]->pivot.y = kp_facehighlight[i]->height / 2; kp_facehighlight[i]->alignflags |= PATCHALIGN_USEPIVOTS; } // Special minimap icons HU_UpdatePatch(&kp_nocontestminimap, "MINIDEAD"); HU_UpdatePatch(&kp_itemboxminimap, "K_ITMMM"); // Rings & Lives HU_UpdatePatch(&kp_ringsticker[0], "K_RNGHD"); HU_UpdatePatch(&kp_ringsticker[1], "K_RNGHL"); HU_UpdatePatch(&kp_ringsticker[2], "K_RNGHDC"); HU_UpdatePatch(&kp_ringsticker[3], "K_RNGHLC"); HU_UpdatePatch(&kp_ringsplitscreen, "K_RNGSS"); HU_UpdatePatch(&kp_ringdebtminus, "K_RNGDM"); HU_UpdatePatch(&kp_ringdebtminussmall, "K_RNGSM"); // Speedometer HU_UpdatePatch(&kp_speedometersticker[0], "SP_SMSTC"); // Speedometer Sticker Color HU_UpdatePatch(&kp_speedometersticker[1], "SC_SMSTC"); // Driftgauge Stickers HU_UpdatePatch(&kp_driftgauge[0], "K_DGAU0"); // Spee HU_UpdatePatch(&kp_driftgauge[1], "K_DGAU1"); // Achii HU_UpdatePatch(&kp_driftgauge[2], "K_WDGSG"); // Wifi HU_UpdatePatch(&kp_driftgauge[3], "K_WDGBG"); // Anywhere you go HU_UpdatePatch(&kp_driftgauge[4], "K_DGAU3"); // Chaotix HU_UpdatePatch(&kp_driftgauge[5], "K_DGAU"); // Legacy HU_UpdatePatch(&kp_driftgauge[6], "K_DGSU"); // Legacy Small HU_UpdatePatch(&kp_driftgauge[7], "K_DGAUC0"); HU_UpdatePatch(&kp_driftgauge[8], "K_DGAUC1"); HU_UpdatePatch(&kp_driftgauge[9], "K_WDGSGC"); HU_UpdatePatch(&kp_driftgauge[10], "K_WDGBGC"); HU_UpdatePatch(&kp_driftgauge[11], "K_DGAUC3"); HU_UpdatePatch(&kp_driftgauge[12], "K_DCAU"); HU_UpdatePatch(&kp_driftgauge[13], "K_DCSU"); // Driftgauge Parts HU_UpdatePatch(&kp_driftgaugeparts[0], "K_WDGM1"); HU_UpdatePatch(&kp_driftgaugeparts[1], "K_WDGM2"); HU_UpdatePatch(&kp_driftgaugeparts[2], "K_WDGM3"); HU_UpdatePatch(&kp_driftgaugeparts[3], "K_WDGM4"); HU_UpdatePatch(&kp_driftgaugeparts[4], "K_DGAU3M"); // Alt. Invin. Sparkles HU_UpdatePatch(&kp_altinvinsparkle, "ALTISPRK"); kp_altinvinsparkle->pivot.x = kp_altinvinsparkle->width / 2; kp_altinvinsparkle->pivot.y = kp_altinvinsparkle->height / 2; kp_altinvinsparkle->alignflags |= PATCHALIGN_USEPIVOTS; // Flamometer UI Elements HU_UpdatePatch(&kp_flamometer[0], "THERMOBACK"); HU_UpdatePatch(&kp_flamometer[1], "THERMOFUEL"); HU_UpdatePatch(&kp_flamometer[2], "THERMOTEMP"); HU_UpdatePatch(&kp_flamometer[3], "THERMOMETRE"); HU_UpdatePatch(&kp_flamometer[4], "THERMCBACK"); HU_UpdatePatch(&kp_flamometer[5], "THERMCMETRE"); //Flamometer Fire { const char* patchNames[] = { "THFIREX", "THFIRE1", "THFIRE2", "THFIRE3", "THFIRE4", "THFIRE5", "THFIRE6", "THFIRE7", "THFIRE8", "THFIRCX", "THFIRC1", "THFIRC2", "THFIRC3", "THFIRC4", "THFIRC5", "THFIRC6", "THFIRC7", "THFIRC8" }; for (size_t m = 0; m < sizeof(patchNames) / sizeof(patchNames[0]); ++m) { kp_flamefire[m] = W_CachePatchName(patchNames[m], PU_HUDGFX); } } // Speedometer labels HU_UpdatePatch(&kp_speedometerlabel[0], "SP_MKMH"); HU_UpdatePatch(&kp_speedometerlabel[1], "SP_MMPH"); HU_UpdatePatch(&kp_speedometerlabel[2], "SP_MFRAC"); HU_UpdatePatch(&kp_speedometerlabel[3], "SP_MPERC"); { const char* patchNames[] = { "K_KZSP1", "K_KZSP2", "K_KZSP3", "K_KZSP4", "K_KZSP5", "K_KZSP6", "K_KZSP7", "K_KZSP8", "K_KZSP9", "K_KZSP10", "K_KZSP11", "K_KZSP12", "K_KZSP13", "K_KZSP14", "K_KZSP15", "K_KZSP16", "K_KZSP17", "K_KZSP18", "K_KZSP19", "K_KZSP20", "K_KZSP21", "K_KZSP22", "K_KZSP23", "K_KZSP24", "K_KZSP25" }; for (size_t m = 0; m < sizeof(patchNames) / sizeof(patchNames[0]); ++m) { kp_kartzspeedo[m] = W_CachePatchName(patchNames[m], PU_HUDGFX); } } { const char* patchNames[] = { "K_KZSS1", "K_KZSS2", "K_KZSS3", "K_KZSS4", "K_KZSS5", "K_KZSS6", "K_KZSS7", "K_KZSS8", "K_KZSS9", "K_KZSS10", "K_KZSS11", "K_KZSS12", "K_KZSS13", "K_KZSS14", "K_KZSS15", "K_KZSS16", "K_KZSS17", "K_KZSS18", "K_KZSS19", "K_KZSS20", "K_KZSS21", "K_KZSS22", "K_KZSS23", "K_KZSS24", "K_KZSS25" }; for (size_t m = 0; m < sizeof(patchNames) / sizeof(patchNames[0]); ++m) { kp_kartzspeedo_smol[m] = W_CachePatchName(patchNames[m], PU_HUDGFX); } } // Extra ranking icons HU_UpdatePatch(&kp_rankbumper, "K_BLNICO"); HU_UpdatePatch(&kp_tinybumper[0], "K_BLNA"); HU_UpdatePatch(&kp_tinybumper[1], "K_BLNB"); HU_UpdatePatch(&kp_ranknobumpers, "K_NOBLNS"); // 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_itembg[2], "K_ISBG"); HU_UpdatePatch(&kp_itembg[3], "K_ISBGD"); HU_UpdatePatch(&kp_itembg[4], "K_ITBC"); HU_UpdatePatch(&kp_itembg[5], "K_ITBCD"); HU_UpdatePatch(&kp_itembg[6], "K_ISBC"); HU_UpdatePatch(&kp_itembg[7], "K_ISBCD"); HU_UpdatePatch(&kp_itemalt[0], "K_ALTITM"); HU_UpdatePatch(&kp_itemalt[1], "K_ALTITS"); HU_UpdatePatch(&kp_itemalt[2], "K_ALTIMM"); HU_UpdatePatch(&kp_itemalt[3], "K_ALTISM"); HU_UpdatePatch(&kp_itemtimer[0], "K_ITIMER"); HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); HU_UpdatePatch(&kp_itemmulsticker[1], "K_ISMUL"); HU_UpdatePatch(&kp_itemmulsticker[2], "K_ITMULC"); HU_UpdatePatch(&kp_itemmulsticker[3], "K_ISMULC"); HU_UpdatePatch(&kp_itemx, "K_ITX"); for (i = 0; i < numkartitems; i++) { kartitem_t *item = &kartitems[i]; INT32 n = sizeof(item->graphics)/sizeof(*item->graphics); for (j = 0; j < n; j++) { kartitemgraphics_t *graphics = &item->graphics[j]; for (INT32 k = 0; k < graphics->numpatches; k++) { if (graphics->patches[k] == NULL) { // have to manually do this because HU_UpdatePatch only checks graphics from added WADs during partadd // UUUUUGGGGGGGHHHHHHHHHHHHHHHHHHh lumpnum_t lump = W_CheckNumForName(graphics->patchnames[k]); graphics->patches[k] = lump == LUMPERROR ? missingpat : W_CachePatchNum(lump, PU_HUDGFX); } else HU_UpdatePatch(&graphics->patches[k], graphics->patchnames[k]); } } R_AddKartItemSprites(item); } // CHECK indicators sprintf(buffer, "K_CHECKx"); for (i = 0; i < 10; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_check[i], "%s", buffer); } HU_UpdatePatch(&kp_check[10], "K_CHECKA"); // Eggman warning numbers sprintf(buffer, "K_EGGNx"); for (i = 0; i < 4; 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 Stick HU_UpdatePatch(&joybacking, "JOYBCK"); HU_UpdatePatch(&joyknob, "JOYKNB"); HU_UpdatePatch(&joyshadow, "JOYSHD"); // 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"); 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); } } //} INT32 ITEM_X, ITEM_Y; // Item Window INT32 TIME_X, TIME_Y; // Time Sticker INT32 LAPS_X, LAPS_Y; // Lap Sticker INT32 RING_X, RING_Y; // Player Rings INT32 SPDM_X, SPDM_Y; // Speedometer INT32 ACCE_X, ACCE_Y; // Accessibility INT32 DRAT_X, DRAT_Y; // Drafting INT32 LIVE_X, LIVE_Y; // Stats and Lives 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 RING2_X, RING2_Y; INT32 POSI2_X, POSI2_Y; INT32 DRAT2_X, DRAT2_Y; INT32 LIVE2_X, LIVE2_Y; void K_ReloadHUDColorCvar(void) { HudColor_cons_t[0].value = 0; HudColor_cons_t[0].strvalue = "Skin Color"; for (INT32 i = 1; i < numskincolors; i++) { HudColor_cons_t[i].value = i; HudColor_cons_t[i].strvalue = skincolors[i].name; // SRB2kart } HudColor_cons_t[MAXSKINCOLORS].value = 0; HudColor_cons_t[MAXSKINCOLORS].strvalue = NULL; } boolean K_UseColorHud(void) { return cv_colorizedhud.value; } skincolornum_t K_GetHudColor(void) { if (cv_colorizedhud.value && cv_colorizedhudcolor.value) return cv_colorizedhudcolor.value; return ((stplyr && gamestate == GS_LEVEL) ? stplyr->skincolor : cv_playercolor[0].value); } static boolean K_BigLapSticker(void) { return ((cv_numlaps.value > 9) && (!stplyr->exiting)); } patch_t *K_getItemBoxPatch(boolean small, boolean dark) { UINT8 ofs = (cv_darkitembox.value && dark ? 1 : 0) + (small ? 2 : 0); return (cv_colorizeditembox.value && K_UseColorHud()) ? kp_itembg[4+ofs] : kp_itembg[ofs]; } patch_t *K_getItemMulPatch(boolean small) { UINT8 ofs = small ? 1 : 0; return K_UseColorHud() ? kp_itemmulsticker[2+ofs] : kp_itemmulsticker[ofs]; } patch_t *K_getItemAltPatch(boolean small, boolean multimode) { UINT8 ofs = (small ? 1 : 0) + (multimode ? 2 : 0); return kp_itemalt[ofs]; } boolean K_ShowAltItemIcon(kartitemtype_e type, boolean small) { return K_GetCachedItemPatchEx(type, small, 0, (SINT8)true) == missingpat; } // 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.scaledwidth; screenHeight = vid.scaledheight; 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 = cv_fov[cameraNum].value - 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; } if (camera[cameraNum].postimgflags & POSTIMG_FLIP) { result->y = -result->y; } // 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.scaledwidth) - BASEVIDWIDTH)<<(FRACBITS-((r_splitscreen >= 2) ? 2 : 1)); result->y -= ((vid.scaledheight) - 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 + cv_item_xoffset.value; // 5 ITEM_Y = 5 + cv_item_yoffset.value; // 5 // Level Timer TIME_X = BASEVIDWIDTH - 148 + cv_time_xoffset.value; // 172 TIME_Y = 9 + cv_time_yoffset.value; // 9 // Level Laps LAPS_X = 9 + cv_laps_xoffset.value; // 9 LAPS_Y = BASEVIDHEIGHT - 29 + cv_laps_yoffset.value; // 171 // Player Rings RING_X = 9 + cv_rings_xoffset.value; // 9 RING_Y = BASEVIDHEIGHT - 29 + cv_rings_yoffset.value; // 171 // Speedometer SPDM_X = 9 + cv_speed_xoffset.value; // 9 SPDM_Y = BASEVIDHEIGHT - 29 + cv_speed_yoffset.value; // 171 // Accessibility ACCE_X = 9 + cv_speed_xoffset.value; // 9 ACCE_Y = BASEVIDHEIGHT - 29 + cv_speed_yoffset.value; // 171 // Drafting DRAT_X = 172 + cv_draft_xoffset.value; // 172 DRAT_Y = BASEVIDHEIGHT - 41 + cv_draft_yoffset.value; // 159 // Lives and Stats LIVE_X = 9 + cv_lives_xoffset.value; // 9 LIVE_Y = BASEVIDHEIGHT - 29 + cv_lives_yoffset.value; // 171 // Position Number POSI_X = BASEVIDWIDTH - 9 + cv_posi_xoffset.value; // 268 POSI_Y = BASEVIDHEIGHT - 9 + cv_posi_yoffset.value; // 138 // Top-Four Faces FACE_X = 9 + cv_face_xoffset.value; // 9 FACE_Y = 92 + cv_face_yoffset.value; // 92 // Starting countdown STCD_X = BASEVIDWIDTH/2 + cv_stcd_xoffset.value; // 9 STCD_Y = BASEVIDHEIGHT/2 + cv_stcd_yoffset.value; // 92 // CHECK graphic CHEK_Y = BASEVIDHEIGHT + cv_chek_yoffset.value; // 200 // Minimap MINI_X = BASEVIDWIDTH - 50 + cv_mini_xoffset.value; // 270 MINI_Y = (BASEVIDHEIGHT/2)-16 + cv_mini_yoffset.value; // 84 // Battle WANTED poster WANT_X = BASEVIDWIDTH - 55 + cv_want_xoffset.value; // 270 WANT_Y = BASEVIDHEIGHT- 71 + cv_want_yoffset.value; // 176 if (r_splitscreen) // Splitscreen { // Lock the positions in. ITEM_X = 5; ITEM_Y = 3; LAPS_X = 9; LAPS_Y = (BASEVIDHEIGHT/2)-24; RING_X = 9; RING_Y = (BASEVIDHEIGHT/2)-24; SPDM_X = 9; SPDM_Y = (BASEVIDHEIGHT/2)-24; POSI_X = BASEVIDWIDTH; POSI_Y = (BASEVIDHEIGHT/2)-2; DRAT_X = 172; DRAT_Y = (BASEVIDHEIGHT/2)-34; LIVE_X = 9; LIVE_Y = (BASEVIDHEIGHT/2)-24; STCD_X = BASEVIDWIDTH/2; STCD_Y = BASEVIDHEIGHT/4; MINI_X = BASEVIDWIDTH - 50; 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; RING_X = 3; RING_Y = (BASEVIDHEIGHT/2)-12; POSI_X = 24; POSI_Y = (BASEVIDHEIGHT/2)-26; DRAT_X = 95; DRAT_Y = (BASEVIDHEIGHT/2)-32; LIVE_X = 3; LIVE_Y = (BASEVIDHEIGHT/2)-12; // 2P (top right) ITEM2_X = (BASEVIDWIDTH/2)-39; ITEM2_Y = -8; LAPS2_X = (BASEVIDWIDTH/2)-43; LAPS2_Y = (BASEVIDHEIGHT/2)-12; RING2_X = (BASEVIDWIDTH/2)-43; RING2_Y = (BASEVIDHEIGHT/2)-12; POSI2_X = (BASEVIDWIDTH/2)-4; POSI2_Y = (BASEVIDHEIGHT/2)-26; DRAT2_X = 95; DRAT2_Y = (BASEVIDHEIGHT/2)-32; LIVE2_X = (BASEVIDWIDTH/2)-43; LIVE2_Y = (BASEVIDHEIGHT/2)-12; // 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); if (r_splitscreen > 2) // 4P-only { MINI_X = (BASEVIDWIDTH/2); MINI_Y = (BASEVIDHEIGHT/2); } } } } // RadioRacers static trackingResult_t K_getRoulettePositionForTrackingPlayer(void) { trackingResult_t result = {0}; // No player object? Not bothering. const boolean doesPlayerHaveMo = !((stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo))); if (!doesPlayerHaveMo) return result; vector3_t v = { R_InterpolateFixed(stplyr->mo->old_x, stplyr->mo->x) + stplyr->mo->sprxoff, R_InterpolateFixed(stplyr->mo->old_y, stplyr->mo->y) + stplyr->mo->spryoff, R_InterpolateFixed(stplyr->mo->old_z, stplyr->mo->z) + stplyr->mo->sprzoff + (stplyr->mo->height >> 1), }; vector3_t v2 = { 0, 0, 64 * stplyr->mo->scale * P_MobjFlip(stplyr->mo) }; FV3_Add(&v,&v2); K_ObjectTracking(&result, &v, false); return result; } void K_getItemBoxDrawinfo(drawinfo_t *out, rouletteinfo_t *rinfo) { INT32 fx, fy, fflags; boolean flipamount = false; fixed_t baseHudScale = FRACUNIT; float baseHudScaleFloat = (float)((float)(baseHudScale) / (FRACUNIT)); fflags = V_HUDTRANS; // pain and suffering defined below if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = ITEM_X; fy = ITEM_Y; // We are NOT supporting this for splitscreen, the vanilla layout is easier to read. if (cv_rouletteonplayer.value && r_splitscreen == 0) { trackingResult_t result = K_getRoulettePositionForTrackingPlayer(); if(result.x != 0 && result.y != 0) { fflags = 0; rinfo->flags |= (RINFO_DRAWONPLAYER|RINFO_USECROP); /** * This solution WILL obscure the player's view. * The item roulette background is transparent .. but some items are pretty loud visually (e.g. flame shield) * Making it scale any smaller than 75% is unreasonable (it spins pretty fast towards the end of a race).. * so we'll just make it a bit translucent. */ baseHudScale = (3*FRACUNIT)/5; // 60% baseHudScaleFloat = (float)((float)(baseHudScale) / (FRACUNIT)); rinfo->crop.x = rinfo->crop.y = (int)(8 * baseHudScaleFloat); // Upside down? const boolean isupsidedown = (stplyr->mo->eflags & MFE_VERTICALFLIP); /** * Offset it horizontally so it's closer to the center of the player. * Offset it vertically so it's floating above the player. */ INT32 base_x = 22; INT32 base_y = 18; INT32 baseUpsideDown_y = 18; base_x = (int) (22 * baseHudScaleFloat); base_y = (int) ((35 * baseHudScaleFloat) + 2); baseUpsideDown_y = (int) ((18 * baseHudScaleFloat) + 2); fx = (result.x / FRACUNIT) - base_x; // 18 (+2) fy = (result.y / FRACUNIT) - (isupsidedown ? baseUpsideDown_y : base_y); // 15, 28 // In case I forget the math.. // ROULETTE_SPACING (36) * baseHudScale // If we're drawing the item box at 75% scale, then it's 36 * 75%; rinfo->intSpacing = (INT32)((((float)(ROULETTE_SPACING)) * baseHudScaleFloat)); rinfo->spacing = (rinfo->intSpacing << FRACBITS); rinfo->offset = FixedMul(rinfo->offset, FixedDiv(rinfo->spacing, ROULETTE_SPACING_FIXED)); if (stplyr->exiting) fflags = V_HUDTRANS; } } else { fflags = V_SNAPTOTOP|V_SNAPTOLEFT|V_SPLITSCREEN; } } else // now we're having a fun game. { if (stplyrnum == 0 || stplyrnum == 2) // 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; } } out->x = fx; out->y = fy; out->flags = fflags; out->flipamount = flipamount; out->hudScale = baseHudScale; out->hudScaleFloat = baseHudScaleFloat; } void K_getLapsDrawinfo(drawinfo_t *out) { INT32 fx, fy, splitflags = 0; // pain and suffering defined below if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = LAPS_X; fy = LAPS_Y; splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANS|V_SPLITSCREEN; } else { if (stplyrnum == 0 || stplyrnum == 2) // 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; } } out->x = fx; out->y = fy; out->flags = splitflags; } void K_getRingsDrawinfo(drawinfo_t *out) { INT32 fx, fy, splitflags = 0; // pain and suffering defined below if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = RING_X; fy = RING_Y; splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANS|V_SPLITSCREEN; } else { if (stplyrnum == 0 || stplyrnum == 2) // If we are P1 or P3... { fx = RING_X; fy = RING_Y; splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { fx = RING2_X; fy = RING2_Y; splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } } out->x = fx; out->y = fy; out->flags = splitflags; } void K_getMinimapDrawinfo(drawinfo_t *out) { INT32 fx = MINI_X, fy = MINI_Y, fflags = (r_splitscreen < 2 ? V_SNAPTORIGHT : 0); // flags should only be 0 when it's centered (4p split) fx -=minimapinfo.minimap_pic->width/2; fy -=minimapinfo.minimap_pic->height/2; out->x = fx; out->y = fy; out->flags = fflags; } static void K_DrawItemBar(INT32 fx, INT32 fy, fixed_t scale, INT32 fflags, fixed_t itembar, UINT8 colors[static 3]) { const boolean fourp = r_splitscreen > 1; const INT32 barlength = (fourp ? 12 : 26)*scale; const INT32 length = min(barlength, FixedMul(itembar, barlength)); const INT32 height = (fourp ? 1 : 2)*scale; const INT32 x = ((fx * FRACUNIT) + ((fourp ? 17 : 11) * scale)), y = ((fy * FRACUNIT) + ((fourp ? 27 : 35) * scale)); V_DrawSciencePatch(x, y, V_HUDTRANS|fflags, kp_itemtimer[fourp ? 1 : 0], scale); V_DrawFixedFill(x+scale, y+scale, min(scale, length), height, colors[2]|fflags); // the left edge V_DrawFixedFill(x+max(scale, length), y+scale, min(FRACUNIT, length), height, colors[2]|fflags); // the right edge if (!fourp) V_DrawFixedFill(x+2*scale, y+2*scale, max(0, length - 2*scale), scale, colors[1]|fflags); // the dulled underside V_DrawFixedFill(x+2*scale, y+scale, max(0, length - 2*scale), scale, colors[0]|fflags); // the shine } // TODO: use an actual patch overlay and clip it instead of using a rect, now that an actual patch can be added for this static void K_DrawItemCooldown(INT32 fx, INT32 fy, INT32 fflags, tic_t timer, tic_t maxtimer, fixed_t scale) { const boolean fourp = r_splitscreen > 1; const INT32 rectTopFour = 13*scale; INT32 rectLeft = 5*scale; INT32 rectTop = 6*scale; INT32 rectSize = 40*scale; fixed_t prog = FixedDiv(timer, maxtimer); INT32 length = min(rectSize, FixedMul(rectSize, prog)); if (timer > 0 && maxtimer > 0) { if (fourp) { rectLeft = 14*scale; rectTop = 14*scale; rectSize = 20*scale; length = min(rectSize, FixedMul(rectSize, prog)); V_DrawFixedFill((fx << FRACBITS) + rectLeft, (fy << FRACBITS) + rectTopFour + (rectSize - length), rectSize, length, 2|fflags); } else { V_DrawFixedFill((fx << FRACBITS) + rectLeft, (fy << FRACBITS) + rectTop + (rectSize - length), rectSize, length, 2|fflags); } } } // see also MT_PLAYERARROW mobjthinker in p_mobj.c 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 boolean tiny = r_splitscreen > 1; patch_t *localpatch = kp_nodraw; patch_t *localbg; boolean dark = false; INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... fixed_t itembar = -1, flamebar = -1; UINT16 localcolor = SKINCOLOR_NONE; SINT8 colormode = TC_RAINBOW; UINT8 *colmap = NULL; UINT8 *colormap = NULL; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff boolean isalt = false; // RadioRacers boolean shouldDrawOnPlayer = false; if (stplyr->itemroulette) { const INT32 item = K_GetRollingRouletteItem(stplyr); if (K_GetHudColor()) localcolor = K_GetHudColor(); localpatch = K_GetCachedItemPatch(item, tiny, 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 if (stplyr->stolentimer > 0) { if (leveltime & 2) localpatch = K_GetCachedItemPatch(KITEM_HYUDORO, tiny, 0); else localpatch = kp_nodraw; } else if ((stplyr->stealingtimer > 0) && (leveltime & 2)) { localpatch = K_GetCachedItemPatch(KITEM_HYUDORO, tiny, 0); } else if (stplyr->eggmanexplode > 1) { if (leveltime & 1) localpatch = K_GetCachedItemPatch(KITEM_EGGMAN, tiny, 0); else localpatch = kp_nodraw; } else if (stplyr->rocketsneakertimer > 1) { itembar = FixedDiv(stplyr->rocketsneakertimer, itemtime*3); if (leveltime & 1) localpatch = K_GetCachedItemPatch(KITEM_ROCKETSNEAKER, tiny, 0); else localpatch = kp_nodraw; } else if (stplyr->flametimer > 1) { itembar = FixedDiv(stplyr->flametimer, itemtime*3); flamebar = FixedDiv(stplyr->flamestore, FLAMESTOREMAX); dark = true; if ((stplyr->flamestore >= FLAMESTOREMAX-1) && (leveltime & 1)) { colormode = TC_BLINK; localcolor = SKINCOLOR_WHITE; } if (leveltime & 1) localpatch = K_GetCachedItemPatch(KITEM_FLAMESHIELD, tiny, 0); else localpatch = kp_nodraw; } else if (stplyr->growshrinktimer > 0) { if (stplyr->growcancel > 0) itembar = FixedDiv(stplyr->growcancel, 26); if (leveltime & 1) localpatch = K_GetCachedItemPatch(KITEM_GROW, tiny, 0); else localpatch = kp_nodraw; } else if ((stplyr->invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY))) { itembar = FixedDiv(stplyr->invincibilitytimer, max(1, stplyr->maxinvincibilitytime)); if (stplyr->invincibilitycancel > 0) flamebar = FixedDiv(stplyr->invincibilitycancel, 26); if (leveltime & 1) { localpatch = K_GetCachedItemPatch(KITEM_INVINCIBILITY, tiny, 0); isalt = true; } else localpatch = kp_nodraw; } else if (K_GetShieldFromPlayer(stplyr) == KSHIELD_BUBBLE) { localpatch = K_GetCachedItemPatch(KITEM_BUBBLESHIELD, tiny, 0); dark = true; if (stplyr->bubbleblowup > 0 && leveltime & 1) { colormode = TC_BLINK; localcolor = SKINCOLOR_WHITE; } itembar = FixedDiv(stplyr->bubblehealth, MAXBUBBLEHEALTH); } else if (stplyr->bricktimer > 0) { if (leveltime & 2) localpatch = K_GetCachedItemPatch(KITEM_EGGBRICK, tiny, 0); else localpatch = kp_nodraw; } else if (stplyr->sadtimer > 0) { if (leveltime & 2) localpatch = K_GetCachedItemPatch(MAXKARTITEMS, tiny, 0); else localpatch = kp_nodraw; } else { if (stplyr->itemamount <= 0 && stplyr->itemusecooldown <= 0) return; if (stplyr->itemtype == 0 && stplyr->itemusecooldown <= 0) return; if (stplyr->itemtype >= numkartitems && stplyr->itemtype != MAXKARTITEMS) return; if (stplyr->itemtype > 0 && stplyr->itemamount > 0) localpatch = K_GetCachedItemPatch(stplyr->itemtype, tiny, stplyr->itemamount); if (localpatch == NULL) localpatch = kp_nodraw; // diagnose underflows else if (K_IsKartItemAlternate(stplyr->itemtype) && K_ShowAltItemIcon(stplyr->itemtype, tiny)) isalt = true; dark = K_GetItemFlags(stplyr->itemtype) & KIF_DARKBG; if ((stplyr->itemflags & IF_ITEMOUT) && !(leveltime & 1)) localpatch = kp_nodraw; } if (stplyr->itemblink && (leveltime & 1)) { colormode = TC_BLINK; switch (stplyr->itemblinkmode) { case KITEMBLINK_DEBUG: case KITEMBLINK_KARMA: localcolor = K_RainbowColor(leveltime); break; case KITEMBLINK_MASHED: localcolor = SKINCOLOR_RED; break; default: localcolor = SKINCOLOR_WHITE; break; } } } localbg = K_getItemBoxPatch(tiny, dark); drawinfo_t info; rouletteinfo_t rinfo; info.hudScale = FRACUNIT; info.hudScaleFloat = 1.0f; rinfo.crop.x = 8; rinfo.crop.y = 8; rinfo.offset = 0; rinfo.flags = 0; rinfo.offset = 0; rinfo.spacing = ROULETTE_SPACING_FIXED; rinfo.intSpacing = ROULETTE_SPACING; K_getItemBoxDrawinfo(&info, &rinfo); shouldDrawOnPlayer = ((rinfo.flags & RINFO_DRAWONPLAYER) == RINFO_DRAWONPLAYER); if ((shouldDrawOnPlayer) && (stplyr->flametimer > 1)) { // Flamometer exists. You REALLY do not need this. itembar = -1; flamebar = -1; } INT32 hudtrans = V_GetHudTrans(); INT32 transflag = V_HUDTRANS; INT32 transmul = FRACUNIT - (hudtrans * FRACUNIT / 10); if (hudtrans > 9) { // Vamoose return; } // Let the player pick and choose their visibility level. INT32 roulettetrans = cv_rouletteplayertrans.value; // RadioRacers if (shouldDrawOnPlayer) { boolean rocketinvinbar = ((stplyr->rocketsneakertimer > 1) || ((stplyr->invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY)))); /** * RadioRacers * * Flame Shield and Eggman Fakes get unique exceptions. * This is because there are other visual elements that are more important than just the item icon. * * For Eggman Fakes, it's the impending timer. * For Flame shields, it's the flame meter (more on that below). * For rocket sneakers, it's the duration bar. */ // For Blan, Alt. Invincibility also needs duration visibility, so we dim the icon there, too. // Nice-to-have: some way for custom items to have this feature? Not sure yet. if (stplyr->itemtype == KITEM_FLAMESHIELD || stplyr->eggmanexplode > 1 || (rocketinvinbar)) { roulettetrans = (INT32)((float)(roulettetrans) * 0.57143f); } if (stplyr->exiting) roulettetrans = -1; if (roulettetrans > -1) { transflag = max(0, min(9, 10 - FixedMul(roulettetrans, transmul))) << V_ALPHASHIFT; } else { transflag = V_HUDTRANS; } } fx = info.x; fy = info.y; fflags = info.flags; flipamount = info.flipamount; INT32 localpatchflags = (transflag|fflags); if (localcolor != SKINCOLOR_NONE) { colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); } else if (K_GetItemFlags(stplyr->itemtype) & KIF_COLPATCH2PLAYER) { colmap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); } if (K_UseColorHud()) colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); V_DrawFixedPatch(fx << FRACBITS, fy << FRACBITS, info.hudScale, V_HUDTRANS|fflags, localbg, colormap); // Then, the numbers: if (stplyr->itemamount >= K_GetItemNumberDisplayMin(stplyr->itemtype, tiny) && !stplyr->itemroulette) { localbg = K_getItemMulPatch(tiny); V_DrawFixedPatch((fx + (flipamount ? 48 : 0))<itemusecooldown, stplyr->itemusecooldownmax); if (isalt) V_DrawFixedPatch(fx<itemamount)); else V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->itemamount)); } else { INT32 itemAmountFlags = V_HUDTRANS; if (shouldDrawOnPlayer) itemAmountFlags = (stplyr->exiting) ? V_HUDTRANS : V_30TRANS; V_DrawSciencePatch((fx << FRACBITS) + (28 * info.hudScale), (fy << FRACBITS) + (41 * info.hudScale), itemAmountFlags|fflags, kp_itemx, info.hudScale); V_DrawScalingKartStringAtFixed((fx << FRACBITS) + (38 * info.hudScale), (fy << FRACBITS) + (36 * info.hudScale), info.hudScale, itemAmountFlags|fflags, va("%d", stplyr->itemamount)); } } else if (cv_fancyroulette.value && stplyr->itemroulette)// xitem-next styled animated roulette { fixed_t rfy = fy<playerstate == PST_DEAD) ? 0 : R_GetTimeFrac(RTF_LEVEL); INT32 baseitem; if (tiny) { V_SetClipRect((fx + 15) << FRACBITS, (fy + 14) << FRACBITS, 18 << FRACBITS, 18 << FRACBITS, V_HUDTRANS|fflags); } else { V_SetClipRect((fx + rinfo.crop.x) << FRACBITS, (fy + rinfo.crop.y) << FRACBITS, rinfo.spacing, rinfo.spacing, V_HUDTRANS|fflags); } for (int rouletteshift = -1; rouletteshift <= 2; rouletteshift++) { shiftprog = (FRACUNIT * -rouletteshift) + (FixedDiv((stplyr->itemroulette<> FRACBITS)+(10-cvarhudtrans); rfy = (fy< alpha) alpha = 0; if (alpha >= 10) alpha = 10; fancyflags = (alpha<itemusecooldown, stplyr->itemusecooldownmax, info.hudScale); } else { V_DrawFixedPatch(fx<itemusecooldown, stplyr->itemusecooldownmax, info.hudScale); if (isalt) V_DrawFixedPatch(fx<eggmanexplode > 1) { V_DrawSciencePatch((fx<eggmanexplode))], info.hudScale); } } void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode) { // TIME_X = BASEVIDWIDTH-124; // 196 // TIME_Y = 6; // 6 tic_t worktime; fixed_t jitter = 0; boolean overtime = false, fastjitter = false, countdown = false; UINT8 *textcolor = NULL; INT32 splitflags = 0; if (!mode) { splitflags = V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; if (timelimitintics > 0) { if (drawtime >= timelimitintics) { overtime = true; jitter = 1; } else { drawtime = timelimitintics - drawtime; countdown = true; if (secretextratime) ; else if (extratimeintics) { jitter = 2; fastjitter = true; } else if (drawtime <= 5*TICRATE) { jitter = ((drawtime <= 3*TICRATE) && (((drawtime-1) % TICRATE) >= TICRATE-2)) ? 3 : 1; } } } } if (!K_UseColorHud()) V_DrawScaledPatch(TX, TY, splitflags, ((mode == 2) ? kp_lapstickerwide[0] : kp_timestickerwide[0])); else //Colourized hud { UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); V_DrawMappedPatch(TX, TY, splitflags, ((mode == 2) ? kp_lapstickerwide[1] : kp_timestickerwide[1]), colormap); } TX += 33; worktime = drawtime/(60*TICRATE); if (drawtime != UINT32_MAX && worktime >= 100) { jitter = 1; fastjitter = true; worktime = 99; drawtime = (100*(60*TICRATE))-1; } if (fastjitter) { jitter *= R_GetTimeFrac(RTF_LEVEL) * (leveltime & 1 ? 1 : -1); } else { if (drawtime & 2) jitter = -jitter; jitter *= !!(drawtime & 1) != countdown ? FRACUNIT - R_GetTimeFrac(RTF_LEVEL) : R_GetTimeFrac(RTF_LEVEL); } if (mode && drawtime == UINT32_MAX) V_DrawKartString(TX, TY+3, splitflags, va("--'--\"--")); else if (overtime) { if (((drawtime*2)/TICRATE) % 2 == 0) { textcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); } else { textcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_ORANGE, GTC_CACHE); } const char *overtimestr = "OVERTIME"; for (UINT8 i = 0; i < strlen(overtimestr); i++) { V_DrawStringScaledEx( (TIME_X + 33 + 12*i + (i == 6 ? -4 : i == 7 ? -1 : 0)) << FRACBITS, ((TIME_Y + 3) << FRACBITS) + (i & 1 ? jitter : -jitter), FRACUNIT, FRACUNIT, FRACUNIT, FRACUNIT, splitflags, textcolor, KART_FONT, va("%c", overtimestr[i]) ); } } else { // minutes time 00 __ __ V_DrawKartStringAtFixed(TX<type) { case ET_TIME: { static boolean canplaysound = true; tic_t timetoreach = emblem->var; if (emblem->collected) { emblempic[curemb] = W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE); emblemcol[curemb] = R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE); if (++curemb == 3) break; goto bademblem; } snprintf(targettext, 9, "%i'%02i\"%02i", G_TicsToMinutes(timetoreach, false), G_TicsToSeconds(timetoreach), G_TicsToCentiseconds(timetoreach)); if (!mode) { if (stplyr->realtime > timetoreach) { splitflags = (splitflags &~ V_HUDTRANS)|V_HUDTRANSHALF; if (canplaysound) { S_StartSound(NULL, sfx_s3k72); //sfx_s26d); -- you STOLE fizzy lifting drinks canplaysound = false; } } else if (!canplaysound) canplaysound = true; } targettext[8] = 0; } break; default: goto bademblem; } V_DrawRightAlignedString(workx, worky, splitflags|V_6WIDTHSPACE, targettext); workx -= 67; V_DrawSmallScaledPatch(workx + 4, worky, splitflags, W_CachePatchName("NEEDIT", PU_CACHE)); break; bademblem: emblem = M_GetLevelEmblems(-1); } if (!mode) splitflags = (splitflags &~ V_HUDTRANSHALF)|V_HUDTRANS; while (curemb--) { workx -= 12; V_DrawSmallMappedPatch(workx + 4, worky, splitflags, emblempic[curemb], emblemcol[curemb]); } } } #define POS_DELAY_TIME 10 static void K_DrawKartPositionNum(INT32 num) { // POSI_X = BASEVIDWIDTH - 51; // 269 // POSI_Y = BASEVIDHEIGHT- 64; // 136 boolean win = (stplyr->exiting && num == 1); //INT32 X = POSI_X; INT32 W = kp_positionnum[0][0]->width; fixed_t scale = FRACUNIT; patch_t *localpatch = kp_positionnum[0][0]; INT32 fx = 0, fy = 0, fflags = 0; INT32 xoffs = (cv_showinput.value > 0) ? -48 : 0; INT32 addOrSub = V_ADD; boolean flipdraw = false; // flip the order we draw it in for MORE splitscreen bs. fun. boolean flipvdraw = false; // used only for 2p splitscreen so overtaking doesn't make 1P's position fly off the screen. boolean overtake = false; if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM) { addOrSub = V_SUBTRACT; } if (stplyr->positiondelay || stplyr->exiting) { const UINT8 delay = (stplyr->exiting) ? POS_DELAY_TIME : stplyr->positiondelay; const fixed_t add = (scale * 3) >> ((r_splitscreen == 1) ? 1 : 2); scale = cv_smoothposition.value ? scale + min((add * (delay * delay)) / (POS_DELAY_TIME * POS_DELAY_TIME), add) : scale*2; overtake = true; // this is used for splitscreen stuff in conjunction with flipdraw. } if (r_splitscreen || ((cv_showinput.value > 0) && !r_splitscreen)) scale /= 2; W = FixedMul(W<>FRACBITS; // pain and suffering defined below if (!r_splitscreen) { fx = POSI_X + xoffs; fy = BASEVIDHEIGHT - 8; fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN; } else if (r_splitscreen == 1) // for this splitscreen, we'll use case by case because it's a bit different. { fx = POSI_X; if (stplyrnum == 0) // for player 1: display this at the top right, above the minimap. { fy = 30; fflags = V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; if (overtake) flipvdraw = true; // make sure overtaking doesn't explode us } else // if we're not p1, that means we're p2. display this at the bottom right, below the minimap. { fy = (BASEVIDHEIGHT/2) - 8; fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN; } } else { if (stplyrnum == 0 || stplyrnum == 2) // If we are P1 or P3... { fx = POSI_X; fy = POSI_Y; fflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; flipdraw = true; if (num && num >= 10) fx += W; // this seems dumb, but we need to do this in order for positions above 10 going off screen. } else // else, that means we're P2 or P4. { fx = POSI2_X; fy = POSI2_Y; fflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } } // Special case for 0 if (num <= 0) { V_DrawFixedPatch(fx<laps >= numlaps || stplyr->exiting) // Check for the final lap, or won { boolean useRedNums = K_IsPlayerLosing(stplyr); if (addOrSub == V_SUBTRACT) { // Subtracting RED will look BLUE, and vice versa. useRedNums = !useRedNums; } // Alternate frame every three frames switch ((leveltime % 9) / 3) { case 0: if (useRedNums == true) localpatch = kp_positionnum[num % 10][4]; else localpatch = kp_positionnum[num % 10][1]; break; case 1: if (useRedNums == true) localpatch = kp_positionnum[num % 10][5]; else localpatch = kp_positionnum[num % 10][2]; break; case 2: if (useRedNums == true) localpatch = kp_positionnum[num % 10][6]; else localpatch = kp_positionnum[num % 10][3]; break; default: localpatch = kp_positionnum[num % 10][0]; break; } } else { localpatch = kp_positionnum[num % 10][0]; } V_DrawFixedPatch( (fx<width)*scale/2 : 0), (fy<height)*scale/2 : 0), scale, addOrSub|V_HUDTRANSHALF|fflags, localpatch, NULL ); // ^ if we overtake as p1 or p3 in splitscren, we shift it so that it doesn't go off screen. // ^ if we overtake as p1 in 2p splits, shift vertically so that this doesn't happen either. fx -= W; num /= 10; } } static UINT32 K_InvincibilityHUDVisibility(UINT16 t) { UINT32 alphalevel = st_translucency; alphalevel = min(10, FixedMul(alphalevel, K_InvincibilityGradient(t))); return min(9, 10 - alphalevel)<= 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++; } if (ranklines < 5) Y += (9*ranklines); else Y += (9*5); ranklines--; i = ranklines; if (gametype == GT_BATTLE || strank <= 2) // too close to the top, or playing battle, 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; if (players[rankplayer[i]].mo->color) { colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); if (players[rankplayer[i]].mo->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, players[rankplayer[i]].mo->color, GTC_CACHE); else colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); patch_t *facerank = faceprefix[players[rankplayer[i]].skin][FACE_RANK]; offsets.x = facerank->leftoffset; offsets.y = facerank->topoffset; #ifdef ROTSPRITE if ((K_RankIconCanSpinout()) && (players[rankplayer[i]].spinoutrot)) { // Rotate counterclockwise. rollangle = FixedAngle(players[rankplayer[i]].spinoutrot * -1); rot = R_GetRollAngle(rollangle); if (rot) { facerank = Patch_GetRotated(facerank, rot, false); } } #endif V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, V_HUDTRANS|V_SNAPTOLEFT, facerank, colormap); if ((players[rankplayer[i]].invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY))) { colormap = R_GetTranslationColormap(TC_BLINK, K_AltInvincibilityColor(leveltime / 2), GTC_CACHE); invinchudtrans = K_InvincibilityHUDVisibility(players[rankplayer[i]].invincibilitytimer); V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, invinchudtrans|V_SNAPTOLEFT|V_ADD, facerank, colormap); } if (LUA_HudEnabled(hud_battlebumpers)) { if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumper > 0) { V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[0], colormap); for (j = 1; j < players[rankplayer[i]].bumper; j++) { bumperx += 5; V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[1], colormap); } } } } if (i == strank) { patch_t *highlight = kp_facehighlight[(leveltime / 4) % 8]; INT32 left, top; left = highlight->leftoffset; top = highlight->topoffset; #ifdef ROTSPRITE if ((K_RankIconCanSpinout()) && (players[rankplayer[i]].spinoutrot)) { // Rotate counterclockwise. rollangle = FixedAngle(players[rankplayer[i]].spinoutrot * -1); rot = R_GetRollAngle(rollangle); if (rot) { highlight = Patch_GetRotated(highlight, rot, false); } } #endif V_DrawScaledPatch(FACE_X+left, Y+top, V_HUDTRANS|V_SNAPTOLEFT, highlight); } if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumper <= 0) V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SNAPTOLEFT, kp_ranknobumpers); else { INT32 pos = players[rankplayer[i]].position; // Draws the little number over the face if (pos < 0 || pos > 16) V_DrawPingNum(FACE_X+2, Y+10, V_HUDTRANS|V_SNAPTOLEFT, pos, NULL); else V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SNAPTOLEFT, kp_facenum[pos]); } Y -= 18; } 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 = min((bossinfo.visualbarimpact/4) + 1, 8); 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_HUDTRANS|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_6WIDTHSPACE, 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 = M_RandomKey(randtemp)+1; randsign = M_RandomChance(FRACUNIT/2); // Right wing. V_DrawScaledPatch(startx-1, starty, V_HUDTRANS|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_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[1]); if (i < bossinfo.visualbar) { randlen--; if (!randlen) { randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); if (randtemp > 0) randlen = M_RandomKey(randtemp)+1; if (barstatus > 1) { rolrand = M_RandomKey(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_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[(2*barstatus)+darken]); } i++; } // Left wing. V_DrawScaledPatch(startx-i, starty, V_HUDTRANS|V_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[0]); } // // K_DrawNeoTabRankings -- A new way to view ingame info! // returns starting x of right offset for drawing your own info in there! // INT32 K_DrawNeoTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol, boolean split) { INT32 i, rightoffset = split ? 160 - 6: BASEVIDWIDTH - 6; const UINT8 *colormap = NULL; UINT16 hightlightcolor = 0; INT32 dupadjust = (vid.scaledwidth), duptweak = (dupadjust - BASEVIDWIDTH)/2; INT32 y2, x2; // INT32 basey = y, basex = x; (void)hilicol; scorelines--; x += 4; y += 9*scorelines; V_DrawFill(1-duptweak, 26, dupadjust-2, 1, 0); // Draw a horizontal line because it looks nice! if (split) { V_DrawFill(x + rightoffset + 6, 26, 1, 162, 0); // Draw a vertical line because it looks nice! } for (i = scorelines; i >= 0; i--) { INT32 pos = CLAMP(players[tab[i].num].position, 0 , MAXPLAYERS); char playername[MAXPLAYERNAME+1]; if (players[tab[i].num].spectator || !players[tab[i].num].mo) continue; //ignore them. if (netgame) // don't draw ping offline { if (players[tab[i].num].bot) { V_DrawThinString(x - 18 + 25, y, 0, "CPU"); } else if (tab[i].num != serverplayer || (server_lagless == 0 || server_lagless == -1)) { INT32 xoff = cv_pingmeasurement.value == 1 ? 33 : 26; HU_drawPing(x - 18 + xoff , y-10, playerpingtable[tab[i].num], playerdelaytable[tab[i].num], playerpacketlosstable[tab[i].num], 0, false); } else if (tab[i].num == serverplayer) { V_DrawThinString(x - 18 + 25, y, 0, "SRV"); } } STRBUFCPY(playername, tab[i].name); if (players[tab[i].num].mo->color) { colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); if (players[tab[i].num].mo->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, players[tab[i].num].mo->color, GTC_CACHE); else colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); hightlightcolor = skincolors[players[tab[i].num].mo->color].chatcolor; } if (pos < 0 || pos > 16) V_DrawPingNum(x+2, y+1, 0, pos, NULL); else V_DrawScaledPatch(x-5, y+1, 0, kp_facenum[pos]); x2 = netgame ? x + (BASEVIDWIDTH/20) : x; y2 = y; V_DrawThinString(x2 + 20, y2, ((tab[i].num == whiteplayer) ? hightlightcolor : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, playername); patch_t *facerank = faceprefix[players[tab[i].num].skin][FACE_RANK]; fixed_t scale = FRACUNIT/2; V_DrawFixedPatch( ((x2+11)*FRACUNIT) + ((facerank->leftoffset) * scale), ((y+1)*FRACUNIT) + ((facerank->topoffset) * scale), scale, 0, facerank, colormap ); /*if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumper > 0) -- not enough space for this { INT32 bumperx = x+19; V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap); for (j = 1; j < players[tab[i].num].bumper; j++) { bumperx += 5; V_DrawMappedPatch(bumperx, y-4, 0, kp_tinybumper[1], colormap); } }*/ patch_t *highlight = kp_facehighlight[(leveltime / 4) % 8]; if (tab[i].num == whiteplayer) V_DrawFixedPatch( ((x2+11)*FRACUNIT)+(highlight->leftoffset * scale), ((y+1)*FRACUNIT)+(highlight->topoffset * scale), scale, 0, highlight, NULL ); if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumper <= 0) V_DrawScaledPatch(x2-4, y-7, 0, kp_ranknobumpers); if (tab[i].string[0] != '\0') V_DrawRightAlignedThinString(x+rightoffset+2, y, V_6WIDTHSPACE, tab[i].string); y -= 9; } return x+rightoffset; } servermods_t customservermods[MAXSERVERMODS]; UINT8 numcustomservermods = 0; // Adds a new mod to the scoreboard display. void K_AddNewScoreboardMod(const char *name, const consvar_t *cvar, SINT8 active) { UINT32 hashcompare = HASH32(name, MAXSERVERMODNAME); UINT8 i; for (i = 0; i < MAXSERVERMODS; i++) { if (customservermods[i].hash == hashcompare) { CONS_Alert(CONS_WARNING, "Scoreboard mod '%s' has already been added to the scoreboard.\n", name); return; } } if (numcustomservermods+1 == MAXSERVERMODS) { CONS_Alert(CONS_ERROR, "Maximum Amount of scoreboard mods has been reached.\n"); return; } strncpy(customservermods[numcustomservermods].modname, name, MAXSERVERMODNAME); customservermods[numcustomservermods].hash = hashcompare; customservermods[numcustomservermods].cvar = cvar; customservermods[numcustomservermods].active = CLAMP(active, SCOREBOARDMOD_NOTUSED, SCOREBOARDMOD_ACTIVE); customservermods[numcustomservermods].valid = true; numcustomservermods++; } // Change the status of static scoreboard displays. void K_SetScoreboardModStatus(const char *name, SINT8 active) { UINT32 hashcompare = HASH32(name, MAXSERVERMODNAME); UINT8 i; for (i = 0; i < MAXSERVERMODS; i++) { if (customservermods[i].hash == hashcompare) { customservermods[i].active = CLAMP(active, SCOREBOARDMOD_NOTUSED, SCOREBOARDMOD_ACTIVE); return; } } CONS_Alert(CONS_WARNING, "Server mod '%s' does not exist so status cannot be changed.\n", name); } #define BASEMODS 19 static void K_DrawServerMods(INT32 x, INT32 y) { UINT8 i, j; INT32 xoff = 0, yoff = 10; UINT8 numdrawn = 0; servermods_t basemods[BASEMODS] = { {"Restat", 0, &cv_allowrestat, -1, true}, {"Item Litter", 0, NULL, K_ItemLitterActive() > 0, true}, {"Rings", 0, NULL, K_RingsActive() > 0, true}, {"4-Tier Drift", 0, NULL, K_PurpleDriftActive() > 0, true}, {"Slipdash", 0, NULL, K_SlipdashActive() > 0, true}, {"Stacking", 0, NULL, K_StackingActive() > 0, true}, {"Chaining", 0, NULL, K_ChainingActive() > 0, true}, {"Chain Offroad", 0, &cv_kartchainingoffroad, -1, true}, {"Slope Boost", 0, NULL, K_PurpleDriftActive() > 0, true}, {"Drafting", 0, NULL, K_DraftingActive() > 0, true}, {"Light AirDrop", 0, NULL, K_AirDropActive() == AIRDROP_LIGHT, true}, {"Heavy AirDrop", 0, NULL, K_AirDropActive() == AIRDROP_HEAVY, true}, {"Fus. AirDrop", 0, NULL, K_AirDropActive() == AIRDROP_FUSION, true}, {"Air Thrust", 0, NULL, K_AirThrustActive() > 0, true}, {"Recovery Dash", 0, NULL, K_RecoveryDashActive() > 0, true}, {"Bump Spark", 0, &cv_kartbumpspark, -1, true}, {"Bump Drift", 0, NULL, K_GetBumpSpark() > 0, true}, {"Bump Spark", 0, NULL, K_GetBumpSpark() > BUMPSPARK_NOCHARGE, true}, {"Bump Spring", 0, &cv_kartbumpspring, -1, true} //TODO: separate drawer that enumerates item changes? // {"Alt. Invin.", 0, NULL, K_IsKartItemAlternate(KITEM_INVINCIBILITY), true} }; for (j = 0; j < 2; j++) { UINT8 modcount = j == 0 ? BASEMODS : numcustomservermods; servermods_t *modslist = j == 0 ? basemods : customservermods; // Draw the the modlist. for (i = 0; i < modcount; i++) { boolean drawdis = false; if (modslist[i].valid) { if (modslist[i].cvar && modslist[i].cvar->value) { drawdis = true; } else if (modslist[i].active == SCOREBOARDMOD_ACTIVE) { drawdis = true; } if (drawdis && modslist[i].modname[0] != '\0') { V_DrawSmallString(x+xoff, y+yoff, V_6WIDTHSPACE|V_ALLOWLOWERCASE, modslist[i].modname); numdrawn++; if ((numdrawn % 2) == 0) { xoff -= 50; yoff += 5; } else if ((numdrawn % 1) == 0) { xoff += 50; } } } } } if (numdrawn > 0) V_DrawThinString(x, y, V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_GRAYMAP, "Gameplay / Balance Changes:"); } #undef BASEMODS void K_DrawServerDescrption(INT32 x, INT32 y) { INT32 i, newlinecount = 0; if (connectedservername[0] != '\0') V_DrawThinString(x, y, V_6WIDTHSPACE|V_ALLOWLOWERCASE, connectedservername); V_DrawSmallString(x, y+10, V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_GRAYMAP, va("Contact: %s", (connectedservercontact[0] != '\0') ? connectedservercontact : "")); if (connectedserverdescription[0] != '\0') { V_DrawSmallString(x, y+20, V_6WIDTHSPACE|V_ALLOWLOWERCASE, connectedserverdescription); for (i = 0; connectedserverdescription[i]; i++) newlinecount += (connectedserverdescription[i] == '\n'); } K_DrawServerMods(x, y + 25 + newlinecount*6); } // The old school one.... void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol) { INT32 i, rightoffset = 240; UINT8 *colormap = NULL; UINT16 hightlightcolor = 0; INT32 dupadjust = (vid.scaledwidth), duptweak = (dupadjust - BASEVIDWIDTH)/2; int basey = y, basex = x, y2; (void)hilicol; V_DrawFill(1-duptweak, 26, dupadjust-2, 1, 0); // Draw a horizontal line because it looks nice! scorelines--; if (scorelines >= 8) { V_DrawFill(160, 26, 1, 147, 0); // Draw a vertical line to separate the two sides. V_DrawFill(1-duptweak, 173, dupadjust-2, 1, 0); // And a horizontal line near the bottom. rightoffset = (BASEVIDWIDTH/2) - 4 - x; x = (BASEVIDWIDTH/2) + 4; y += 18*(scorelines-8); } else { y += 18*scorelines; } for (i = scorelines; i >= 0; i--) { char playername[MAXPLAYERNAME+1]; if (players[tab[i].num].spectator || !players[tab[i].num].mo) continue; //ignore them. if (netgame) // don't draw ping offline { if (players[tab[i].num].bot) { V_DrawString(x + ((i < 8) ? -25 : rightoffset + 3), y-2, V_SNAPTOLEFT, "CPU"); } else if (tab[i].num != serverplayer || (server_lagless == 0 || server_lagless == -1)) { HU_drawPing(x + ((i < 8) ? -17 : rightoffset + 11), y-4, playerpingtable[tab[i].num], playerdelaytable[tab[i].num], playerpacketlosstable[tab[i].num], 0, true); } else if (tab[i].num == serverplayer) { V_DrawString(x + ((i < 8) ? -25 : rightoffset + 3), y-2, V_SNAPTOLEFT, "SRV"); } } STRBUFCPY(playername, tab[i].name); y2 = y; if (players[tab[i].num].mo->color) { colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); if (players[tab[i].num].mo->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, players[tab[i].num].mo->color, GTC_CACHE); else colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); hightlightcolor = skincolors[players[tab[i].num].mo->color].chatcolor; } if (scorelines >= 8) V_DrawThinString(x + 20, y2, ((tab[i].num == whiteplayer) ? hightlightcolor : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, playername); else V_DrawString(x + 20, y2, ((tab[i].num == whiteplayer) ? hightlightcolor : 0)|V_ALLOWLOWERCASE, playername); patch_t *facerank = faceprefix[players[tab[i].num].skin][FACE_RANK]; V_DrawMappedPatch(x+facerank->leftoffset, y-4+facerank->topoffset, 0, facerank, colormap); /*if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumper > 0) -- not enough space for this { INT32 bumperx = x+19; V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap); for (j = 1; j < players[tab[i].num].bumper; j++) { bumperx += 5; V_DrawMappedPatch(bumperx, y-4, 0, kp_tinybumper[1], colormap); } }*/ if (tab[i].num == whiteplayer) { patch_t *highlight = kp_facehighlight[(leveltime / 4) % 8]; V_DrawScaledPatch(x+highlight->leftoffset, y-4+highlight->topoffset, 0, highlight); } if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumper <= 0) V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers); else { INT32 pos = players[tab[i].num].position; if (pos < 0 || pos > MAXPLAYERS) pos = 0; // Draws the little number over the face V_DrawScaledPatch(x-5, y+6, 0, kp_facenum[pos]); } if (tab[i].string[0] != '\0') V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, tab[i].string); y -= 18; if (i == 8) { y = basey + 7*18; x = basex; } } } static void K_drawKartLaps(void) { INT32 fx = 0, fy = 0, splitflags = 0; // stuff for 3p / 4p splitscreen. drawinfo_t info; K_getLapsDrawinfo(&info); fx = info.x; fy = info.y; splitflags = info.flags; if (r_splitscreen > 1) { // Laps V_DrawScaledPatch(fx, fy, V_HUDTRANS|splitflags, kp_splitlapflag); V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|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|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); ln[0] = ((numlaps / 10) % 10); ln[1] = (numlaps % 10); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); } else { V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->laps) % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[(numlaps) % 10]); } } else { if (!K_UseColorHud()) { if (K_BigLapSticker()) V_DrawScaledPatch(fx, fy, V_HUDTRANS|splitflags, ((stplyr->laps > 9) ? kp_lapstickerbig2[0] : kp_lapstickerbig[0])); else V_DrawScaledPatch(fx, fy, V_HUDTRANS|splitflags, kp_lapsticker[0]); } else { UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); if (K_BigLapSticker()) V_DrawMappedPatch(fx, fy, V_HUDTRANS|splitflags, ((stplyr->laps > 9) ? kp_lapstickerbig2[1] : kp_lapstickerbig[1]), colormap); else V_DrawMappedPatch(fx, fy, V_HUDTRANS|splitflags, kp_lapsticker[1], colormap); } if (stplyr->exiting) V_DrawKartString(fx+33, fy+3, V_HUDTRANS|V_SLIDEIN|splitflags, "FIN"); else V_DrawKartString(fx+33, fy+3, V_HUDTRANS|splitflags, va("%d/%d", min(stplyr->laps, numlaps), numlaps)); } } void K_getLivesnStatsDrawinfo(drawinfo_t *out) { INT32 fx, fy, splitflags = 0; // pain and suffering defined below if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = LIVE_X; fy = LIVE_Y; splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANS|V_SPLITSCREEN; } else { if (stplyrnum == 0 || stplyrnum == 2) // If we are P1 or P3... { fx = LIVE_X; fy = LIVE_Y; splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { fx = LIVE2_X; fy = LIVE2_Y; splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; } } out->x = fx; out->y = fy; out->flags = splitflags; } static void K_drawKartStatsnLives(void) { const boolean uselives = G_GametypeUsesLives(); const boolean stats = cv_showstats.value; INT32 fx = 0, fy = 0, splitflags = 0; // stuff for 3p / 4p splitscreen. drawinfo_t info; K_getLivesnStatsDrawinfo(&info); fx = info.x; fy = info.y; splitflags = info.flags; if (r_splitscreen > 1) { // Lives if (LUA_HudEnabled(hud_lives) && uselives) { // We specify stplyr->skincolor since we want it to match the player and not the hud color. UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); patch_t *mmappatch = faceprefix[stplyr->skin][FACE_MINIMAP]; V_DrawMappedPatch(fx+21+mmappatch->leftoffset, fy-13+mmappatch->topoffset, V_HUDTRANS|splitflags, mmappatch, colormap); if (stplyr->lives >= 0 && uselives) V_DrawScaledPatch(fx+34, fy-10, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow } } else { // Stats and Lives if (LUA_HudEnabled(hud_lives) && (uselives || stats)) { INT32 offsetx = 0; INT32 offsety = 0; boolean split = r_splitscreen == 1; if ((cv_lives_xoffset.value == 0 && cv_lives_yoffset.value == 0) || split) { if ((cv_newspeedometer.value == 0 || cv_newspeedometer.value == 2) && !K_RingsActive() && !split) { offsetx = 25; offsety = 15; } else if (K_RingsActive()) { offsetx = 4; } } // We specify stplyr->skincolor since we want it to match the player and not the hud color. UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); patch_t *facerank = faceprefix[stplyr->skin][FACE_RANK]; V_DrawMappedPatch(fx+59+offsetx+facerank->leftoffset, fy-16+offsety+facerank->topoffset, V_HUDTRANS|splitflags, facerank, colormap); if (stplyr->lives >= 0 && uselives) V_DrawScaledPatch(fx+77+offsetx, fy-11+offsety, V_HUDTRANS|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow if (stats) { // Draw stats UINT8 *colormapstat = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLUE, GTC_CACHE); UINT8 *colormapstat2 = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_ORANGE, GTC_CACHE); if (stplyr->kartspeed != skins[stplyr->skin].kartspeed || stplyr->kartweight != skins[stplyr->skin].kartweight) { colormapstat = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); colormapstat2 = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_MUSTARD, GTC_CACHE); } V_DrawFixedPatch((fx+56+offsetx+facerank->leftoffset)<< FRACBITS, (fy-19+offsety+facerank->topoffset)<< FRACBITS, FRACUNIT, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartspeed % 10)], colormapstat); V_DrawFixedPatch((fx+69+offsetx+facerank->leftoffset)<< FRACBITS, (fy-4+offsety+facerank->topoffset)<< FRACBITS, FRACUNIT, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartweight % 10)], colormapstat2); } } } } static void K_drawKartAccessibilityIcons(INT32 fx) { INT32 fy = ACCE_Y-25; INT32 splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|V_SPLITSCREEN; //INT32 step = 1; -- if there's ever more than one accessibility icon fx += ACCE_X; if (r_splitscreen < 2) // adjust to speedometer height { if (cv_acce_xoffset.value == 0 && cv_acce_yoffset.value == 0) { if (cv_newspeedometer.value == 0 || (cv_newspeedometer.value == 2 && !K_RingsActive())) { fy += 44; } else { if ((cv_newspeedometer.value == 0 || cv_newspeedometer.value == 2) && !K_RingsActive()) fy += 18; if ((gametyperules & GTR_BUMPERS) && !(gametyperules & GTR_CIRCUIT)) fy -= 4; } } } else { fx = ACCE_X+43; fy = ACCE_Y; if (!(stplyrnum == 0 || stplyrnum == 2)) // If we are not P1 or P3... { splitflags ^= (V_SNAPTOLEFT|V_SNAPTORIGHT); fx = (BASEVIDWIDTH/2) - (fx + 10); //step = -step; } } if (stplyr->pflags & PF_KICKSTARTACCEL) // just KICKSTARTACCEL right now, maybe more later { 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|splitflags); V_DrawFill(fx, (fy+ofs-1)+8, 10, 1, 31|splitflags); while (i--) { wid = (i/2)+1; V_DrawFill(fx+4-wid, fy+ofs+i, 2+(wid*2), 1, 31|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|splitflags); } //fx += step*12; } } static void K_drawKartSpeedometer(void) { static fixed_t convSpeed; UINT8 labeln = 0; UINT8 numbers[3]; INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; INT32 battleoffset = 0; INT32 ringoffset = 0; INT32 oldringoffset = 0; switch (cv_kartspeedometer.value) { case 1: // Kilometers convSpeed = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale) / FRACUNIT; // 2.172409058 labeln = 0; break; case 2: // Miles convSpeed = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale) / FRACUNIT; // 1.349868774 labeln = 1; break; case 3: // Fracunits convSpeed = FixedDiv(stplyr->speed, mapobjectscale) / FRACUNIT; // 1.0. duh. labeln = 2; break; case 4: // Sonic Drift 2 style percentage if (stplyr->mo) convSpeed = (FixedDiv(stplyr->speed, FixedMul(K_GetKartSpeed(stplyr, false, false), K_PlayerBaseFriction(stplyr, ORIG_FRICTION)))*100)>>FRACBITS; labeln = 3; break; default: break; } // Don't overflow // (negative speed IS really high speed :V) if (convSpeed > 999 || convSpeed < 0) convSpeed = 999; if (cv_speed_xoffset.value == 0 && cv_speed_yoffset.value == 0) { if ((gametyperules & GTR_BUMPERS) && !(gametyperules & GTR_CIRCUIT)) battleoffset = -4; if (K_RingsActive() == true) { ringoffset = -16; oldringoffset = 6; } } if (cv_newspeedometer.value == 0) { switch (cv_kartspeedometer.value) { case 1: V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d km/h", convSpeed)); break; case 2: V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d mph", convSpeed)); break; case 3: V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d fu/t", convSpeed)); break; case 4: V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%4d %%", convSpeed)); break; default: break; } } else if (cv_newspeedometer.value == 1) { numbers[0] = ((convSpeed / 100) % 10); numbers[1] = ((convSpeed / 10) % 10); numbers[2] = (convSpeed % 10); if (!K_UseColorHud()) { V_DrawScaledPatch(SPDM_X, SPDM_Y-9 + battleoffset + ringoffset, V_HUDTRANS|splitflags, kp_speedometersticker[0]); } else { UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); V_DrawMappedPatch(SPDM_X, SPDM_Y-9 + battleoffset + ringoffset, V_HUDTRANS|splitflags, kp_speedometersticker[1], colormap); } V_DrawScaledPatch(SPDM_X+7, SPDM_Y-9 + battleoffset + ringoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[0]]); V_DrawScaledPatch(SPDM_X+13, SPDM_Y-9 + battleoffset + ringoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[1]]); V_DrawScaledPatch(SPDM_X+19, SPDM_Y-9 + battleoffset + ringoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[2]]); V_DrawScaledPatch(SPDM_X+29, SPDM_Y-9 + battleoffset + ringoffset, V_HUDTRANS|splitflags, kp_speedometerlabel[labeln]); } else if (cv_newspeedometer.value == 2) { fixed_t fuspeed = FixedDiv(stplyr->speed, mapobjectscale)/FRACUNIT; INT32 spdpatch = 0; SINT8 yoffset = K_RingsActive() ? 12 : 18; #define NUM_INTERVALS 22 const int speedIntervals[NUM_INTERVALS] = {2, 5, 7, 10, 12, 15, 17, 20, 22, 25, 27, 30, 32, 35, 37, 40, 42, 45, 47, 50, 52, 55}; for (int i = 0; i < NUM_INTERVALS; ++i) { if (fuspeed < speedIntervals[i]) { spdpatch = i; break; } } #undef NUM_INTERVALS if (((fuspeed < 57 && fuspeed > 54) || (fuspeed < 60 && fuspeed > 56) || (fuspeed > 59)) && (leveltime & 4)) spdpatch = 24; else if (((fuspeed < 57 && fuspeed > 54) || (fuspeed < 60 && fuspeed > 56) || (fuspeed > 59)) && !(leveltime & 4)) spdpatch = 23; V_DrawScaledPatch(SPDM_X, SPDM_Y-yoffset + battleoffset + ringoffset, V_HUDTRANS|splitflags, (!K_RingsActive() ? kp_kartzspeedo : kp_kartzspeedo_smol)[spdpatch]); } K_drawKartAccessibilityIcons((cv_newspeedometer.value == 0 || cv_newspeedometer.value == 2) ? 50 : 56); } static void K_drawRingMeter(void) { UINT8 rn[2]; UINT8 *ringmap = NULL; boolean colorring = false; SINT8 ringcount = stplyr->rings; // SINT8 overring = stplyr->rings % 20; SINT8 ringmax = stplyr->ringmax; INT32 fx = 0, fy = 0, splitflags = 0; // stuff for 3p / 4p splitscreen. rn[0] = ((abs(ringcount) / 10) % 10); rn[1] = (abs(ringcount) % 10); drawinfo_t info; K_getRingsDrawinfo(&info); fx = info.x; fy = info.y; splitflags = info.flags; if (ringcount <= 0 && (leveltime/5 & 1)) // In debt { ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); colorring = true; } else if (ringcount > 20) // (placeholder) over-ring ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLUE, GTC_CACHE); else if (ringcount >= ringmax) // Maxed out ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); if (r_splitscreen > 1) { V_DrawMappedPatch(fx, fy-10, V_HUDTRANS|splitflags, kp_ringsplitscreen, (colorring ? ringmap : NULL)); if (ringcount < 0) // Draw the minus for ring debt V_DrawMappedPatch(fx+7, fy-8, V_HUDTRANS|splitflags, kp_ringdebtminussmall, ringmap); V_DrawMappedPatch(fx+11, fy-10, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap); V_DrawMappedPatch(fx+15, fy-10, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap); } else { SINT8 i; SINT8 ringoffsety = 0; UINT8 *colormap = NULL; SINT8 coloroffset = 0; if (cv_speed_xoffset.value == 0 && cv_speed_yoffset.value == 0) { if ((gametyperules & GTR_BUMPERS) && !(gametyperules & GTR_CIRCUIT)) ringoffsety -= 4; if (itembreaker) ringoffsety -= 2; } if (K_UseColorHud()) coloroffset = 2; colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); V_DrawMappedPatch(fx, fy-14 + ringoffsety, V_HUDTRANS|splitflags, kp_ringsticker[((stplyr->pflags & PF_RINGLOCK) ? 1 : 0) + coloroffset], colormap); if (stplyr->rings < 0) // Draw the minus for ring debt { V_DrawMappedPatch(fx-5, fy-11 + ringoffsety, V_HUDTRANS|splitflags, kp_ringdebtminus, ringmap); } #if 0 if (stplyr->rings < 0) { // Invert the ring count ringcount = -ringcount; } #endif // clamp and invert when needed for the bar ringcount = CLAMP(abs(ringcount), -20, 20); if (rn[1] == 1 && ringcount == 11) V_DrawMappedPatch(fx+2, fy-11 + ringoffsety, V_HUDTRANS|splitflags, kp_facenum[rn[0]], ringmap); else V_DrawMappedPatch(fx+2, fy-11 + ringoffsety, V_HUDTRANS|splitflags, kp_facenum[rn[0]], ringmap); if (rn[1] == 1 && ringcount == 11) V_DrawMappedPatch(fx+7, fy-11 + ringoffsety, V_HUDTRANS|splitflags, kp_facenum[rn[1]], ringmap); else V_DrawMappedPatch(fx+8, fy-11 + ringoffsety, V_HUDTRANS|splitflags, kp_facenum[rn[1]], ringmap); // Draw the fillbars if (stplyr->rings) { UINT8 barcolors[5] = {66,72,2,68}; boolean indebt = false; if (stplyr->rings < 0) { barcolors[0] = 38; barcolors[1] = 36; barcolors[2] = 32; barcolors[3] = 40; indebt = true; } else if (stplyr->rings > 20) { barcolors[0] = 132; barcolors[1] = 131; barcolors[2] = 128; barcolors[3] = 154; } if (!indebt || (indebt && (leveltime/5 & 1))) { for (i = 0; i != ringcount; i++) { V_DrawFill(fx+17+(2*i), fy-10 + ringoffsety, 1, 1, barcolors[0]|splitflags); V_DrawFill(fx+17+(2*i), fy-9 + ringoffsety, 1, 4, barcolors[1]|splitflags); V_DrawFill(fx+17+(2*i), fy-8 + ringoffsety, 1, 1, barcolors[2]|splitflags); V_DrawFill(fx+17+(2*i), fy-7 + ringoffsety, 1, 1, barcolors[3]|splitflags); } } } } } static void K_drawKartBumpersOrKarma(void) { UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); INT32 fx = 0, fy = 0, splitflags = 0; // stuff for 3p / 4p splitscreen. drawinfo_t info; K_getLapsDrawinfo(&info); fx = info.x; fy = info.y; splitflags = info.flags; if (r_splitscreen > 1) { V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|splitflags, frameslash); if (itembreaker) { V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|splitflags, kp_rankcapsule, NULL); if (numtargets > 9 || nummapboxes > 9) { UINT8 ln[2]; ln[0] = ((numtargets / 10) % 10); ln[1] = (numtargets % 10); V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); ln[0] = ((nummapboxes / 10) % 10); ln[1] = (nummapboxes % 10); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); } else { V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[numtargets % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[nummapboxes % 10]); } } else { if (stplyr->bumper <= 0 && (gametyperules & GTR_KARMA) && comeback) { V_DrawMappedPatch(fx, fy-1, V_HUDTRANS|splitflags, kp_splitkarmabomb, colormap); V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[stplyr->karmapoints % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[2 % 10]); } else { INT32 maxbumper = K_StartingBumperCount(); V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|splitflags, kp_rankbumper, colormap); if (stplyr->bumper > 9 || maxbumper > 9) { UINT8 ln[2]; ln[0] = (stplyr->bumper / 10 % 10); ln[1] = (stplyr->bumper % 10); V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); ln[0] = ((abs(maxbumper) / 10) % 10); ln[1] = (abs(maxbumper) % 10); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); } else { V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->bumper) % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[(maxbumper) % 10]); } } } } else { if (itembreaker) { patch_t *item = W_CachePatchName("RNDMA0", PU_PATCH); UINT8 *itemcolormap = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_CACHE); if (!K_UseColorHud()) { V_DrawScaledPatch(fx, fy, V_HUDTRANS|splitflags, kp_timesticker[0]); } else //Colourized hud { V_DrawMappedPatch(fx, fy, V_HUDTRANS|splitflags, kp_timesticker[1], colormap); } V_DrawStretchyFixedPatch((29 + item->width/2/4)*FRACUNIT, 193*FRACUNIT, FRACUNIT/2, FRACUNIT/2, V_HUDTRANS|splitflags, item, itemcolormap); V_DrawStretchyFixedPatch((29 + item->width/2/4)*FRACUNIT, 192*FRACUNIT, FRACUNIT/2, FRACUNIT/2, V_HUDTRANS|splitflags, item, NULL); V_DrawKartString(fx+47, fy+3, V_HUDTRANS|splitflags, va("%d/%d", numtargets, nummapboxes)); } else { if (stplyr->bumper <= 0) { V_DrawMappedPatch(fx, fy, V_HUDTRANS|splitflags, (K_UseColorHud() ? kp_karmasticker[1] : kp_karmasticker[0]), colormap); V_DrawKartString(fx+47, fy+3, V_HUDTRANS|splitflags, va("%d/2", stplyr->karmapoints)); } else { INT32 maxbumper = K_StartingBumperCount(); if (stplyr->bumper > 9 && maxbumper > 9) V_DrawMappedPatch(fx, fy, V_HUDTRANS|splitflags, (K_UseColorHud() ? kp_bumperstickerwide[1] : kp_bumperstickerwide[0]), colormap); else V_DrawMappedPatch(fx, fy, V_HUDTRANS|splitflags, (K_UseColorHud() ? kp_bumpersticker[1] : kp_bumpersticker[0]), colormap); V_DrawKartString(fx+47, fy+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->bumper, maxbumper)); } } } } static void K_drawKartWanted(void) { UINT8 i, numwanted = 0; UINT8 *colormap = NULL; INT32 basex = 0, basey = 0; 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) { basex -= 48; // Position Number offset.... if (gametyperules & GTR_CIRCUIT) basex -= 30; } } else if (r_splitscreen == 3) // 4P splitscreen... { basex = BASEVIDWIDTH/2 - (kp_wantedsplit->width/2); // center on screen basey = BASEVIDHEIGHT - 55; //basey2 = 4; } if (battlewanted[0] != -1) colormap = R_GetTranslationColormap(0, players[battlewanted[0]].skincolor, GTC_CACHE); V_DrawFixedPatch(basex< 1 ? kp_wantedsplit : kp_wanted), colormap); /*if (basey2) V_DrawFixedPatch(basex< 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); patch_t *wantedicon = (scale == FRACUNIT ? faceprefix[p->skin][FACE_WANTED] : faceprefix[p->skin][FACE_RANK]); V_DrawFixedPatch((x+wantedicon->leftoffset)<topoffset)<skin][FACE_WANTED] : faceprefix[p->skin][FACE_RANK]), colormap);*/ } } } 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->awayviewtics) { 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_FLAMESHIELD || checkplayer->flametimer > 0) { pnum += 8; } else if (K_IsAltShrunk(checkplayer) && (checkplayer->itemtype == KITEM_SHRINK || checkplayer->growshrinktimer < 0)) { pnum += 6; } else 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, 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) { UINT16 chatcolor = skincolors[p->skincolor].chatcolor; V_DrawCenteredSmallStringAtFixed(x, y, V_HUDTRANS|V_SPLITSCREEN|chatcolor, va("P%d", id+1)); } static void K_DrawRivalTagForPlayer(fixed_t x, fixed_t y) { UINT8 blinkstates[4] = {SKINCOLOR_RED, SKINCOLOR_ORANGE, SKINCOLOR_YELLOW, SKINCOLOR_ORANGE}; UINT16 chatcolor = skincolors[blinkstates[(leveltime / 3) % 4]].chatcolor; V_DrawCenteredSmallStringAtFixed(x, y, V_HUDTRANS|V_SPLITSCREEN|chatcolor, "RIVAL"); } static const char *K_StringTypingDot(player_t *p) { static const char *dots = "..."; return dots + CLAMP(3 - p->typing_duration/16, 0, 3); } static void K_DrawTypingNotifier(fixed_t x, fixed_t y, player_t *p) { if (p->cmd.flags & TICCMD_TYPING) { V_DrawCenteredSmallStringAtFixed(x, y, V_SPLITSCREEN, va("Typing%s",K_StringTypingDot(p))); } } static void K_DrawNameTagForPlayer(fixed_t x, fixed_t y, player_t *p) { const INT32 clr = skincolors[p->skincolor].chatcolor; const INT32 namelen = V_ThinStringWidth(player_names[p - players], V_6WIDTHSPACE|V_ALLOWLOWERCASE); UINT8 *colormap = V_GetStringColormap(clr); INT32 barx = x + 6*FRACUNIT, bary = y - 16*FRACUNIT, barw = namelen*FRACUNIT; UINT8 backcolor = colormap ? colormap[31] : 31, frontcolor = colormap ? colormap[0] : 0; INT32 vflags = V_SPLITSCREEN; // 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. // Draw the stem { fixed_t stemx; fixed_t stemy; int j; boolean flipcam = (p->pflags & PF_FLIPCAM) && (p->mo->eflags & MFE_VERTICALFLIP); boolean flipped; if (flipcam) flipped = (p->mo->eflags & MFE_VERTICALFLIP) != (stplyr->mo->eflags & MFE_VERTICALFLIP); else flipped = p->mo->eflags & MFE_VERTICALFLIP; stemx = x; stemy = y; if (flipped) { for (j = 0; j < 4; j++) { fixed_t last = j == 3 ? FRACUNIT : 0; stemy += FRACUNIT*4; V_DrawFixedFill(stemx, stemy, 3*FRACUNIT, 4*FRACUNIT, vflags|backcolor); V_DrawFixedFill(stemx + FRACUNIT, stemy, FRACUNIT, 4*FRACUNIT - last, vflags|frontcolor); stemx += FRACUNIT; } bary += FRACUNIT*33; x += FRACUNIT; y += FRACUNIT*33; } else { for (j = 0; j < 4; j++) { fixed_t last = j == 3 ? FRACUNIT : 0; stemy -= FRACUNIT*4; V_DrawFixedFill(stemx, stemy, 3*FRACUNIT, 4*FRACUNIT, vflags|backcolor); V_DrawFixedFill(stemx + FRACUNIT, stemy + last, FRACUNIT, 4*FRACUNIT - last, vflags|frontcolor); stemx += FRACUNIT; } } V_DrawFixedFill(barx, bary, barw, 3*FRACUNIT, vflags|backcolor); V_DrawFixedFill(barx - FRACUNIT, bary + FRACUNIT, barw, FRACUNIT, vflags|frontcolor); } // END DRAWFILL DUMBNESS // Draw the name itself V_DrawThinStringAtFixed(x + (5*FRACUNIT), y - (26*FRACUNIT), vflags|V_6WIDTHSPACE|V_ALLOWLOWERCASE|clr, player_names[p - players]); // Also draw stats of restated players. if (p->kartspeed != skins[p->skin].kartspeed || p->kartweight != skins[p->skin].kartweight) { V_DrawSmallStringAtFixed(x + (5*FRACUNIT), y - (31*FRACUNIT), vflags, va("\x84S%d ", p->kartspeed)); V_DrawSmallStringAtFixed(x + (15*FRACUNIT), y - (31*FRACUNIT), vflags, va("\x87W%d ", p->kartweight)); } } 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, 0, GTC_CACHE); else colormap = R_GetTranslationColormap(TC_RAINBOW, 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) { const fixed_t maxdistance = 8192*mapobjectscale; vector3_t c; UINT8 cnum = R_GetViewNumber(); UINT8 tobesorted[MAXPLAYERS]; fixed_t sortdist[MAXPLAYERS]; UINT8 sortlen = 0; UINT8 i, j; if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) { return; } if (stplyr->awayviewtics) { return; } // Crop within splitscreen bounds V_SetClipRectForPlayer(cnum); 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.boss) { 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]); } } for (i = 0; i < MAXPLAYERS; i++) { player_t *ntplayer = &players[i]; fixed_t distance = maxdistance+1; vector3_t v; if (!playeringame[i] || ntplayer->spectator) { // Not in-game continue; } if (ntplayer->mo == NULL || P_MobjWasRemoved(ntplayer->mo)) { // No object continue; } if (ntplayer->mo->renderflags & K_GetPlayerDontDrawFlag(stplyr)) { // Invisible on this screen continue; } if ((gametyperules & GTR_BUMPERS) && (ntplayer->bumper <= 0)) { // Dead in Battle continue; } if (!P_CheckSight(stplyr->mo, ntplayer->mo)) { // Can't see continue; } v.x = R_InterpolateFixed(ntplayer->mo->old_x, ntplayer->mo->x); v.y = R_InterpolateFixed(ntplayer->mo->old_y, ntplayer->mo->y); v.z = R_InterpolateFixed(ntplayer->mo->old_z, ntplayer->mo->z); if (!(ntplayer->mo->eflags & MFE_VERTICALFLIP)) { v.z += ntplayer->mo->height; } distance = R_PointToDist2(c.x, c.y, v.x, v.y); if (distance > maxdistance) { // Too far away continue; } tobesorted[sortlen] = ntplayer - players; sortdist[sortlen] = distance; sortlen++; } if (sortlen > 0) { UINT8 sortedplayers[sortlen]; for (i = 0; i < sortlen; i++) { UINT8 pos = 0; for (j = 0; j < sortlen; j++) { if (j == i) { continue; } if (sortdist[i] < sortdist[j] || (sortdist[i] == sortdist[j] && i > j)) { pos++; } } sortedplayers[pos] = tobesorted[i]; } for (i = 0; i < sortlen; i++) { trackingResult_t result; player_t *ntplayer = &players[sortedplayers[i]]; fixed_t headOffset = 36*ntplayer->mo->scale; SINT8 localindicator = -1; vector3_t v; v.x = R_InterpolateFixed(ntplayer->mo->old_x, ntplayer->mo->x); v.y = R_InterpolateFixed(ntplayer->mo->old_y, ntplayer->mo->y); v.z = R_InterpolateFixed(ntplayer->mo->old_z, ntplayer->mo->z); v.z += (ntplayer->mo->height / 2); if (stplyr->mo->eflags & MFE_VERTICALFLIP) { v.z -= headOffset; } else { v.z += headOffset; } K_ObjectTracking(&result, &v, false); if (result.onScreen == true) { if (!(demo.playback == true && camera[cnum].freecam == true)) { for (j = 0; j <= r_splitscreen; j++) { if (ntplayer == &players[displayplayers[j]]) { break; } } if (j <= r_splitscreen && j != cnum) { localindicator = j; } } if (cv_seenames.value && localindicator >= 0) { K_DrawLocalTagForPlayer(result.x, result.y, ntplayer, localindicator); } else if (ntplayer->bot) { if (ntplayer->botvars.rival == true) { K_DrawRivalTagForPlayer(result.x, result.y); } } else if (netgame || demo.playback) { if (K_ShowPlayerNametag(ntplayer) == true) { K_DrawNameTagForPlayer(result.x, result.y, ntplayer); } K_DrawTypingNotifier(result.x, result.y, ntplayer); } } } } V_ClearClipRect(); } #define MINIHEADSCALE (FRACUNIT / 2) static inline void K_drawKartMinimapIcon(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, patch_t *icon, UINT8 *colormap, vector2_t *origin) { // 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. fixed_t amnumxpos, amnumypos; INT32 amxpos, amypos; fixed_t scale = FRACUNIT; INT16 w, h; if (origin) { w = origin->x * 2; h = origin->y * 2; } else { w = icon->width; h = icon->height; } if (!cv_showminimapangle.value && (icon == kp_minimapdot)) return; if (cv_minihead.value) scale = MINIHEADSCALE; amnumxpos = (FixedMul(objx, minimapinfo.zoom) - minimapinfo.offs_x); amnumypos = -(FixedMul(objy, minimapinfo.zoom) - minimapinfo.offs_y); if (encoremode) amnumxpos = -amnumxpos; amxpos = amnumxpos + ((hudx + (minimapinfo.minimap_pic->width-w)/2)<height-h)/2)<width-48)/2)<height-24)/2)<mo) { return; } fixed_t amnumxpos, amnumypos; INT32 amxpos, amypos; UINT16 skin = 0; UINT16 chatcolor = skincolors[player->mo->color].chatcolor; amnumxpos = (FixedMul(objx, minimapinfo.zoom) - minimapinfo.offs_x); amnumypos = -(FixedMul(objy, minimapinfo.zoom) - minimapinfo.offs_y); if (encoremode) amnumxpos = -amnumxpos; skin = ((skin_t*)player->mo->skin)-skins; amxpos = amnumxpos + ((hudx + (minimapinfo.minimap_pic->width-faceprefix[skin][FACE_MINIMAP]->width)/2)<height-faceprefix[skin][FACE_MINIMAP]->height)/2)<width*FRACUNIT/2; amnumypos += minimapinfo.minimap_pic->height*FRACUNIT/2; V_DrawFixedFill((amnumxpos + hudx*FRACUNIT) - (size / 2), (amnumypos + hudy*FRACUNIT) - (size / 2), size, size, flags | color); } static void K_drawKartMinimapWaypoint(waypoint_t *wp, INT32 hudx, INT32 hudy, INT32 flags) { UINT8 pal = 0x95; // blue UINT8 size = 3; if (wp == stplyr->nextwaypoint) { pal = 0x70; // green size = 6; } else if (K_GetWaypointIsShortcut(wp)) // shortcut { pal = 0x20; // pink } else if (!K_GetWaypointIsEnabled(wp)) // disabled { pal = 0x10; // gray } else if (wp->numnextwaypoints == 0 || wp->numprevwaypoints == 0) { pal = 0x40; // yellow } K_drawKartMinimapDot(wp->mobj->x, wp->mobj->y, hudx, hudy, flags, pal, size); } static void K_drawKartMinimapRings(mobj_t *mobj, INT32 hudx, INT32 hudy, INT32 flags) { UINT8 pal = 0x40; // yellow UINT8 size = 1; K_drawKartMinimapDot(mobj->x, mobj->y, hudx, hudy, flags, pal, size); } static void K_drawKartMinimapCluster(INT32 hudx, INT32 hudy, INT32 flags) { UINT8 pal = 180; // Strong pink color. UINT8 size = 6; fixed_t clusterx, clustery; clusterx = clustery = 0; if ((clusterid != UINT32_MAX) && (players[clusterid].mo) && (!P_MobjWasRemoved(players[clusterid].mo))) { clusterx = players[clusterid].mo->x; clustery = players[clusterid].mo->y; } else { clusterx = clusterpoint.x; clustery = clusterpoint.y; } K_drawKartMinimapDot(clusterx, clustery, hudx, hudy, flags, pal, size); } #define ICON_DOT_RADIUS (cv_minihead.value && !cv_showminimapnames.value) ? 8 : 10 typedef struct { boolean draw; boolean red; } spbdraw_t; static spbdraw_t K_ShouldDrawSPB(mobj_t *mobj) { fixed_t dist; spbdraw_t spbdraw; spbdraw.draw = false; spbdraw.red = false; if (!mobj->tracer) { spbdraw.draw = true; return spbdraw; } dist = FixedHypot(mobj->x-mobj->tracer->x, mobj->y-mobj->tracer->y)/mobj->tracer->scale; if (dist < 3072) { spbdraw.draw = (dist > 1024); spbdraw.red = (leveltime/2 % 2 == 0); return spbdraw; } spbdraw.draw = true; return spbdraw; } #define TICTOANGLE(t) (FixedAngle(((360 * FRACUNIT / TICRATE) * t) % (360 * FRACUNIT))) static void K_drawKartMinimap(void) { patch_t *workingPic; INT32 i = 0; INT32 x, y; INT32 minimaptrans = cv_kartminimap.value; INT32 splitflags = 0; UINT16 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; spbdraw_t spb; UINT16 usecolor; boolean colorizeplayer; fixed_t invingradient = 0; #ifdef ROTSPRITE angle_t rollangle = 0; INT32 rot = 0; INT32 sparkleflags; patch_t *rotsparkle; boolean halftrans = false; fixed_t transmul = 0; UINT32 invintrans = 0; #endif vector2_t iconoffsets; INT32 widthhalf, heighthalf; INT32 adjustx, adjusty; // 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 (stplyrnum != 0) return; if (minimapinfo.minimap_pic == NULL) { return; // no pic, just get outta here } iconoffsets.x = 0; iconoffsets.y = 0; adjustx = adjusty = 0; widthhalf = heighthalf = 0; drawinfo_t info; K_getMinimapDrawinfo(&info); x = info.x; y = info.y; splitflags = info.flags; if (r_splitscreen < 2) // 1/2P right aligned { const tic_t length = TICRATE/2; if (!lt_exitticker) return; if (lt_exitticker < length) minimaptrans = (((INT32)lt_exitticker)*minimaptrans)/((INT32)length); } else if (r_splitscreen == 3) // 4P centered { const tic_t length = TICRATE/2; if (!lt_exitticker) return; if (lt_exitticker < length) minimaptrans = (((INT32)lt_exitticker)*minimaptrans)/((INT32)length); } // 3P lives in the middle of the bottom right player and shouldn't fade in OR slide if (!minimaptrans) return; colorizeplayer = false; minimaptrans = ((10-minimaptrans)<width, y, splitflags|minimaptrans|V_FLIP, minimapinfo.minimap_pic); else V_DrawScaledPatch(x, y, splitflags|minimaptrans, minimapinfo.minimap_pic); // let offsets transfer to the heads, too! if (encoremode) x += SHORT(minimapinfo.minimap_pic->leftoffset); else x -= SHORT(minimapinfo.minimap_pic->leftoffset); y -= SHORT(minimapinfo.minimap_pic->topoffset); // 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) { demoghost *g = ghosts; while (g) { if (g->mo->skin) skin = ((skin_t*)g->mo->skin)-skins; else skin = 0; if (g->mo->color) { if (g->mo->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, g->mo->color, GTC_CACHE); else colormap = R_GetTranslationColormap(skin, 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); patch_t *ghostPic = faceprefix[skin][FACE_MINIMAP]; widthhalf = ((ghostPic->width) / 2); heighthalf = ((ghostPic->height) / 2); if (cv_minihead.value) { adjustx = FixedMul(4, (widthhalf - ghostPic->leftoffset) * FRACUNIT / widthhalf); adjusty = FixedMul(4, (heighthalf - ghostPic->topoffset) * FRACUNIT / heighthalf); } iconoffsets.x = widthhalf - ghostPic->leftoffset - adjustx; iconoffsets.y = heighthalf - ghostPic->topoffset - adjusty; K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, ghostPic, colormap, &iconoffsets); g = g->next; } if (!stplyr->mo || stplyr->spectator || (!cv_showfinishedplayers.value && stplyr->exiting)) return; localplayers[numlocalplayers++] = stplyr-players; } else { for (i = MAXPLAYERS-1; i >= 0; i--) { if (!playeringame[i]) continue; if (!players[i].mo || players[i].spectator || !players[i].mo->skin || (!cv_showfinishedplayers.value && players[i].exiting)) 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; } // Now we know it's not a display player, handle non-local player exceptions. if ((gametyperules & GTR_BUMPERS) && players[i].bumper <= 0) 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)) { workingPic = kp_nocontestminimap; widthhalf = ((workingPic->width) / 2); heighthalf = ((workingPic->height) / 2); if (cv_minihead.value) { adjustx = FixedMul(4, (widthhalf - workingPic->leftoffset) * FRACUNIT / widthhalf); adjusty = FixedMul(4, (heighthalf - workingPic->topoffset) * FRACUNIT / heighthalf); } iconoffsets.x = widthhalf - workingPic->leftoffset - adjustx; iconoffsets.y = heighthalf - workingPic->topoffset - adjusty; colormap = R_GetTranslationColormap(TC_DEFAULT, mobj->color, GTC_CACHE); if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) mobj = mobj->tracer; } else { skin = ((skin_t*)mobj->skin)-skins; workingPic = faceprefix[skin][FACE_MINIMAP]; widthhalf = ((workingPic->width) / 2); heighthalf = ((workingPic->height) / 2); if (cv_minihead.value) { adjustx = FixedMul(4, (widthhalf - workingPic->leftoffset) * FRACUNIT / widthhalf); adjusty = FixedMul(4, (heighthalf - workingPic->topoffset) * FRACUNIT / heighthalf); } iconoffsets.x = widthhalf - workingPic->leftoffset - adjustx; iconoffsets.y = heighthalf - workingPic->topoffset - adjusty; #ifdef ROTSPRITE if ((K_MinimapIconCanSpinout()) && (players[i].spinoutrot)) { // Rotate counterclockwise. rollangle = FixedAngle(players[i].spinoutrot * -1); rot = R_GetRollAngle(rollangle); if (rot) { workingPic = Patch_GetRotated(workingPic, rot, false); } } #endif colorizeplayer = mobj->colorized; invingradient = K_InvincibilityGradient(players[i].invincibilitytimer); if ((players[i].invincibilitytimer) && ((invingradient > (FRACUNIT/2)) || (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))) { usecolor = ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) ? K_AltInvincibilityColor(leveltime / 2) : K_RainbowColor(leveltime / 2)); colorizeplayer = true; } else { usecolor = mobj->color; } if (usecolor) { if (colorizeplayer) colormap = R_GetTranslationColormap(TC_RAINBOW, usecolor, GTC_CACHE); else colormap = R_GetTranslationColormap(skin, usecolor, GTC_CACHE); } else colormap = NULL; } //if (doprogressionbar == false) { // draw external players transparent if (mobj->player) { splitflags &= ~V_HUDTRANS; splitflags |= V_HUDTRANSHALF; } 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, &iconoffsets); #ifdef ROTSPRITE if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (players[i].invincibilitytimer) && (invingradient)) { // Draw Alt. Invin. sparkles halftrans = ((splitflags & V_HUDTRANSHALF) == V_HUDTRANSHALF); transmul = 0; invintrans = 0; sparkleflags = splitflags & (~(V_HUDTRANS | V_HUDTRANSHALF)); if (halftrans) { transmul = FRACUNIT - (V_GetHudTransHalf() * FRACUNIT / 10); } else { transmul = FRACUNIT - (V_GetHudTrans() * FRACUNIT / 10); } transmul *= 2; invintrans = max(0, min(9, 10 - FixedMul(FixedMul(10, invingradient), transmul))) << V_ALPHASHIFT; sparkleflags |= invintrans; if (kp_altinvinsparkle) { rot = R_GetRollAngle(-TICTOANGLE(leveltime)); if (rot) { rotsparkle = Patch_GetRotated( kp_altinvinsparkle, rot, false); } else { rotsparkle = kp_altinvinsparkle; } if (rotsparkle) { widthhalf = ((kp_altinvinsparkle->width) / 2); heighthalf = ((kp_altinvinsparkle->height) / 2); if (cv_minihead.value) { adjustx = FixedMul( 4, (widthhalf - kp_altinvinsparkle->leftoffset) * FRACUNIT / widthhalf); adjusty = FixedMul( 4, (heighthalf - kp_altinvinsparkle->topoffset) * FRACUNIT / heighthalf); } iconoffsets.x = widthhalf - kp_altinvinsparkle->leftoffset - adjustx; iconoffsets.y = heighthalf - kp_altinvinsparkle->topoffset - adjusty; K_drawKartMinimapIcon(interpx, interpy, x, y, sparkleflags | V_ADD, rotsparkle, colormap, &iconoffsets); } } } #endif if (mobj->player) { // Draw the Nametag K_drawKartMinimapNametag(interpx, interpy, x, y, splitflags, &players[i]); } // Target reticule if ((gametype == GT_RACE && players[i].position == spbplace) || ((gametyperules & GTR_WANTED) && K_IsPlayerWanted(&players[i]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, NULL); } } } } // draw minimap-pertinent objects for (mobj = kitemcap; mobj; mobj = next) { workingPic = NULL; colormap = NULL; next = mobj->itnext; if (mobj->health <= 0) continue; switch (mobj->type) { case MT_SPB: spb = K_ShouldDrawSPB(mobj); if (spb.draw) { INT32 tc = spb.red ? TC_BLINK : TC_DEFAULT; skincolornum_t color = SKINCOLOR_RED; // If not flashing red then... if (!spb.red && mobj->color != SKINCOLOR_NONE) { // Use SPB's current color. color = mobj->color; } workingPic = kp_splitkarmabomb; colormap = R_GetTranslationColormap(tc, color, GTC_CACHE); } else { workingPic = NULL; } 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, NULL); } for (mobj = misccap; mobj; mobj = next) { workingPic = NULL; colormap = NULL; next = mobj->itnext; if (mobj->health <= 0) continue; switch (mobj->type) { case MT_RANDOMITEM: if (itembreaker && (mobj->flags2 & MF2_BOSSNOTRAP) && !(mobj->flags2 & MF2_BOSSFLEE)) { workingPic = kp_itemboxminimap; colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE); } else { workingPic = NULL; } 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, NULL); } // draw our local players here, opaque. { splitflags &= ~V_HUDTRANSHALF; splitflags |= V_HUDTRANS; } // ...but first, any boss targets. if (bossinfo.boss) { 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, 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, NULL); } } 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; if (mobj->health <= 0 && (players[localplayers[i]].pflags & PF_NOCONTEST)) { workingPic = kp_nocontestminimap; widthhalf = ((workingPic->width) / 2); heighthalf = ((workingPic->height) / 2); if (cv_minihead.value) { adjustx = FixedMul(4, (widthhalf - workingPic->leftoffset) * FRACUNIT / widthhalf); adjusty = FixedMul(4, (heighthalf - workingPic->topoffset) * FRACUNIT / heighthalf); } iconoffsets.x = widthhalf - workingPic->leftoffset - adjustx; iconoffsets.y = heighthalf - workingPic->topoffset - adjusty; colormap = R_GetTranslationColormap(TC_DEFAULT, mobj->color, GTC_CACHE); nocontest = true; if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) mobj = mobj->tracer; } else { skin = ((skin_t*)mobj->skin)-skins; workingPic = faceprefix[skin][FACE_MINIMAP]; widthhalf = ((workingPic->width) / 2); heighthalf = ((workingPic->height) / 2); if (cv_minihead.value) { adjustx = FixedMul(4, (widthhalf - workingPic->leftoffset) * FRACUNIT / widthhalf); adjusty = FixedMul(4, (heighthalf - workingPic->topoffset) * FRACUNIT / heighthalf); } iconoffsets.x = widthhalf - workingPic->leftoffset - adjustx; iconoffsets.y = heighthalf - workingPic->topoffset - adjusty; #ifdef ROTSPRITE if ((K_MinimapIconCanSpinout()) && (players[localplayers[i]].spinoutrot)) { // Rotate counterclockwise. rollangle = FixedAngle(players[localplayers[i]].spinoutrot * -1); rot = R_GetRollAngle(rollangle); if (rot) { workingPic = Patch_GetRotated(workingPic, rot, false); } } #endif colorizeplayer = mobj->colorized; invingradient = K_InvincibilityGradient( players[localplayers[i]].invincibilitytimer); if ((players[localplayers[i]].invincibilitytimer) && ((invingradient > (FRACUNIT / 2)) || (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))) { usecolor = (K_RainbowColor(leveltime / 2)); colorizeplayer = true; } else { usecolor = mobj->color; } if (usecolor) { if (colorizeplayer) colormap = R_GetTranslationColormap(TC_RAINBOW, usecolor, GTC_CACHE); else colormap = R_GetTranslationColormap(skin, usecolor, GTC_CACHE); } else colormap = NULL; } //if (doprogressionbar == false) { 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, mobj->color, GTC_CACHE); } interpx = R_InterpolateFixed(mobj->old_x, mobj->x); interpy = R_InterpolateFixed(mobj->old_y, mobj->y); if (!nocontest) { if (cv_showminimapangle.value == 1) { K_drawKartMinimapIcon( interpx, interpy, x + FixedMul(FCOS(ang), ICON_DOT_RADIUS), y - FixedMul(FSIN(ang), ICON_DOT_RADIUS), splitflags, kp_minimapdot, colormap, NULL ); } else if (cv_showminimapangle.value == 2) { K_drawKartMinimapHeadlight( interpx, interpy, x, y, splitflags, ang, colormap ); } } K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, &iconoffsets); #ifdef ROTSPRITE if ((!nocontest) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (players[localplayers[i]].invincibilitytimer) && (invingradient)) { // Draw Alt. Invin. sparkles halftrans = ((splitflags & V_HUDTRANSHALF) == V_HUDTRANSHALF); transmul = 0; invintrans = 0; sparkleflags = splitflags & (~(V_HUDTRANS | V_HUDTRANSHALF)); if (halftrans) { transmul = FRACUNIT - (V_GetHudTransHalf() * FRACUNIT / 10); } else { transmul = FRACUNIT - (V_GetHudTrans() * FRACUNIT / 10); } transmul *= 2; invintrans = max(0, min(9, 10 - FixedMul(FixedMul(10, invingradient), transmul))) << V_ALPHASHIFT; sparkleflags |= invintrans; if (kp_altinvinsparkle) { rot = R_GetRollAngle(-TICTOANGLE(leveltime)); if (rot) { rotsparkle = Patch_GetRotated( kp_altinvinsparkle, rot, false); } else { rotsparkle = kp_altinvinsparkle; } if (rotsparkle) { widthhalf = ((kp_altinvinsparkle->width) / 2); heighthalf = ((kp_altinvinsparkle->height) / 2); if (cv_minihead.value) { adjustx = FixedMul( 4, (widthhalf - kp_altinvinsparkle->leftoffset) * FRACUNIT / widthhalf); adjusty = FixedMul( 4, (heighthalf - kp_altinvinsparkle->topoffset) * FRACUNIT / heighthalf); } iconoffsets.x = widthhalf - kp_altinvinsparkle->leftoffset - adjustx; iconoffsets.y = heighthalf - kp_altinvinsparkle->topoffset - adjusty; K_drawKartMinimapIcon(interpx, interpy, x, y, sparkleflags | V_ADD, rotsparkle, colormap, &iconoffsets); } } } #endif // Target reticule if ((gametype == GT_RACE && players[localplayers[i]].position == spbplace) || ((gametyperules & GTR_WANTED) && K_IsPlayerWanted(&players[localplayers[i]]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, NULL); } K_drawKartMinimapNametag(interpx, interpy, x, y, splitflags, mobj->player); } } if (cv_kartdebugwaypoints.value != 0) { size_t idx; for (idx = 0; idx < K_GetNumWaypoints(); ++idx) { waypoint_t *wp = K_GetWaypointFromIndex(idx); I_Assert(wp != NULL); K_drawKartMinimapWaypoint(wp, x, y, splitflags); } if (stplyr->nextwaypoint != NULL) { // should be drawn on top of the others K_drawKartMinimapWaypoint(stplyr->nextwaypoint, x, y, splitflags); } } if (cv_kartdebugrings.value != 0) { for (mobj = misccap; mobj; mobj = next) { next = mobj->itnext; if (mobj->type != MT_RING && mobj->type != MT_FLINGRING) continue; K_drawKartMinimapRings(mobj, x, y, splitflags); } } if (cv_kartdebugcluster.value != 0) { K_drawKartMinimapCluster(x, y, splitflags); } } static void K_drawKartFinish(void) { INT32 timer, minsplitstationary, pnum = 0, splitflags = V_SPLITSCREEN; patch_t **kptodraw; { timer = stplyr->karthud[khud_finish]; kptodraw = kp_racefinish; minsplitstationary = 2; } 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<width)<height)<<(FRACBITS-1)), FRACUNIT, splitflags, kptodraw[pnum], NULL); } } static void K_drawKartStartCountdown(void) { INT32 pnum = 0; if (leveltime > starttime-(3*TICRATE)) { 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; } // Re-enable when graphics are drawn up for this. /*if (numplayers == 2) { pnum++; // DUEL }*/ } if ((leveltime % (2*5)) / 5) // blink pnum += 5; if (r_splitscreen) // splitscreen pnum += 10; V_DrawScaledPatch(STCD_X - (kp_startcountdown[pnum]->width/2), STCD_Y - (kp_startcountdown[pnum]->height/2), V_SPLITSCREEN, kp_startcountdown[pnum]); } } static void K_drawBattleFullscreen(void) { INT32 cardanim = stplyr->karthud[khud_cardanimation] << FRACBITS; // fill in the fractional bits if (cardanim && cardanim != 164*FRACUNIT) { INT32 frac = R_GetTimeFrac(RTF_LEVEL) * ((164 - stplyr->karthud[khud_cardanimation])/8 + 1); if (stplyr->exiting) cardanim += frac; else cardanim += stplyr->karmadelay < 6*TICRATE ? -frac : frac; } INT32 x = BASEVIDWIDTH/2; INT32 y = (-64*FRACUNIT) + cardanim; // card animation goes from 0 to 164, 164 is the middle of the screen the screen INT32 splitflags = V_SNAPTOTOP; // I don't feel like properly supporting non-green resolutions, so you can have a misuse of SNAPTO instead fixed_t scale = FRACUNIT; boolean drawcomebacktimer = true; // lazy hack because it's cleaner in the long run. if (!LUA_HudEnabled(hud_battlecomebacktimer)) drawcomebacktimer = false; if (r_splitscreen) { if ((splitscreen == 1 && stplyrnum == 1) || (splitscreen > 1 && stplyrnum & 2)) { y = (232*FRACUNIT) - (cardanim/2); splitflags = V_SNAPTOBOTTOM; } else y = (-32*FRACUNIT) + (cardanim/2); if (r_splitscreen > 1) { scale /= 2; if (stplyrnum & 1) x = 3*BASEVIDWIDTH/4; else x = BASEVIDWIDTH/4; } else { if (stplyr->exiting) { if (stplyrnum & 1) x = BASEVIDWIDTH-96; else x = 96; } else scale /= 2; } } if (stplyr->exiting) { if (stplyrnum == 0) V_DrawFadeScreen(0xFF00, 16); if (stplyr->exiting <= 6*TICRATE && !stplyr->spectator) { patch_t *p = kp_battlecool; if (K_IsPlayerLosing(stplyr)) p = kp_battlelose; else if (stplyr->position == 1 && (!itembreaker || numtargets >= nummapboxes)) p = kp_battlewin; V_DrawFixedPatch(x<bumper <= 0 && stplyr->karmadelay && comeback && !stplyr->spectator && drawcomebacktimer) { UINT16 t = stplyr->karmadelay/(10*TICRATE); INT32 txoff, adjust = (r_splitscreen > 1) ? 4 : 6; // normal string is 8, kart string is 12, half of that for ease INT32 ty = (BASEVIDHEIGHT/2)+66; txoff = adjust; while (t) { txoff += adjust; t /= 10; } if (r_splitscreen) { if (r_splitscreen > 1) ty = (BASEVIDHEIGHT/4)+33; if ((splitscreen == 1 && stplyrnum == 1) || (splitscreen > 1 && stplyrnum & 2)) ty += (BASEVIDHEIGHT/2); } else V_DrawFadeScreen(0xFF00, 16); if (!comebackshowninfo) V_DrawFixedPatch(x< 1) V_DrawString(x-txoff, ty, 0, va("%d", stplyr->karmadelay/TICRATE)); else { if (!K_UseColorHud()) V_DrawFixedPatch(x<karmadelay/TICRATE)); } } // FREE PLAY? { UINT8 i; // check to see if there's anyone else at all for (i = 0; i < MAXPLAYERS; i++) { if (i == displayplayers[0]) continue; if (playeringame[i] && !players[i].spectator) break; } if (i != MAXPLAYERS) K_drawKartFreePlay(); } } 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)) return; if (stplyrnum == 1 && r_splitscreen) { pn = pnum[1]; tn = turn[1]; dr = drift[1]; } else if (stplyrnum == 2 && r_splitscreen > 1) { pn = pnum[2]; tn = turn[2]; dr = drift[2]; } else if (stplyrnum == 3 && r_splitscreen > 2) { pn = pnum[3]; tn = turn[3]; dr = drift[3]; } else { pn = pnum[0]; tn = turn[0]; dr = drift[0]; } 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->cmd.turning > steerThreshold) // strong left turn target = 2; else if (stplyr->cmd.turning < -steerThreshold) // strong right turn target = -2; else if (stplyr->cmd.turning > 0) // weak left turn target = 1; else if (stplyr->cmd.turning < 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->cmd.turning/50) tn -= (tn - (stplyr->cmd.turning/50))/8; if (dr != stplyr->drift*16) dr -= (dr - (stplyr->drift*16))/8; if (r_splitscreen == 1) { scale = (2*FRACUNIT)/3; y += FRACUNIT/vid.dup; // 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); 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, driftcolor, GTC_CACHE); else if (stplyr->mo->colorized && stplyr->mo->color) // invincibility/grow/shrink! colmap = R_GetTranslationColormap(TC_RAINBOW, stplyr->mo->color, GTC_CACHE); } V_DrawFixedPatch(x, y, scale, splitflags, kp_fpview[target], colmap); if (stplyrnum == 1 && r_splitscreen) { pnum[1] = pn; turn[1] = tn; drift[1] = dr; } else if (stplyrnum == 2 && r_splitscreen > 1) { pnum[2] = pn; turn[2] = tn; drift[2] = dr; } else if (stplyrnum == 3 && r_splitscreen > 2) { pnum[3] = pn; turn[3] = tn; drift[3] = dr; } else { pnum[0] = pn; turn[0] = tn; drift[0] = dr; } } // doesn't need to ever support 4p static void K_drawInput(void) { static INT32 pn = 0; INT32 target = 0, splitflags = (V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SLIDEIN); INT32 x = (BASEVIDWIDTH - 32)*FRACUNIT, y = (BASEVIDHEIGHT - 24)*FRACUNIT; UINT8 *shadowcolormap = NULL; INT32 offs, col; const skincolornum_t hudcolor = K_GetHudColor(); const INT32 accent1 = splitflags | skincolors[hudcolor].ramp[5]; const INT32 accent2 = splitflags | skincolors[hudcolor].ramp[9]; const UINT8 *hudcolormap = R_GetTranslationColormap(0, hudcolor, GTC_CACHE); if (r_splitscreen) return; #define BUTTW 8 #define BUTTH 11 #define drawbutt(xoffs, butt, symb)\ if ((K_GetKartButtons(stplyr) & butt))\ {\ offs = 2*FRACUNIT;\ col = accent1;\ }\ else\ {\ offs = 0;\ col = accent2;\ V_DrawFill((x + xoffs*FRACUNIT)>>FRACBITS, (y + BUTTH*FRACUNIT)>>FRACBITS, BUTTW-1, 2, splitflags|31);\ }\ V_DrawFill((x + xoffs*FRACUNIT)>>FRACBITS, (y+offs)>>FRACBITS, BUTTW-1, BUTTH, col);\ V_DrawFixedPatch(x + FRACUNIT + xoffs*FRACUNIT, y + offs + FRACUNIT, FRACUNIT, splitflags, fontv[TINY_FONT].font[symb-HU_FONTSTART], NULL) drawbutt(-2*BUTTW, BT_ACCELERATE, 'A'); drawbutt( -BUTTW, BT_BRAKE, 'B'); drawbutt( 0, BT_DRIFT, 'D'); drawbutt( BUTTW, BT_ATTACK, 'I'); #undef drawbutt #undef BUTTW #undef BUTTH y -= FRACUNIT; if (cv_showinput.value > 1) { INT32 joyx, joyxoffs, joyy, joyyoffs; joyxoffs = -8, joyyoffs = -24; joyx = x>>FRACBITS, joyy = y>>FRACBITS; // O backing if (cv_showinput.value == 2) { shadowcolormap = R_GetTranslationColormap(0, SKINCOLOR_BLACK, GTC_CACHE); V_DrawFixedPatch((joyx+joyxoffs)<cmd.turning || stplyr->cmd.throwdir) { INT16 turning = encoremode ? -stplyr->cmd.turning : stplyr->cmd.turning; if (cv_showinput.value == 2) { V_DrawFixedPatch((joyx+joyxoffs+3-turning/80)<cmd.throwdir/80)<cmd.throwdir/64)<cmd.throwdir/80, 10, 10, splitflags|31); V_DrawFill(joyx+joyxoffs+3-turning/64, joyy+joyyoffs+1-stplyr->cmd.throwdir/64, 10, 10, splitflags|accent1); } } else { if (cv_showinput.value == 2) { V_DrawFixedPatch((joyx+joyxoffs+3)<cmd.turning) // no turn target = 0; else // turning of multiple strengths! { target = ((abs(stplyr->cmd.turning) - 1)/200)+1; // was 125, do we need another toggle for this? Zzz... if (target > 4) target = 4; if (stplyr->cmd.turning < 0) target = -target; } if (pn != target) { if (abs(pn - target) == 1) pn = target; else if (pn < target) pn += 2; else //if (pn > target) pn -= 2; } if (pn < 0) { splitflags |= V_FLIP; // right turn x -= FRACUNIT; } target = abs(pn); if (target > 4) target = 4; if (!K_GetHudColor()) V_DrawFixedPatch(x, y, FRACUNIT, splitflags, kp_inputwheel[target], NULL); else { UINT8 *colormap; colormap = R_GetTranslationColormap(0, K_GetHudColor(), GTC_CACHE); V_DrawFixedPatch(x, y, FRACUNIT, splitflags, kp_inputwheel[target], colormap); } } } 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 = min(52-1, (3*TICRATE)-mapreset); V_DrawFadeScreen(0xFF00, 16); // Fade out V_DrawScaledPatch(0, 0, 0, kp_challenger[anim[offset]]); } static boolean K_DisplayingLapEmblem(void) { return ((cv_showlapemblem.value & 1) && (stplyr->karthud[khud_lapanimation])); } static void K_drawLapStartAnim(void) { if (!K_DisplayingLapEmblem()) 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, K_GetHudColor(), GTC_CACHE); fixed_t interpx, interpy, newval, oldval; newval = (BASEVIDWIDTH/2 + (32 * max(0, t - 76))) * FRACUNIT; oldval = (BASEVIDWIDTH/2 + (32 * max(0, tOld - 76))) * FRACUNIT; interpx = R_InterpolateFixed(oldval, newval); newval = (48 - (32 * max(0, progress - 76))) * FRACUNIT; oldval = (48 - (32 * 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->laps == (UINT8)(numlaps)) { newval = (62 - (32 * max(0, progress - 76))) * FRACUNIT; oldval = (62 - (32 * 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[min(progress/2, 10)], NULL); if (progress/2-12 >= 0) { newval = (188 + (32 * max(0, progress - 76))) * FRACUNIT; oldval = (188 + (32 * 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[min(progress/2-12, 6)], NULL); } } else { newval = (82 - (32 * max(0, progress - 76))) * FRACUNIT; oldval = (82 - (32 * 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[min(progress/2, 6)], NULL); if (progress/2-8 >= 0) { newval = (188 + (32 * max(0, progress - 76))) * FRACUNIT; oldval = (188 + (32 * 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->laps) / 10)][min(progress/2-8, 2)], NULL); if (progress/2-10 >= 0) { newval = (208 + (32 * max(0, progress - 76))) * FRACUNIT; oldval = (208 + (32 * 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->laps) % 10)][min(progress/2-10, 2)], NULL); } } } } // Redundant copy from k_kart. static INT32 K_GetRaceSplitsToggle(void) { if (cv_showlapemblem.value < 2) { // Splits aren't on. return 0; } // Not taking any chances with this stupid goddamn engine. return max((INT32)(cv_racesplits.value), 1); } static boolean K_doDrawSplits(void) { return (!K_DisplayingLapEmblem()) && stplyr->karthud[khud_splittimer] && (stplyr->karthud[khud_splittimer] > TICRATE/3 || stplyr->karthud[khud_splittimer]%2); } static void K_drawLapSplitTimestamp(void) { if (!K_GetRaceSplitsToggle()) { return; } // Draw the timestamp boolean debug_alwaysdrawsplits = false; if (K_doDrawSplits() || debug_alwaysdrawsplits) { INT32 split = stplyr->karthud[khud_splittime]; INT32 skin = stplyr->karthud[khud_splitskin]; INT32 color = stplyr->karthud[khud_splitcolor]; INT32 ahead = stplyr->karthud[khud_splitwin]; // INT32 pos = stplyr->karthud[khud_splitposition]; INT32 splitflags = V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; // debug if (!stplyr->karthud[khud_splittimer]) { ahead = ((leveltime/17)%5) - 2; split = leveltime; skin = stplyr->skin; color = stplyr->skincolor; } split = abs(split); UINT8 *skincolor = R_GetTranslationColormap(skin, color, GTC_CACHE); UINT8 *textcolor = 0; switch (ahead) { case 2: textcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLUE, GTC_CACHE); // leading and gaining break; case 1: textcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SKY, GTC_CACHE); // leading and losing break; case -1: textcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_ORANGE, GTC_CACHE); // trailing and gaining break; case -2: textcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_RASPBERRY, GTC_CACHE); // trailing and losing break; } if (!K_UseColorHud()) V_DrawScaledPatch(TIME_X, TIME_Y, splitflags, ((gamemap == 2) ? kp_lapstickerwide[0] : kp_timestickerwide[0])); else //Colourized hud { UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); V_DrawMappedPatch(TIME_X, TIME_Y, splitflags, ((gamemap == 2) ? kp_lapstickerwide[1] : kp_timestickerwide[1]), colormap); } char buffer[32]; snprintf(buffer, 32, "%02i'%02i\"%02i", G_TicsToMinutes(split, true), G_TicsToSeconds(split), G_TicsToCentiseconds(split) ); V_DrawStringScaledEx( (TIME_X + 33) << FRACBITS, (TIME_Y + 3) << FRACBITS, FRACUNIT, FRACUNIT, FRACUNIT, FRACUNIT, splitflags, textcolor, KART_FONT, buffer ); V_DrawStringScaledEx( (TIME_X + 15) << FRACBITS, (TIME_Y + 3) << FRACBITS, FRACUNIT, FRACUNIT, FRACUNIT, FRACUNIT, splitflags, textcolor, KART_FONT, ahead >= 0 ? "-" : "+" ); V_DrawMappedPatch(TIME_X - 8, TIME_Y + 3, splitflags, faceprefix[skin][FACE_MINIMAP], skincolor); } } static void K_drawLapSplitComparison(void) { if (!K_GetRaceSplitsToggle()) { return; } // Draw the timestamp boolean debug_alwaysdrawsplits = false; if (K_doDrawSplits() || debug_alwaysdrawsplits) { INT32 split = stplyr->karthud[khud_splittime]; INT32 skin = stplyr->karthud[khud_splitskin]; INT32 color = stplyr->karthud[khud_splitcolor]; INT32 ahead = stplyr->karthud[khud_splitwin]; INT32 pos = stplyr->karthud[khud_splitposition]; // debug if (!stplyr->karthud[khud_splittimer]) { ahead = ((leveltime/17)%5) - 2; split = leveltime; skin = stplyr->skin; color = stplyr->skincolor; } split = abs(split); UINT8 *skincolor = R_GetTranslationColormap(skin, color, GTC_CACHE); UINT32 textcolor = 0; switch (ahead) { case 2: textcolor = V_BLUEMAP; // leading and gaining break; case 1: textcolor = V_SKYMAP; // leading and losing break; case -1: textcolor = V_ORANGEMAP; // trailing and gaining break; case -2: textcolor = V_REDMAP; // trailing and losing break; } fixed_t row_position[2] = { BASEVIDWIDTH/2 + cv_lapem_xoffset.value, BASEVIDHEIGHT/4 + cv_lapem_yoffset.value }; UINT32 splitflags = V_30TRANS; const char *arrow = (ahead == 1 || ahead == -2) ? "\x1B" : "\x1A"; char buffer[256]; snprintf(buffer, 256, "%s%02i'%02i\"%02i%s", ahead >= 0 ? "-" : "+", G_TicsToMinutes(split, true), G_TicsToSeconds(split), G_TicsToCentiseconds(split), arrow ); INT32 stwidth = V_StringWidth(buffer, splitflags) / 2; // vibes offset V_DrawMappedPatch(row_position[0] - stwidth - 35, row_position[1], splitflags, faceprefix[skin][FACE_MINIMAP], skincolor); if (pos > 1) { V_DrawPingNum(row_position[0] - stwidth - 35, row_position[1], splitflags, pos, NULL); } // vibes offset TWO row_position[0] += 15; V_DrawCenteredString(row_position[0], row_position[1], splitflags|textcolor, buffer); } } void K_drawKartFreePlay(void) { // Doesn't support splitscreens higher than 2 for real estate reasons. if (!LUA_HudEnabled(hud_freeplay)) return; if (modeattacking || grandprixinfo.gp || bossinfo.boss || stplyr->spectator) return; if (!cv_showfreeplay.value) return; if (lt_exitticker < TICRATE/2) return; if (((leveltime-lt_endtime) % TICRATE) < TICRATE/2) return; V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - (12*9), // mirror the laps thingy LAPS_Y+3, V_HUDTRANS|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, "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, max(ping, mindelay), V_HUDTRANS|V_SPLITSCREEN|V_SNAPTOTOP|snap); } static void K_drawMiniPing (void) { UINT32 f = V_SNAPTORIGHT; UINT8 i; if (!cv_showping.value) { return; } for (i = 0; i <= r_splitscreen; i++) { if (stplyr == &players[displayplayers[i]]) { if (r_splitscreen > 1 && !(i & 1)) { f = V_SNAPTOLEFT; } Draw_party_ping(i, f); break; } } } static void K_drawDistributionDebugger(void) { UINT8 useodds = 0; UINT8 pingame = 0, bestbumper = 0; UINT32 pdis = 0; INT32 i; INT32 x = -9, y = -9; //boolean dontforcespb = false; boolean spbrush = false; //if (stplyrnum != 0) // only for p1 //return; // The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; pingame++; //if (players[i].exiting) //dontforcespb = true; if (players[i].bumper > bestbumper) bestbumper = players[i].bumper; } pdis = K_CalculatePDIS(stplyr, pingame, &spbrush); useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush); if (pingame == 1) { if (stplyr->itemroulette && stplyr->cmd.buttons & BT_ATTACK && K_ItemResultEnabled(K_GetKartResult("superring"))) V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(KITEM_SUPERRING, true, 0)); else V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(KITEM_SNEAKER, true, 0)); V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, va("%d", 200)); } else { INT32 itemodds[MAXKARTRESULTS]; kartroulette_t roulette = { .pdis = pdis, .playerpos = stplyr->position, .pos = useodds, .ourDist = stplyr->distancetofinish, .clusterDist = stplyr->distancefromcluster, .mashed = 0, .spbrush = spbrush, .bot = stplyr->bot, .rival = stplyr->bot && stplyr->botvars.rival, .inBottom = K_IsPlayerLosing(stplyr), }; K_KartGetItemOdds(&roulette, itemodds); for (i = 0; i < numkartresults; i++) { kartresult_t *result = &kartresults[i]; if (itemodds[i] <= 0 && roulette.forceme[i] == 0) // At the very least display forced items; that info's also important. continue; V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(result->type, true, 0)); if (result->isalt) V_DrawScaledPatch(x+2, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_getItemAltPatch(true, false)); V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, roulette.forceme[i] ? va("\x85" "FRC(%d)" "\x80 ", roulette.forceme[i]) : va("%d", itemodds[i])); if (result->amount > 1) V_DrawString(x+24, y+31, V_SPLITSCREEN|V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", result->amount)); x += 32; if (x >= 297) { x = -9; y += 32; } } } if (pingame == 1) V_DrawString(0, 0, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, "TA MODE"); else V_DrawString(0, 0, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, va("USEODDS %d", useodds)); V_DrawString(150, 0, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, va("PDIS %u", pdis)); if (K_LegacyOddsMode()) V_DrawSmallString(70, 0, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, "Legacy Distance Mode"); // cooldown timer debugging x = 240; y = 160; for (i = 0; i < numkartresults; i++) { kartresult_t *result = &kartresults[i]; if (result->cooldown > 0 && result->isalt == K_IsKartItemAlternate(result->type)) { INT32 color = result->flags & KRF_INDIRECTITEM ? V_ORANGEMAP : K_GetItemFlags(result->type) & KIF_UNIQUE ? V_YELLOWMAP : 0; V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOBOTTOM, K_GetCachedItemPatch(result->type, true, 0)); if (result->amount > 1) V_DrawThinString(x+30, y+30, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOBOTTOM|V_6WIDTHSPACE, va("x%d", result->amount)); V_DrawString(x+11, y+31, V_HUDTRANS|V_SNAPTOBOTTOM|color, va("%u", result->cooldown/TICRATE)); x -= 32; } } } static void K_drawCheckpointDebugger(void) { if (stplyrnum != 0) // only for p1 return; if (stplyr->starpostnum >= K_CheckpointThreshold(true)) V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Can finish)", stplyr->starpostnum, numstarposts)); else if (K_CheckpointThreshold(false) != numstarposts) V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Skip: %d)", stplyr->starpostnum, numstarposts, K_CheckpointThreshold(false) + stplyr->starpostnum)); else V_DrawString(8, 184, 0, va("Checkpoint: %d / %d", stplyr->starpostnum, numstarposts)); if (K_UsingLegacyCheckpoints()) V_DrawString(8, 192, 0, va("Waypoint dist: Prev %d, Next %d", stplyr->prevcheck, stplyr->nextcheck)); } static void K_DrawWaypointDebugger(void) { if (cv_kartdebugwaypoints.value == 0) return; if (stplyrnum != 0) // only for p1 return; if (stplyr->bigwaypointgap) V_DrawString(8, 140, 0, va("Auto Respawn Timer: %d", stplyr->bigwaypointgap)); V_DrawString(8, 150, 0, va("Current Waypoint ID: %d", K_GetWaypointID(stplyr->currentwaypoint))); V_DrawString(8, 160, 0, va("Next Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint))); V_DrawString(8, 170, 0, va("Finishline Distance: %u (WP %u)", stplyr->distancetofinish, stplyr->currentwaypoint ? stplyr->currentwaypoint->distancetofinish : 0)); } static void K_DrawClusterDebugger(void) { if (cv_kartdebugcluster.value == 0) return; if (stplyrnum != 0) // only for p1 return; INT32 vflags = V_6WIDTHSPACE|V_ALLOWLOWERCASE; if (K_LegacyOddsMode()) { V_DrawThinString(8, 136, vflags, va("Cluster player: %s", player_names[clusterid])); V_DrawThinString(8, 146, vflags, va("X: %f, Y: %f, Z: %f, Dist. from cluster: %d", FIXED_TO_FLOAT(clusterpoint.x), FIXED_TO_FLOAT(clusterpoint.y), FIXED_TO_FLOAT(clusterpoint.z), stplyr->distancefromcluster)); } else { V_DrawThinString(8, 126, vflags, va("Cluster player: %s", player_names[clusterid])); V_DrawThinString(8, 136, vflags, va("Cluster DtF: %d, Your DtF: %d", (UINT32)(clusterdtf.x), stplyr->distancetofinish)); V_DrawThinString(8, 146, vflags, va("Distance from Cluster: %d", stplyr->distancefromcluster)); } } void K_getSlipstreamDrawinfo(drawinfo_t *out) { INT32 fx, fy, splitflags = 0; if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = DRAT_X; fy = DRAT_Y; splitflags = V_SPLITSCREEN; if (r_splitscreen > 0) splitflags |= V_30TRANS; } else // now we're having a fun game. { if (stplyrnum == 0 || stplyrnum == 2) // If we are P1 or P3... { fx = DRAT_X; fy = DRAT_Y; splitflags = V_30TRANS|V_SPLITSCREEN; } else // else, that means we're P2 or P4. { fx = DRAT2_X; fy = DRAT2_Y; splitflags = V_30TRANS|V_SPLITSCREEN; } } out->x = fx; out->y = fy; out->flags = splitflags; } static void K_SlipstreamIndicator(boolean tiny) { if (!K_DraftingActive()) return; if (!cv_draftindicator.value) return; const char *fullstr = "DRAFTING"; char str[256] = {0}; SINT8 stringlen = strlen(fullstr); SINT8 len = min(stplyr->draftpower / (FRACUNIT / stringlen), stringlen); INT32 flags; INT32 fx, fy; drawinfo_t info; K_getSlipstreamDrawinfo(&info); fx = info.x; fy = info.y; flags = info.flags; if (!len) return; strncpy(str, fullstr, len); if (stplyr->draftpower >= FRACUNIT) { const INT32 clr = skincolors[stplyr->skincolor].chatcolor; flags |= clr; } if (tiny) V_DrawSmallString(fx - V_StringWidth(str, flags)/2, fy, flags, str); else V_DrawThinString(fx - V_StringWidth(str, flags)/2, fy, flags, str); } void K_drawKartHUD(void) { boolean islonesome = false; boolean battlefullscreen = false; UINT8 viewnum = R_GetViewNumber(); boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam UINT8 i; // 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". for (i = 0; i <= r_splitscreen; i++) { if (stplyr == &players[displayplayers[i]] && !camera[i].chase && !freecam) K_drawKartFirstPerson(); } // Draw full screen stuff that turns off the rest of the HUD if (mapreset && stplyrnum == 0) { K_drawChallengerScreen(); return; } battlefullscreen = ((gametyperules & (GTR_BUMPERS|GTR_KARMA)) == (GTR_BUMPERS|GTR_KARMA) && (stplyr->exiting || (stplyr->bumper <= 0 && stplyr->karmadelay > 0 && !(stplyr->pflags & PF_ELIMINATED) && comeback == true && stplyr->playerstate == PST_LIVE))); if (!demo.title && (!battlefullscreen || r_splitscreen)) { // Draw the CHECK indicator before the other items, so it's overlapped by everything else if (LUA_HudEnabled(hud_check)) // delete lua when? if (cv_kartcheck.value && !splitscreen && !players[displayplayers[0]].exiting && !freecam) K_drawKartPlayerCheck(); // nametags if (LUA_HudEnabled(hud_names)) K_drawKartNameTags(); // Draw WANTED status if (gametyperules & GTR_WANTED) { if (LUA_HudEnabled(hud_wanted)) K_drawKartWanted(); } if (cv_kartminimap.value) { if (LUA_HudEnabled(hud_minimap)) K_drawKartMinimap(); } } // Drift gauge should ideally be drawn behind other hud stuff, right? // right? K_DrawDriftGauge(); // new flame shield bars (player-space) K_DrawFlamometer(); if (battlefullscreen && !freecam) { if (LUA_HudEnabled(hud_battlefullscreen)) K_drawBattleFullscreen(); return; } // Draw the item window if (LUA_HudEnabled(hud_item) && !freecam) K_drawKartItem(); // If not splitscreen, draw... if (!r_splitscreen && !demo.title) { // Draw the timestamp // this branching code sucks now gotta fix later :,) if (LUA_HudEnabled(hud_time)) { if (!(K_GetRaceSplitsToggle() || K_doDrawSplits())) { K_drawKartTimestamp(stplyr->realtime, TIME_X, TIME_Y, gamemap, 0); } else { // Draw splits if (cv_lapemblemmode.value) { if (K_doDrawSplits()) K_drawLapSplitTimestamp(); else K_drawKartTimestamp(stplyr->realtime, TIME_X, TIME_Y, gamemap, 0); } else { K_drawKartTimestamp(stplyr->realtime, TIME_X, TIME_Y, gamemap, 0); // mine: moved this here to avoid redundant comparisions K_drawLapSplitComparison(); } } } islonesome = K_drawKartPositionFaces(); } if (!stplyr->spectator && !freecam) // Bottom of the screen elements, don't need in spectate mode { if (demo.title) // Draw title logo instead in demo.titles { INT32 x = (BASEVIDWIDTH - 32), y = 128, snapflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; if (r_splitscreen == 3) { x = BASEVIDWIDTH/2 + 10; y = BASEVIDHEIGHT/2 - 30; snapflags = 0; } V_DrawTinyScaledPatch(x-54, y, snapflags|V_SLIDEIN, W_CachePatchName("TTKBANNR", PU_CACHE)); V_DrawTinyScaledPatch(x-54, y+25, snapflags|V_SLIDEIN, W_CachePatchName("TTKART", PU_CACHE)); } else { if (LUA_HudEnabled(hud_position)) { if (bossinfo.boss) { K_drawBossHealthBar(); } else if (gametyperules & GTR_CIRCUIT) // Race-only elements { if (!islonesome) { // Draw the numerical position K_DrawKartPositionNum(stplyr->position); } } } if (LUA_HudEnabled(hud_gametypeinfo)) { if (gametyperules & GTR_CIRCUIT) { K_drawKartLaps(); K_drawKartStatsnLives(); } else if (gametyperules & GTR_BUMPERS) { K_drawKartBumpersOrKarma(); } } // Draw the speedometer and/or accessibility icons if (cv_kartspeedometer.value && !r_splitscreen && (LUA_HudEnabled(hud_speedometer))) { K_drawKartSpeedometer(); } else { K_drawKartAccessibilityIcons((cv_newspeedometer.value == 0 || cv_newspeedometer.value == 2) ? 50 : 0); } if (LUA_HudEnabled(hud_rings) && K_RingsActive() == true) { K_drawRingMeter(); } if (cv_showinput.value > 0) { // Draw the input UI if (LUA_HudEnabled(hud_position)) K_drawInput(); } } } // Draw the countdowns after everything else. if (!(gametyperules & GTR_NOCOUNTDOWN) && 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_DrawKartString((BASEVIDWIDTH/2)-karlen, LAPS_Y+3, V_SPLITSCREEN, countstr); } } // Race overlays if ((gametyperules & GTR_CIRCUIT) && !freecam) { if (stplyr->exiting) K_drawKartFinish(); else if (stplyr->karthud[khud_lapanimation] && !r_splitscreen) K_drawLapStartAnim(); } K_DisplayItemTimers(); K_SlipstreamIndicator(r_splitscreen > 1); if (modeattacking || freecam) // everything after here is MP and debug only return; // TODO: Make this slide like the other titlecards if ((gametyperules & GTR_KARMA) && !r_splitscreen && stplyr->karthud[khud_yougotem]) // * YOU GOT EM * V_DrawScaledPatch(BASEVIDWIDTH/2 - (kp_yougotem->width/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. if (islonesome) K_drawKartFreePlay(); if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1) && !stplyr->exiting) { V_DrawCenteredString(BASEVIDWIDTH>>1, cv_itemtimers.value && !modeattacking ? 191 : 179, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY"); } if ((netgame || cv_mindelay.value) && r_splitscreen && Playing()) { K_drawMiniPing(); } if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); if (cv_kartdebugcheckpoint.value) K_drawCheckpointDebugger(); 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, c, GTC_CACHE); V_DrawFixedPatch(x<>1, 0, faceprefix[stplyr->skin][FACE_WANTED], cm); x += 16; if (x > BASEVIDWIDTH-16) { x = 0; y += 16; } } } } K_DrawWaypointDebugger(); K_DrawClusterDebugger(); K_DrawDirectorDebugger(); if (stplyrnum == 0) K_DrawBotDebugger(stplyr); } // Thank you Haya.... // DRIFT GAUGE // static INT32 driftskins[] = { SKINCOLOR_BLACK, SKINCOLOR_SILVER, SKINCOLOR_BLUEBELL, SKINCOLOR_RASPBERRY, SKINCOLOR_VIOLET, }; enum driftgauge_e { DGAUGE_NONE = 0, DGAUGE_SPEE, DGAUGE_ACHII, DGAUGE_WIFI, DGAUGE_CHAOTIX, DGAUGE_NUMBERS, DGAUGE_LEGACY, DGAUGE_LEGACYSMALL, DGAUGE_LEGACYLARGE, DGAUGE_LEGACYNUMBERS, }; typedef struct { UINT8 patchnum; INT16 barx, bary; INT16 textx, texty; INT16 barwidth; INT32 meterfont; boolean legacy; } driftgauge_t; static UINT8 legacydriftcolors[5][4] = { { 23, 23, 23, 23}, // back { 1, 1, 10, 16}, // white { 133, 133, 172, 197}, // blue { 33, 33, 215, 71}, // red { 161, 161, 196, 198}, // purple }; static UINT8 driftcolors[5][4] = { { 24, 20, 24}, // back (extracted directly from generated TC_RAINBOW + SKINCOLOR_BLACK colormap) { 9, 2, 9}, // white { 135, 130, 135}, // blue { 34, 32, 34}, // red { 163, 161, 163}, // purple }; static driftgauge_t driftgauges[] = { [DGAUGE_SPEE] = { .patchnum = 0, .barx = -22, .bary = 2, .textx = 3, .texty = 6, .barwidth = 46, .meterfont = OPPRNK_FONT, }, [DGAUGE_ACHII] = { .patchnum = 1, .barx = -22, .bary = 2, .textx = 3, .texty = 6, .barwidth = 46, .meterfont = OPPRNK_FONT, }, [DGAUGE_WIFI] = { .patchnum = 2, .barx = 6 + 2, .bary = -8 + 22, .textx = 30, .texty = 10, .barwidth = 1, // special-cased .meterfont = PINGNUM_FONT, }, [DGAUGE_CHAOTIX] = { .patchnum = 4, .barx = -23, .bary = -7, .textx = -18, .texty = -8, .barwidth = 34, .meterfont = OPPRNK_FONT, }, [DGAUGE_NUMBERS] = { .textx = 0, .texty = 0, .meterfont = OPPRNK_FONT, }, [DGAUGE_LEGACY] = { .patchnum = 5, .barx = -23, .bary = -1, .textx = 30, .texty = 0, .barwidth = 46, .meterfont = PINGNUM_FONT, .legacy = true, }, [DGAUGE_LEGACYSMALL] = { .patchnum = 6, .barx = -23, .bary = -1, .textx = 20, .texty = 0, .barwidth = 23, .meterfont = PINGNUM_FONT, .legacy = true, }, [DGAUGE_LEGACYLARGE] = { .patchnum = 5, .barx = -23, .bary = -1, .textx = 30, .texty = 0, .barwidth = 46, .meterfont = TALLNUM_FONT, .legacy = true, }, [DGAUGE_LEGACYNUMBERS] = { .textx = 10, .texty = 0, .meterfont = TALLNUM_FONT, }, }; static UINT8 driftrainbow[] = { 0, 32, 48, 64, 72, 80, 88, 96, 112, 120, 128, 144, 160, 176, 192, 200, 208, 216, 224, 240 }; static UINT8 wifibars[] = { 6, 11, 16, 21 }; // Based off https://github.com/GenericHeroGuy/ringracers-scripts/blob/master/src/sglua/Lua/HUD/driftgauge.lua // Original script by GenericHeroGuy, graphics by Spee void K_DrawDriftGauge(void) { // Actually have it enabled? if (!cv_driftgauge.value) return; // Not in RA if (modeattacking != ATTACKING_NONE) return; // Make sure we actually have one, lmao if (stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) return; // Check for chasecam // TODO: Check for this better ffs if (!cv_chasecam[R_GetViewNumber()].value) return; // I WANT TO LIVE if (stplyr->playerstate != PST_LIVE) return; mobj_t *mo = stplyr->mo; fixed_t gaugeofs = FixedMul(cv_driftgaugeoffset.value, ((cv_driftgaugeoffset.value > 0) ? mo->scale : mapobjectscale)); vector3_t pos = { R_InterpolateFixed(mo->old_x, mo->x) + mo->sprxoff, R_InterpolateFixed(mo->old_y, mo->y) + mo->spryoff, R_InterpolateFixed(mo->old_z, mo->z) + mo->sprzoff + (mo->eflags & MFE_VERTICALFLIP ? mo->height - gaugeofs : gaugeofs), }; trackingResult_t res; INT32 basex, basey, i = 0; INT32 flags = V_SPLITSCREEN; // V_HUDTRANS does not like other transparent effects... K_ObjectTracking(&res, &pos, false); basex = res.x; basey = res.y; driftgauge_t *gauge = &driftgauges[cv_driftgauge.value]; // Deal with Wifi-Style when no purps SINT8 holdupoffset = ((cv_driftgauge.value == DGAUGE_WIFI) && K_PurpleDriftActive()) + (K_UseColorHud() ? 1 : 0); patch_t *basepatch = gauge->barwidth ? kp_driftgauge[gauge->patchnum + (!!K_UseColorHud() * 6) + holdupoffset] : NULL; fixed_t textx = basex + gauge->textx*FRACUNIT; fixed_t texty = basey + gauge->texty*FRACUNIT; // TODO: fix the patch offsets if (cv_driftgauge.value == DGAUGE_LEGACYSMALL) basex += 10*FRACUNIT; // TODO: fix wifi offsets if (cv_driftgauge.value == DGAUGE_WIFI) basex -= 10*FRACUNIT; INT32 driftval = K_GetKartDriftSparkValue(stplyr); INT32 driftcharge = min(driftval*4, stplyr->driftcharge); boolean rainbow = driftcharge >= driftval*4; const UINT8 level = K_GetKartDriftSparkStageForValue(stplyr, driftcharge) + 1; const UINT8 backlevel = rainbow ? 4 : max(0, level - 1); UINT8 *cmap = R_GetTranslationColormap(TC_RAINBOW, rainbow ? (INT32)(1 + (leveltime % FIRSTSUPERCOLOR)) : driftskins[level], GTC_CACHE); INT32 afterimage = stplyr->karthud[khud_afterimagetime]; if (!stplyr->drift) { if (afterimage) goto doafterimage; else return; } if (basepatch) { UINT8 *backcmap, *basecmap = K_UseColorHud() ? R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE) : NULL; UINT8 frontcolors[4], backcolors[4]; // the base graphic V_DrawFixedPatch(basex, basey, FRACUNIT, flags|V_HUDTRANS, basepatch, basecmap); if (rainbow) { INT32 backsize = sizeof(backcolors); backcmap = cmap; for (i = 0; i < backsize; i++) backcolors[i] = driftrainbow[leveltime % sizeof(driftrainbow)] + i; // HOT HOT HOT HOT HOOOOOOOT AAAAIIIIIIIIEEEEEEEEEEEEEEEEE UINT8 trans = abs(FINESINE((leveltime*ANGLE_22h)>>ANGLETOFINESHIFT)/(4*FRACUNIT/10)); V_DrawFixedPatch(basex, basey, FRACUNIT, flags|(V_90TRANS - V_10TRANS*trans), basepatch, R_GetTranslationColormap(TC_BLINK, SKINCOLOR_RED, GTC_CACHE)); } else { UINT8 (*barcolors)[4] = gauge->legacy ? legacydriftcolors : driftcolors; memcpy(frontcolors, barcolors[level], sizeof(frontcolors)); if (backlevel == 0 && K_UseColorHud()) { backcmap = R_GetTranslationColormap(TC_RAINBOW, stplyr->skincolor, GTC_CACHE); if (gauge->legacy) memset(backcolors, skincolors[K_GetHudColor()].ramp[7], sizeof(backcolors)); else for (i = 0; i < 3; i++) backcolors[i] = skincolors[K_GetHudColor()].ramp[i == 1 ? 9 : 12]; } else { memcpy(backcolors, barcolors[backlevel], sizeof(backcolors)); backcmap = R_GetTranslationColormap(TC_RAINBOW, driftskins[backlevel], GTC_CACHE); } } const fixed_t barx = basex + gauge->barx*FRACUNIT; const fixed_t bary = basey + gauge->bary*FRACUNIT; const fixed_t barwidth = (cv_driftgauge.value == DGAUGE_WIFI ? wifibars[backlevel] : gauge->barwidth)*FRACUNIT; const INT32 chargewidth = FixedMul(barwidth, FixedDiv( driftcharge - K_GetKartDriftSparkValueForStage(stplyr, backlevel), // charge remaining K_GetKartDriftSparkValueForStage(stplyr, level) - K_GetKartDriftSparkValueForStage(stplyr, backlevel) // stage total )); if (cv_driftgauge.value == DGAUGE_WIFI) { SINT8 levels = K_PurpleDriftActive() ? 4 : 3; for (i = 0; i < levels; i++) { fixed_t h = backlevel < i ? 0 : backlevel > i ? wifibars[i]*FRACUNIT : chargewidth; V_SetClipRect(barx + (i*5)*FRACUNIT, bary-h, 4*FRACUNIT, wifibars[i]*FRACUNIT, flags); V_DrawFixedPatch(basex, basey, FRACUNIT, flags, kp_driftgaugeparts[i], cmap); } } else if (cv_driftgauge.value == DGAUGE_CHAOTIX) { // back if (backlevel) V_DrawFixedPatch(basex, basey, FRACUNIT, flags, kp_driftgaugeparts[4], backcmap); // front V_SetClipRect(barx, bary, chargewidth, 18*FRACUNIT, flags); V_DrawFixedPatch(basex, basey, FRACUNIT, flags, kp_driftgaugeparts[4], cmap); } else // Meter style { for (i = 0; i < (gauge->legacy ? 4 : 3); i++) { fixed_t ofs = gauge->legacy ? 0 : (i & 1)*FRACUNIT; fixed_t x = barx; fixed_t y = bary + i*FRACUNIT; fixed_t w = chargewidth; fixed_t h = FRACUNIT; // seamlessly clipped bars! V_SetClipRect(x + max(ofs, w), y, (barwidth - max(ofs, w)) - ofs, h, flags); V_DrawFixedFill(x, y, barwidth, h, flags|V_HUDTRANS|backcolors[i]); V_SetClipRect(x + ofs, y, min(w - ofs, barwidth - ofs*2), h, flags); V_DrawFixedFill(x, y, barwidth, h, flags|V_HUDTRANS|frontcolors[i]); } } V_ClearClipRect(); } doafterimage:; // right, also draw a cool number INT32 charge = (afterimage ? stplyr->karthud[khud_afterimagevalue] : driftcharge)*100 / driftval; if (afterimage) { flags |= V_TRANSLUCENT*2 - (V_10TRANS * afterimage); cmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_CACHE); } if (gauge->meterfont == OPPRNK_FONT) { UINT8 numbers[3]; numbers[0] = ((charge / 100) % 10); numbers[1] = ((charge / 10) % 10); numbers[2] = (charge % 10); V_DrawFixedPatch(textx, texty, FRACUNIT, flags, kp_facenum[numbers[0]], cmap); V_DrawFixedPatch(textx+(6*FRACUNIT), texty, FRACUNIT, flags, kp_facenum[numbers[1]], cmap); V_DrawFixedPatch(textx+(12*FRACUNIT), texty, FRACUNIT, flags, kp_facenum[numbers[2]], cmap); } else if (gauge->meterfont == PINGNUM_FONT) { // TODO pad with zeroes(?) V_DrawPingNumAtFixed(textx, texty, flags, charge, cmap); } else if (gauge->meterfont == TALLNUM_FONT) { V_DrawPaddedTallColorNumAtFixed(textx, texty, flags, charge, 3, cmap); } /*V_DrawStringScaledEx( textx, texty, FRACUNIT, FRACUNIT, FRACUNIT, FRACUNIT, flags|V_MONOSPACE, cmap, meterfont, va("%03d", charge) );*/ } void K_DrawFlamometer(void) { // Make sure we actually have one, lmao if (stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) return; // Check for chasecam // TODO: Check for this better ffs if (!cv_chasecam[R_GetViewNumber()].value) return; // I WANT TO LIVE if (stplyr->playerstate != PST_LIVE) return; if (stplyr->exiting) return; if (stplyr->flametimer <= 0) return; mobj_t *mo = stplyr->mo; vector3_t pos = { R_InterpolateFixed(mo->old_x, mo->x) + mo->sprxoff, R_InterpolateFixed(mo->old_y, mo->y) + mo->spryoff, R_InterpolateFixed(mo->old_z, mo->z) + mo->sprzoff + (mo->eflags & MFE_VERTICALFLIP ? mo->height : 0), }; trackingResult_t res; INT32 basex, basey, barextraflags = 0; INT32 flags = V_SPLITSCREEN|V_HUDTRANS; INT32 fuelbarheight = 41; INT32 tempbarheight = 49; UINT8 *colormap = NULL; K_ObjectTracking(&res, &pos, false); basex = res.x + (16<flamestore >= FLAMESTOREMAX-1) { UINT8 flamofiretic = ((leveltime / 3) % MAXFLAMOFIRETICS) + 1; if (stplyr->flameoverheat < 3) { // Fancy "explode" VFX flamofiretic = 0; } if (K_UseColorHud()) { flamofiretic += 9; } V_DrawFixedPatch(basex, basey, FRACUNIT, flags|V_ADD, kp_flamefire[flamofiretic], colormap); if (leveltime % 3 != 0) { barextraflags = V_ADD; } } // back V_DrawFixedPatch(basex, basey, FRACUNIT, flags, kp_flamometer[K_UseColorHud() ? 4 : 0], colormap); // bars // fuel fuelbarheight *= FixedDiv(stplyr->flametimer, itemtime*3); V_SetClipRect(basex + 7*FRACUNIT, basey + 58*FRACUNIT - fuelbarheight, 4*FRACUNIT, fuelbarheight, flags); V_DrawFixedPatch(basex, basey, FRACUNIT, flags, kp_flamometer[1], NULL); V_ClearClipRect(); // temperature tempbarheight *= FixedDiv(stplyr->flamestore, FLAMESTOREMAX); V_SetClipRect(basex + 10*FRACUNIT, basey + 62*FRACUNIT - tempbarheight, 32*FRACUNIT, tempbarheight, flags); V_DrawFixedPatch(basex, basey, FRACUNIT, flags|barextraflags, kp_flamometer[2], NULL); V_ClearClipRect(); // front V_DrawFixedPatch(basex, basey, FRACUNIT, flags, kp_flamometer[K_UseColorHud() ? 5 : 3], colormap); }