blankart/src/k_hud.c
minenice55 bc26ad20c1 tune tilt controls to not feel as touchy
needed after fixing that bug that made em feel stiffer than they were supposed to since the old value was to counteract that
2026-04-25 01:18:11 -04:00

6919 lines
188 KiB
C

// 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 "command.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 "g_input.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);
typedef enum
{
NT_OFF = 0,
NT_DEFAULT,
NT_SMALL,
NT_TEXT,
} nametag_e;
static CV_PossibleValue_t nametag_cons_t[]= {
{NT_OFF, "Off"},
{NT_DEFAULT, "Default"},
{NT_SMALL, "Small"},
{NT_TEXT, "Text-Only"},
{0, NULL}
};
consvar_t cv_seenames = CVAR_INIT ("seenames", "Default", CV_SAVE, nametag_cons_t, NULL);
consvar_t cv_seenamerestat = CVAR_INIT ("seenamerestat", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_seeownname = CVAR_INIT ("seeownname", "Off", 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;
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 S-Monitor sparkles
static patch_t *kp_smonitorsparkle;
static patch_t *kp_rankbumper;
static patch_t *kp_tinybumper[2];
static patch_t *kp_ranknobumpers;
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;
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;
static patch_t *kp_inputwheel_shadow;
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;
patch_t *kp_itemtarget_arrow[2][2];
patch_t *kp_itemtarget_icon[2];
patch_t *kp_itemtarget_far[2][2];
patch_t *kp_itemtarget_far_text[2];
patch_t *kp_itemtarget_near[2][8];
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_seenames);
CV_RegisterVar(&cv_seenamerestat);
CV_RegisterVar(&cv_seeownname);
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);
// k_hud_track.cpp
CV_RegisterVar(&cv_kartdebughudtracker);
}
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");
// S-Monitor. Sparkles
HU_UpdatePatch(&kp_smonitorsparkle, "ALTISPRK");
kp_smonitorsparkle->pivot.x = kp_smonitorsparkle->width / 2;
kp_smonitorsparkle->pivot.y = kp_smonitorsparkle->height / 2;
kp_smonitorsparkle->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
HU_UpdatePatch(&kp_inputwheel, "K_WHEEL0");
kp_inputwheel->pivot.x =
(kp_inputwheel->width / 2) + kp_inputwheel->leftoffset;
kp_inputwheel->pivot.y =
(kp_inputwheel->height / 2) + kp_inputwheel->topoffset;
kp_inputwheel->alignflags |=
(PATCHALIGN_AUTOCENTER | PATCHALIGN_USEPIVOTS);
HU_UpdatePatch(&kp_inputwheel_shadow, "K_WHEEL1");
kp_inputwheel_shadow->pivot.x =
(kp_inputwheel_shadow->width / 2) + kp_inputwheel_shadow->leftoffset;
kp_inputwheel_shadow->pivot.y =
(kp_inputwheel_shadow->height / 2) + kp_inputwheel_shadow->topoffset;
kp_inputwheel_shadow->alignflags |=
(PATCHALIGN_AUTOCENTER | PATCHALIGN_USEPIVOTS);
// 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);
}
sprintf(buffer, "HITEARxx");
for (i = 0; i < 2; i++)
{
buffer[6] = 'A'+i;
for (j = 0; j < 2; j++)
{
buffer[7] = '0'+j;
HU_UpdatePatch(&kp_itemtarget_arrow[i][j], "%s", buffer);
}
}
sprintf(buffer, "HUDITEDx");
for (i = 0; i < 2; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_itemtarget_far_text[i], "%s", buffer);
}
sprintf(buffer, "HUDITECx");
for (i = 0; i < 2; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_itemtarget_icon[i], "%s", buffer);
}
sprintf(buffer, "HUDITEBx");
for (i = 0; i < 2; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_itemtarget_far[0][i], "%s", buffer);
}
sprintf(buffer, "HUDI4PBx");
for (i = 0; i < 2; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_itemtarget_far[1][i], "%s", buffer);
}
sprintf(buffer, "HUDITEAx");
for (i = 0; i < 8; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_itemtarget_near[0][i], "%s", buffer);
}
sprintf(buffer, "HUDI4PAx");
for (i = 0; i < 8; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_itemtarget_near[1][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;
boolean fade = 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
{
// S-Monitor nonsense necessitates we do this ahead of time
boolean candrawitemstatus = true;
if (stplyr->itemamount <= 0 && stplyr->itemusecooldown <= 0)
candrawitemstatus = false;
if (stplyr->itemtype == 0 && stplyr->itemusecooldown <= 0)
candrawitemstatus = false;
if (stplyr->itemtype >= numkartitems && stplyr->itemtype != MAXKARTITEMS)
candrawitemstatus = false;
// 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->smonitortimer && (!candrawitemstatus))
{
if (stplyr->smonitortimer < SMONITORTIME)
itembar = FixedDiv(stplyr->smonitortimer, max(1, stplyr->maxsmonitortime));
if (stplyr->smonitorcancel > 0)
flamebar = FixedDiv(stplyr->smonitorcancel, 26);
if (leveltime & 1)
{
localpatch = K_GetCachedItemPatchEx(KITEM_INVINCIBILITY, tiny, 0, 1);
}
else
localpatch = kp_nodraw;
if (!K_SMonitorSlotHogging(stplyr))
{
fade = true;
}
}
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->attractioncharge > 0)
{
if (leveltime & 2)
{
if (stplyr->attractioncharge >= ATTRACTIONCHARGETIME)
{
colormode = TC_BLINK;
localcolor = SKINCOLOR_WHITE;
}
localpatch = K_GetCachedItemPatch(KITEM_THUNDERSHIELD, tiny, 0);
}
else
{
localpatch = kp_nodraw;
}
itembar = FixedDiv(stplyr->attractioncharge, ATTRACTIONCHARGETIME);
}
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 (!candrawitemstatus)
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 = (fade) ? V_HUDTRANSHALF : V_HUDTRANS;
INT32 boxtransflag = 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;
INT32 boxtrans = cv_rouletteplayerboxtrans.value;
// RadioRacers
if (shouldDrawOnPlayer)
{
boolean rocketsmonitorbar = ((stplyr->rocketsneakertimer > 1) || (stplyr->smonitortimer));
/**
* 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, the S-Monitor 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 || (rocketsmonitorbar)) {
roulettetrans = (INT32)((float)(roulettetrans) * (0.57143f * ((fade) ? 0.5f : 1.0f)));
}
if (stplyr->exiting)
{
roulettetrans = -1;
boxtrans = -1;
}
if (roulettetrans > -1)
{
transflag = max(0, min(9, 10 - FixedMul(roulettetrans, transmul))) << V_ALPHASHIFT;
}
else
{
transflag = (fade) ? V_HUDTRANSHALF : V_HUDTRANS;
}
if (boxtrans > -1)
{
boxtransflag = max(0, min(9, 10 - FixedMul(boxtrans, transmul))) << V_ALPHASHIFT;
}
else
{
boxtransflag = V_HUDTRANS;
}
}
fx = info.x;
fy = info.y;
fflags = info.flags;
flipamount = info.flipamount;
INT32 localpatchflags = (transflag|fflags);
INT32 boxpatchflags = (boxtransflag|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, boxpatchflags, 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))<<FRACBITS, (fy<<FRACBITS), info.hudScale, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), localbg, colormap); // flip this graphic for p2 and p4 in split and shift it.
V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, info.hudScale, localpatchflags, localpatch, colmap);
// draw minecraft-style cooldown
K_DrawItemCooldown(fx, fy, info.hudScale, V_HUDTRANSHALF|fflags, stplyr->itemusecooldown, stplyr->itemusecooldownmax);
if (isalt)
V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, info.hudScale, localpatchflags, K_getItemAltPatch(tiny, true), colmap);
if (tiny)
{
if (flipamount) // reminder that this is for 3/4p's right end of the screen.
V_DrawString(fx+2, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->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<<FRACBITS;
INT32 fancyflags = V_HUDTRANS|fflags;
INT32 cvarhudtrans = cv_translucenthud.value;
INT32 animlength = cv_fancyroulettespeed.value;
INT32 alpha = 0;
INT32 shift = tiny ? 16 : 32;
fixed_t shiftprog;
fixed_t timefracs = (stplyr->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)|timefracs, animlength<<FRACBITS) % FRACUNIT);
alpha = (FixedMul((abs(shiftprog) * 10), FixedDiv(cvarhudtrans << FRACBITS, 10 << FRACBITS)) >> FRACBITS)+(10-cvarhudtrans);
rfy = (fy<<FRACBITS) + FixedMul(shift*shiftprog, info.hudScale);
if (0 > alpha) alpha = 0;
if (alpha >= 10) alpha = 10;
fancyflags = (alpha<<V_ALPHASHIFT)|fflags;
baseitem = K_GetRollingRouletteItemOffset(stplyr, rouletteshift, animlength);
localpatch = K_GetCachedItemPatch(baseitem, tiny, 0);
V_DrawFixedPatch(fx<<FRACBITS, rfy, info.hudScale, fancyflags, localpatch, colmap);
}
V_ClearClipRect();
// draw minecraft-style cooldown
K_DrawItemCooldown(fx, fy, V_HUDTRANSHALF|fflags, stplyr->itemusecooldown, stplyr->itemusecooldownmax, info.hudScale);
}
else
{
V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, info.hudScale, localpatchflags, localpatch, colmap);
// draw minecraft-style cooldown
K_DrawItemCooldown(fx, fy, V_HUDTRANSHALF|fflags, stplyr->itemusecooldown, stplyr->itemusecooldownmax, info.hudScale);
if (isalt)
V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, info.hudScale, localpatchflags, K_getItemAltPatch(tiny, false), colmap);
}
// Extensible meter, currently used by Grow, Rocket Sneakers, Flame Shield, and the S-Monitor
// ...aren't you forgetting something?
if (itembar != -1)
K_DrawItemBar(fx, fy, info.hudScale, fflags|V_HUDTRANS, itembar, (UINT8 []){0, 8, 12});
if (flamebar != -1)
K_DrawItemBar(fx, fy - FixedMul(8, info.hudScale), info.hudScale, fflags|V_HUDTRANS, flamebar, (UINT8 []){51, 36, 55});
// Quick Eggman numbers
if (stplyr->eggmanexplode > 1)
{
V_DrawSciencePatch((fx<<FRACBITS) + (17 * info.hudScale),
(fy<<FRACBITS) + (13 * info.hudScale) - (tiny ? FRACUNIT : 0),
V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->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<<FRACBITS, ((TY+3)<<FRACBITS)+jitter, splitflags, va("%d", worktime/10));
V_DrawKartStringAtFixed((TX+12)<<FRACBITS, ((TY+3)<<FRACBITS)-jitter, splitflags, va("%d", worktime%10));
// apostrophe location _'__ __
V_DrawKartString(TX+24, TY+3, splitflags, va("'"));
worktime = (drawtime/TICRATE % 60);
// seconds time _ 00 __
V_DrawKartStringAtFixed((TX+36)<<FRACBITS, ((TY+3)<<FRACBITS)+jitter, splitflags, va("%d", worktime/10));
V_DrawKartStringAtFixed((TX+48)<<FRACBITS, ((TY+3)<<FRACBITS)-jitter, splitflags, va("%d", worktime%10));
// quotation mark location _ __"__
V_DrawKartString(TX+60, TY+3, splitflags, va("\""));
worktime = G_TicsToCentiseconds(drawtime);
// tics _ __ 00
V_DrawKartStringAtFixed((TX+72)<<FRACBITS, ((TY+3)<<FRACBITS)+jitter, splitflags, va("%d", worktime/10));
V_DrawKartStringAtFixed((TX+84)<<FRACBITS, ((TY+3)<<FRACBITS)-jitter, splitflags, va("%d", worktime%10));
}
if ((modeattacking || (mode == 1)) && G_EmblemsEnabled()) // emblem time!
{
INT32 workx = TX + 96, worky = TY+18;
SINT8 curemb = 0;
patch_t *emblempic[3] = {NULL, NULL, NULL};
UINT8 *emblemcol[3] = {NULL, NULL, NULL};
emblem_t *emblem = M_GetLevelEmblems(emblemmap);
while (emblem)
{
char targettext[9];
switch (emblem->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 = 0;
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, scale)>>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<<FRACBITS, fy<<FRACBITS, scale, addOrSub|fflags, kp_positionnum[0][0], NULL);
return;
}
// Draw the number
while (num)
{
if (win) // 1st place winner? You get rainbows!!
{
localpatch = kp_winnernum[(leveltime % (NUMWINFRAMES*3)) / 3];
}
else if (stplyr->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<<FRACBITS) + ((overtake && flipdraw) ? (localpatch->width)*scale/2 : 0),
(fy<<FRACBITS) + ((overtake && flipvdraw) ? (localpatch->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_SMonitorHUDVisibility(UINT16 t)
{
UINT32 alphalevel = st_translucency;
alphalevel = min(10, FixedMul(alphalevel, K_SMonitorGradient(t)));
return min(9, 10 - alphalevel)<<V_ALPHASHIFT;
}
static boolean K_RankIconCanSpinout(void)
{
return ((cv_spinoutroll.value) & 2);
}
static boolean K_MinimapIconCanSpinout(void)
{
return ((cv_spinoutroll.value) & 1);
}
static boolean K_drawKartPositionFaces(void)
{
// FACE_X = 15; // 15
// FACE_Y = 72; // 72
INT32 Y = FACE_Y-9; // -9 to offset where it's being drawn if there are more than one
INT32 i, j, ranklines, strank = -1;
boolean completed[MAXPLAYERS];
INT32 rankplayer[MAXPLAYERS];
INT32 bumperx, numplayersingame = 0;
UINT8 *colormap;
UINT32 smntrhudtrans;
vector2_t offsets;
#ifdef ROTSPRITE
angle_t rollangle = 0;
INT32 rot = 0;
#endif
offsets.x = offsets.y = 0;
ranklines = 0;
memset(completed, 0, sizeof (completed));
memset(rankplayer, 0, sizeof (rankplayer));
for (i = 0; i < MAXPLAYERS; i++)
{
rankplayer[i] = -1;
if (!playeringame[i] || players[i].spectator || !players[i].mo)
continue;
numplayersingame++;
}
if (numplayersingame <= 1)
return true;
if (!LUA_HudEnabled(hud_minirankings))
return false; // Don't proceed but still return true for free play above if HUD is disabled.
for (j = 0; j < numplayersingame; j++)
{
UINT8 lowestposition = MAXPLAYERS+1;
for (i = 0; i < MAXPLAYERS; i++)
{
if (completed[i] || !playeringame[i] || players[i].spectator || !players[i].mo)
continue;
if (players[i].position >= lowestposition)
continue;
rankplayer[ranklines] = i;
lowestposition = players[i].position;
}
i = rankplayer[ranklines];
completed[i] = true;
if (players+i == stplyr)
strank = ranklines;
//if (ranklines == 5)
//break; // Only draw the top 5 players -- we do this a different way now...
ranklines++;
}
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]].smonitortimer)
{
colormap = R_GetTranslationColormap(TC_BLINK, K_SMonitorColor(leveltime / 2), GTC_CACHE);
smntrhudtrans = K_SMonitorHUDVisibility(players[rankplayer[i]].smonitortimer);
V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, smntrhudtrans|V_SNAPTOLEFT|V_ADD, facerank, colormap);
}
if (LUA_HudEnabled(hud_battlebumpers))
{
if ((gametypes[gametype]->rules & 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 ((gametypes[gametype]->rules & 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 ((gametypes[gametype]->rules & 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 ((gametypes[gametype]->rules & 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?
};
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 ((gametypes[gametype]->rules & 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 ((gametypes[gametype]->rules & 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 ((gametypes[gametype]->rules & GTR_BUMPERS) && !(gametypes[gametype]->rules & 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 ((gametypes[gametype]->rules & GTR_BUMPERS) && !(gametypes[gametype]->rules & 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 ((gametypes[gametype]->rules & GTR_BUMPERS) && !(gametypes[gametype]->rules & 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_itemboxminimap, 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 && (gametypes[gametype]->rules & 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 (gametypes[gametype]->rules & 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<<FRACBITS, basey<<FRACBITS, FRACUNIT, V_HUDTRANS|(r_splitscreen < 3 ? V_SNAPTORIGHT : 0)|V_SNAPTOBOTTOM, (r_splitscreen > 1 ? kp_wantedsplit : kp_wanted), colormap);
/*if (basey2)
V_DrawFixedPatch(basex<<FRACBITS, basey2<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SNAPTOTOP, (splitscreen == 3 ? kp_wantedsplit : kp_wanted), colormap); // < used for 4p splits.*/
for (i = 0; i < numwanted; i++)
{
INT32 x = basex+(r_splitscreen > 1 ? 13 : 8), y = basey+(r_splitscreen > 1 ? 16 : 21);
fixed_t scale = FRACUNIT/2;
player_t *p = &players[battlewanted[i]];
if (battlewanted[i] == -1)
break;
if (numwanted == 1)
scale = FRACUNIT;
else
{
if (i & 1)
x += 16;
if (i > 1)
y += 16;
}
if (players[battlewanted[i]].skincolor)
{
colormap = R_GetTranslationColormap(TC_RAINBOW, p->skincolor, GTC_CACHE);
patch_t *wantedicon = (scale == FRACUNIT ? faceprefix[p->skin][FACE_WANTED] : faceprefix[p->skin][FACE_RANK]);
V_DrawFixedPatch((x+wantedicon->leftoffset)<<FRACBITS, (y+wantedicon->topoffset)<<FRACBITS, FRACUNIT, V_HUDTRANS|(r_splitscreen < 3 ? V_SNAPTORIGHT : 0)|V_SNAPTOBOTTOM, wantedicon, colormap);
/*if (basey2) // again with 4p stuff
V_DrawFixedPatch(x<<FRACBITS, (y - (basey-basey2))<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SNAPTOTOP, (scale == FRACUNIT ? faceprefix[p->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) && (checkplayer->smonitortimer <= 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 ((!K_IsKartItemAlternate(KITEM_INVINCIBILITY) && checkplayer->itemtype == KITEM_INVINCIBILITY) || checkplayer->invincibilitytimer)
{
pnum += 2;
}
else if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY) && checkplayer->itemtype == KITEM_INVINCIBILITY) || checkplayer->smonitortimer)
{
// FIXME: Separate "CHECK" icon(?)
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 == NT_OFF)
{
return false;
}
if (demo.playback == true && camera[R_GetViewNumber()].freecam == true)
{
return true;
}
if (stplyr == p)
{
if (cv_seeownname.value)
{
return true;
}
else
{
return false;
}
}
if (gametypes[gametype]->rules & 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, UINT8 flags)
{
UINT16 chatcolor = skincolors[p->skincolor].chatcolor;
V_DrawCenteredSmallStringAtFixed(x, y, V_HUDTRANS|chatcolor|flags, va("P%d", id+1));
}
static void K_DrawRivalTagForPlayer(fixed_t x, fixed_t y, UINT8 flags)
{
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|chatcolor|flags, "RIVAL");
}
static const char *K_StringTypingDot(UINT8 duration)
{
static const char *dots = "...";
return dots + CLAMP(3 - duration/16, 0, 3);
}
static void K_DrawTypingNotifier(fixed_t x, fixed_t y, player_t *p, UINT8 flags)
{
if (p->cmd.flags & TICCMD_TYPING)
{
V_DrawCenteredSmallStringAtFixed(x, y, V_SPLITSCREEN|flags, va("Typing%s",K_StringTypingDot(p->typing_duration)));
}
}
static void K_DrawNameTagForPlayer(fixed_t x, fixed_t y, player_t *p, UINT8 flags)
{
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|flags;
fixed_t textxoffset = 0;
fixed_t textyoffset = 0;
fixed_t barxoffset = 0;
fixed_t baryoffset = 0;
// 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.
// Calculate offsets and draw the stem
{
fixed_t stemx;
fixed_t stemy;
SINT8 stemcount = 4;
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;
switch(cv_seenames.value)
{
case NT_DEFAULT:
break;
case NT_SMALL:
stemcount = 2;
textxoffset = -2*FRACUNIT;
textyoffset = flipped ? -2*FRACUNIT : 12*FRACUNIT;
barxoffset = -3*FRACUNIT;
baryoffset = flipped ? -7*FRACUNIT : 7*FRACUNIT;
break;
case NT_TEXT:
textxoffset = -9*FRACUNIT;
textyoffset = 21*FRACUNIT;
stemcount = 0;
break;
}
if (stemcount > 0)
{
if (flipped)
{
for (j = 0; j < stemcount; 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 < stemcount; 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;
}
}
if (cv_seenames.value == NT_SMALL)
{
fixed_t flipoffset = flipped ? 2*FRACUNIT : 0;
V_DrawFixedFill(barx + barxoffset - 2*FRACUNIT, bary + baryoffset + flipoffset, 3*FRACUNIT, 1*FRACUNIT, vflags|backcolor);
}
V_DrawFixedFill(barx + barxoffset, bary + baryoffset, barw, 3*FRACUNIT, vflags|backcolor);
V_DrawFixedFill(barx - FRACUNIT + barxoffset, bary + FRACUNIT + baryoffset, barw, FRACUNIT, vflags|frontcolor);
}
// END DRAWFILL DUMBNESS
}
// Draw the name itself
if (cv_seenames.value == NT_DEFAULT)
{
V_DrawThinStringAtFixed(x + (5*FRACUNIT) + textxoffset, y - (26*FRACUNIT) + textyoffset, vflags|V_6WIDTHSPACE|V_ALLOWLOWERCASE|clr, player_names[p - players]);
}
else
{
V_DrawSmallStringAtFixed(x + (5*FRACUNIT) + textxoffset, y - (26*FRACUNIT) + textyoffset, vflags|V_6WIDTHSPACE|V_ALLOWLOWERCASE|clr, player_names[p - players]);
}
// Also draw stats of restated players.
if (cv_seenamerestat.value && (p->kartspeed != skins[p->skin].kartspeed
|| p->kartweight != skins[p->skin].kartweight))
{
V_DrawSmallStringAtFixed(x + (5*FRACUNIT) + textxoffset, y - (31*FRACUNIT) + textyoffset, vflags, va("\x84S%d ", p->kartspeed));
V_DrawSmallStringAtFixed(x + (15*FRACUNIT) + textxoffset, y - (31*FRACUNIT) + textyoffset, vflags, va("\x87W%d ", p->kartweight));
}
}
playertagtype_t K_WhichPlayerTag(player_t *p)
{
const UINT8 cnum = R_GetViewNumber();
if (!(demo.playback == true && camera[cnum].freecam == true) && P_IsDisplayPlayer(p) &&
p != &players[displayplayers[cnum]])
{
return PLAYERTAG_LOCAL;
}
else if (p->bot)
{
if ((p->botvars.rival == true || cv_forcebots.value))
{
return PLAYERTAG_RIVAL;
}
/*else if (K_ShowPlayerNametag(p) == true)
{
return PLAYERTAG_CPU;
}*/
}
else if (netgame || demo.playback || cv_seeownname.value)
{
if (K_ShowPlayerNametag(p) == true)
{
return PLAYERTAG_NAME;
}
}
return PLAYERTAG_NONE;
}
void K_DrawPlayerTag(fixed_t x, fixed_t y, player_t *p, playertagtype_t type, boolean foreground)
{
INT32 flags = 0;
switch (type)
{
case PLAYERTAG_LOCAL:
flags |= V_SPLITSCREEN;
K_DrawLocalTagForPlayer(x, y, p, G_PartyPosition(p - players), flags);
break;
case PLAYERTAG_RIVAL:
flags |= V_SPLITSCREEN;
flags |= foreground ? 0 : V_60TRANS;
K_DrawRivalTagForPlayer(x, y, flags);
break;
// FALLTHRU
/*case PLAYERTAG_CPU:
flags |= V_SPLITSCREEN;
flags |= foreground ? 0 : V_60TRANS;
K_DrawCPUTagForPlayer(x, y, p, flags);
break;*/
case PLAYERTAG_NAME:
flags |= foreground ? 0 : V_60TRANS;
K_DrawNameTagForPlayer(x, y, p, flags);
break;
default:
break;
}
K_DrawTypingNotifier(x, y, p, flags);
}
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)
{
vector3_t c;
const UINT8 cnum = R_GetViewNumber();
size_t 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]);
}
}
K_drawTargetHUD(&c, stplyr);
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)<<FRACBITS);
amypos = amnumypos + ((hudy + (minimapinfo.minimap_pic->height-h)/2)<<FRACBITS);
if (cv_minihead.value && (!(icon == kp_minimapdot)) && (!origin))
{
amxpos += 2 * FRACUNIT;
amypos += 2 * FRACUNIT;
}
if (cv_minihead.value && (icon == kp_wantedreticle))
{
amxpos += 2 * FRACUNIT;
amypos += 2 * FRACUNIT;
}
V_DrawFixedPatch(amxpos, amypos, scale, flags, icon, colormap);
}
static void K_drawKartMinimapHeadlight(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, angle_t ang, UINT8 *colormap)
{
// amnum xpos & ypos are the icon's speed around the HUD.
// The number being divided by is for how fast it moves.
// The higher the number, the slower it moves.
// am xpos & ypos are the icon's starting position. Withouht
// it, they wouldn't 'spawn' on the top-right side of the HUD.
fixed_t amnumxpos, amnumypos;
INT32 amxpos, amypos;
fixed_t scale = FRACUNIT;
patch_t *icon = W_CachePatchNameRotated("MMAPHDLT", R_GetRollAngle(ang), PU_PATCH);
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-48)/2)<<FRACBITS);
amypos = amnumypos + ((hudy + (minimapinfo.minimap_pic->height-24)/2)<<FRACBITS);
V_DrawFixedPatch(amxpos, amypos, scale, flags|V_ADD, icon, colormap);
}
static void K_drawKartMinimapNametag(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, player_t *player)
{
// 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.
if (!cv_showminimapnames.value)
{
return;
}
if (!player)
{
return;
}
if (!player->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)<<FRACBITS);
amypos = amnumypos + ((hudy + (minimapinfo.minimap_pic->height-faceprefix[skin][FACE_MINIMAP]->height)/2)<<FRACBITS);
const char *player_name = va("%s",player_names[player - players]);
V_DrawCenteredSmallStringAtFixed(amxpos + (6*FRACUNIT), amypos - (5*FRACUNIT), V_ALLOWLOWERCASE|flags|chatcolor, player_name);
}
static inline void K_drawKartMinimapDot(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, UINT8 color, UINT8 intsize)
{
fixed_t amnumxpos, amnumypos;
fixed_t size = intsize * FRACUNIT;
amnumxpos = (FixedMul(objx, minimapinfo.zoom) - minimapinfo.offs_x);
amnumypos = -(FixedMul(objy, minimapinfo.zoom) - minimapinfo.offs_y);
if (encoremode)
amnumxpos = -amnumxpos;
amnumxpos += minimapinfo.minimap_pic->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 smntrgradient = 0;
#ifdef ROTSPRITE
angle_t rollangle = 0;
INT32 rot = 0;
INT32 sparkleflags;
patch_t *rotsparkle;
boolean halftrans = false;
fixed_t transmul = 0;
UINT32 smonitortrans = 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)<<FF_TRANSSHIFT);
if (encoremode)
V_DrawScaledPatch(x+minimapinfo.minimap_pic->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 ((gametypes[gametype]->rules & 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;
smntrgradient = K_SMonitorGradient(players[i].smonitortimer);
if (players[i].invincibilitytimer)
{
usecolor = (K_RainbowColor(leveltime / 2));
colorizeplayer = true;
}
else if ((players[i].smonitortimer) && (smntrgradient > (FRACUNIT/2)))
{
usecolor = (K_SMonitorColor(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 ((players[i].smonitortimer) && (smntrgradient))
{
// Draw S-Monitor sparkles
halftrans =
((splitflags & V_HUDTRANSHALF) == V_HUDTRANSHALF);
transmul = 0;
smonitortrans = 0;
sparkleflags =
splitflags & (~(V_HUDTRANS | V_HUDTRANSHALF));
if (halftrans)
{
transmul = FRACUNIT -
(V_GetHudTransHalf() * FRACUNIT / 10);
}
else
{
transmul =
FRACUNIT - (V_GetHudTrans() * FRACUNIT / 10);
}
smonitortrans =
max(0,
min(9,
10 - FixedMul(FixedMul(10, smntrgradient),
transmul)))
<< V_ALPHASHIFT;
sparkleflags |= smonitortrans;
if (kp_smonitorsparkle)
{
rot = R_GetRollAngle(-TICTOANGLE(leveltime));
if (rot)
{
rotsparkle = Patch_GetRotated(
kp_smonitorsparkle, rot, false);
}
else
{
rotsparkle = kp_smonitorsparkle;
}
if (rotsparkle)
{
widthhalf = ((kp_smonitorsparkle->width) / 2);
heighthalf = ((kp_smonitorsparkle->height) / 2);
if (cv_minihead.value)
{
adjustx = FixedMul(
4,
(widthhalf -
kp_smonitorsparkle->leftoffset) *
FRACUNIT / widthhalf);
adjusty = FixedMul(
4,
(heighthalf -
kp_smonitorsparkle->topoffset) *
FRACUNIT / heighthalf);
}
iconoffsets.x = widthhalf -
kp_smonitorsparkle->leftoffset -
adjustx;
iconoffsets.y = heighthalf -
kp_smonitorsparkle->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)
|| ((gametypes[gametype]->rules & 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;
smntrgradient =
K_SMonitorGradient(
players[localplayers[i]].smonitortimer);
if (players[localplayers[i]].invincibilitytimer)
{
usecolor = (K_RainbowColor(leveltime / 2));
colorizeplayer = true;
}
else if ((players[localplayers[i]].smonitortimer)
&& ((smntrgradient > (FRACUNIT / 2))))
{
usecolor = (K_SMonitorColor(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) && (players[localplayers[i]].smonitortimer) && (smntrgradient))
{
// Draw S-Monitor sparkles
halftrans = ((splitflags & V_HUDTRANSHALF) == V_HUDTRANSHALF);
transmul = 0;
smonitortrans = 0;
sparkleflags = splitflags & (~(V_HUDTRANS | V_HUDTRANSHALF));
if (halftrans)
{
transmul = FRACUNIT - (V_GetHudTransHalf() * FRACUNIT / 10);
}
else
{
transmul = FRACUNIT - (V_GetHudTrans() * FRACUNIT / 10);
}
transmul *= 2;
smonitortrans =
max(0,
min(9,
10 - FixedMul(FixedMul(10, smntrgradient), transmul)))
<< V_ALPHASHIFT;
sparkleflags |= smonitortrans;
if (kp_smonitorsparkle)
{
rot = R_GetRollAngle(-TICTOANGLE(leveltime));
if (rot)
{
rotsparkle = Patch_GetRotated(
kp_smonitorsparkle, rot, false);
}
else
{
rotsparkle = kp_smonitorsparkle;
}
if (rotsparkle)
{
widthhalf = ((kp_smonitorsparkle->width) / 2);
heighthalf = ((kp_smonitorsparkle->height) / 2);
if (cv_minihead.value)
{
adjustx = FixedMul(
4,
(widthhalf - kp_smonitorsparkle->leftoffset) *
FRACUNIT / widthhalf);
adjusty = FixedMul(
4,
(heighthalf - kp_smonitorsparkle->topoffset) *
FRACUNIT / heighthalf);
}
iconoffsets.x =
widthhalf - kp_smonitorsparkle->leftoffset - adjustx;
iconoffsets.y =
heighthalf - kp_smonitorsparkle->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)
|| ((gametypes[gametype]->rules & 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)
{
if (!(gametypes[gametype]->rules & GTR_CIRCUIT))
return;
if (waypointcap == NULL)
return;
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<<FRACBITS)/vid.dup);
xval = (SHORT(kptodraw[pnum]->width)<<FRACBITS);
pwidth = max(xval, x);
x = ((TICRATE - timer) * pwidth) / TICRATE;
ox = ((TICRATE - (timer - 1)) * pwidth) / TICRATE;
interpx = R_InterpolateFixed(ox, x);
if (r_splitscreen && stplyrnum == 1)
interpx = -interpx;
V_DrawFixedPatch(interpx + (STCD_X<<FRACBITS) - (pwidth / 2),
(STCD_Y<<FRACBITS) - (SHORT(kptodraw[pnum]->height)<<(FRACBITS-1)),
FRACUNIT,
splitflags, kptodraw[pnum], NULL);
}
}
static void K_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<<FRACBITS, y, scale, splitflags, p, NULL);
}
K_drawKartFinish();
}
else if (stplyr->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<<FRACBITS, y, scale, splitflags, kp_battleinfo, NULL);
else
V_DrawFixedPatch(x<<FRACBITS, y, scale, splitflags, kp_battlewait, NULL);
if (r_splitscreen > 1)
V_DrawString(x-txoff, ty, 0, va("%d", stplyr->karmadelay/TICRATE));
else
{
if (!K_UseColorHud())
V_DrawFixedPatch(x<<FRACBITS, ty<<FRACBITS, scale, 0, kp_timeoutsticker[0], NULL);
else //Colourized hud
{
UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE);
V_DrawFixedPatch(x<<FRACBITS, ty<<FRACBITS, scale, 0, kp_timeoutsticker[1], colormap);
}
V_DrawKartString(x-txoff, ty, 0, va("%d", stplyr->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)<<FRACBITS, (joyy+joyyoffs-1)<<FRACBITS, FRACUNIT, splitflags, joybacking, hudcolormap);
}
else
{
V_DrawFill(joyx+joyxoffs, joyy+joyyoffs-1, 16, 16, splitflags|accent2);
V_DrawFill(joyx+joyxoffs, joyy+joyyoffs+15, 16, 1, splitflags|31);
}
if (stplyr->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)<<FRACBITS, (joyy+joyyoffs+2-stplyr->cmd.throwdir/80)<<FRACBITS, FRACUNIT, splitflags, joyknob, shadowcolormap);
V_DrawFixedPatch((joyx+joyxoffs+3-turning/64)<<FRACBITS, (joyy+joyyoffs+1-stplyr->cmd.throwdir/64)<<FRACBITS, FRACUNIT, splitflags, joyknob, hudcolormap);
}
else
{
// joystick hole
V_DrawFill(joyx+joyxoffs+5, joyy+joyyoffs+4, 6, 6, splitflags|accent1);
// joystick top and back
V_DrawFill(joyx+joyxoffs+3-turning/80,
joyy+joyyoffs+2-stplyr->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)<<FRACBITS, (joyy+joyyoffs+8)<<FRACBITS, FRACUNIT, splitflags, joyshadow, shadowcolormap);
V_DrawFixedPatch((joyx+joyxoffs+3)<<FRACBITS, (joyy+joyyoffs+1)<<FRACBITS, FRACUNIT, splitflags, joyknob, hudcolormap);
}
else
{
V_DrawFill(joyx+joyxoffs+3, joyy+joyyoffs+11, 10, 1, splitflags|accent2);
V_DrawFill(joyx+joyxoffs+3,
joyy+joyyoffs+1,
10, 10,splitflags|accent1);
}
}
}
else if (cv_showinput.value == 1)
{
patch_t *workingPic = kp_inputwheel;
patch_t *shadowPic = kp_inputwheel_shadow;
INT16 turning = encoremode ? -stplyr->cmd.turning : stplyr->cmd.turning;
angle_t rotate = FixedAngle(60 * FixedDiv(abs(turning), 1024) * intsign(turning));
UINT8 *colormap;
if (!K_GetHudColor())
{
V_DrawRotatedPatch(x-(17*FRACUNIT), y-(38*FRACUNIT)+(FRACUNIT*2), rotate, FRACUNIT, FRACUNIT, splitflags|V_SUBTRACT|V_10TRANS, shadowPic, NULL);
V_DrawRotatedPatch(x-(17*FRACUNIT), y-(38*FRACUNIT), rotate, FRACUNIT, FRACUNIT, splitflags, workingPic, NULL);
}
else
{
colormap = R_GetTranslationColormap(0, K_GetHudColor(), GTC_CACHE);
V_DrawRotatedPatch(x-(17*FRACUNIT), y-(38*FRACUNIT)+(FRACUNIT*2), rotate, FRACUNIT, FRACUNIT, splitflags|V_SUBTRACT|V_10TRANS, shadowPic, colormap);
V_DrawRotatedPatch(x-(17*FRACUNIT), y-(38*FRACUNIT), rotate, FRACUNIT, FRACUNIT, splitflags, workingPic, colormap);
}
// a la GT7
if (stplyr->cmd.flags & TICCMD_USINGTILT)
{
fixed_t tilt = G_GetGamepadGravity(stplyrnum).x;
if (demo.playback || !P_IsMachineLocalPlayer(stplyr))
tilt = -1;
else
rotate = FixedAngle(120 * abs(tilt));
x += 17*FSIN(rotate) * intsign(tilt);
y -= 17*FCOS(rotate);
splitflags |= (((leveltime % 3 == 0) && (stplyr->cmd.flags & TICCMD_EXCESSTILT)) ? V_ADD|V_20TRANS : 0);
if (!K_GetHudColor())
{
V_DrawFixedPatch(x-(2*FRACUNIT)-(FRACUNIT/2), y-(23*FRACUNIT), FRACUNIT, splitflags, kp_minimapdot, NULL);
}
else
{
colormap = R_GetTranslationColormap(0, K_GetHudColor(), GTC_CACHE);
V_DrawFixedPatch(x-(2*FRACUNIT)-(FRACUNIT/2), y-(23*FRACUNIT), FRACUNIT, splitflags, kp_minimapdot, 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));
if (stplyr->currentwaypoint && (stplyr->currentwaypoint->mobj != NULL) && K_SafeRespawnPosition(stplyr->currentwaypoint->mobj))
{
V_DrawString(8, 120, 0, va("Waypoint %d (Current) Safe: True", K_GetWaypointID(stplyr->currentwaypoint)));
}
else
{
V_DrawString(8, 120, 0, va("Waypoint %d (Current) Safe: False", K_GetWaypointID(stplyr->currentwaypoint)));
}
if (stplyr->nextwaypoint && (stplyr->nextwaypoint->mobj != NULL) && K_SafeRespawnPosition(stplyr->nextwaypoint->mobj))
{
V_DrawString(8, 130, 0, va("Waypoint %d (Next) Safe: True", K_GetWaypointID(stplyr->nextwaypoint)));
}
else
{
V_DrawString(8, 130, 0, va("Waypoint %d (Next) Safe: False", K_GetWaypointID(stplyr->nextwaypoint)));
}
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
splitflags |= V_SNAPTOBOTTOM;
}
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 = ((gametypes[gametype]->rules & (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 (gametypes[gametype]->rules & 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 (gametypes[gametype]->rules & GTR_CIRCUIT) // Race-only elements
{
if (!islonesome)
{
// Draw the numerical position
K_DrawKartPositionNum(stplyr->position);
}
}
}
if (LUA_HudEnabled(hud_gametypeinfo))
{
if (gametypes[gametype]->rules & GTR_CIRCUIT)
{
K_drawKartLaps();
K_drawKartStatsnLives();
}
else if (gametypes[gametype]->rules & 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 (!(gametypes[gametype]->rules & 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_SNAPTOBOTTOM, countstr);
}
}
// Race overlays
if ((gametypes[gametype]->rules & 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 ((gametypes[gametype]->rules & 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<<FRACBITS, y<<FRACBITS, FRACUNIT>>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<<FRACBITS);
basey = res.y - (52<<FRACBITS);
if (K_UseColorHud())
{
colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE);
}
// fire animation
if (stplyr->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);
}