blankart/src/k_hud.c

7206 lines
196 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"},
{3, "Dial"},
{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);
// make char potraits use their high-res version instead
consvar_t cv_highresportrait = CVAR_INIT ("highresportrait", "Off", 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];
// dial speedometer
static patch_t *kp_dial = NULL;
static patch_t *kp_dialfinish = NULL;
static patch_t *kp_dialbase[4] = {NULL};
static patch_t *kp_speedpatchesdial[8] = {NULL};
static patch_t *kp_dialnum[20] = {NULL};
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);
CV_RegisterVar(&cv_highresportrait);
// 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");
// Dial speedometer
HU_UpdatePatch(&kp_dial, "K_DSDIAL");
kp_dial->pivot.x = kp_dial->width / 2;
kp_dial->pivot.y = kp_dial->height / 2;
kp_dial->alignflags |= PATCHALIGN_USEPIVOTS;
HU_UpdatePatch(&kp_dialfinish, "K_DILFIN");
HU_UpdatePatch(&kp_dialbase[0], "K_DSPBS1");
HU_UpdatePatch(&kp_dialbase[1], "K_DSPBS2");
HU_UpdatePatch(&kp_dialbase[2], "K_DSPBC1");
HU_UpdatePatch(&kp_dialbase[3], "K_DSPBC2");
HU_UpdatePatch(&kp_speedpatchesdial[0], "SP_DKMH");
HU_UpdatePatch(&kp_speedpatchesdial[1], "SP_DMPH");
HU_UpdatePatch(&kp_speedpatchesdial[2], "SP_DFRAC");
HU_UpdatePatch(&kp_speedpatchesdial[3], "SP_DPERC");
HU_UpdatePatch(&kp_speedpatchesdial[4], "SC_DKMH");
HU_UpdatePatch(&kp_speedpatchesdial[5], "SC_DMPH");
HU_UpdatePatch(&kp_speedpatchesdial[6], "SC_DFRAC");
HU_UpdatePatch(&kp_speedpatchesdial[7], "SC_DPERC");
sprintf(buffer, "K_DSPNMx");
for (i = 0; i < 10; i++)
{
buffer[7] = '0'+(i%10);
HU_UpdatePatch(&kp_dialnum[i], "%s", buffer);
}
sprintf(buffer, "K_DSPNCx");
for (i = 0; i < 10; i++)
{
buffer[7] = '0'+(i%10);
HU_UpdatePatch(&kp_dialnum[i+10], "%s", buffer);
}
// 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, "B_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, "B_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_IsHighResolution(void)
{
return (vid.width >= 640 && vid.height >= 400);
}
boolean K_UseHighResPortraits(void)
{
return (cv_highresportrait.value && K_IsHighResolution());
}
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;
boolean dialspeedometer = (cv_newspeedometer.value == 3 && r_splitscreen < 1);
// pain and suffering defined below
if (r_splitscreen < 2) // don't change shit for THIS splitscreen.
{
fx = RING_X + ((dialspeedometer) ? 31 : 0);
fy = RING_Y + ((dialspeedometer) ? 14 : 0);
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);
boolean hires = K_UseHighResPortraits();
patch_t *facerank = faceprefix[players[rankplayer[i]].skin][hires ? FACE_WANTED : 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_DrawFixedPatch((FACE_X + offsets.x)<<FRACBITS, (Y + offsets.y)<<FRACBITS, hires ? FRACUNIT / 2 : FRACUNIT, 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_DrawFixedPatch((FACE_X + offsets.x)<<FRACBITS, (Y + offsets.y)<<FRACBITS, hires ? FRACUNIT / 2 : FRACUNIT, 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 23
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},
{"Keep Stuff", 0, NULL, K_KeepStuffActive() > 0, true},
{"No BananaDrag", 0, NULL, K_TrailSlowActive() < 1, true},
{"No Air Bumps", 0, NULL, K_GetPlayerBump() == PBUMP_GNDONLY, true},
{"No PvP Bumps", 0, NULL, K_GetPlayerBump() == PBUMP_NONE, 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);
boolean hires = K_UseHighResPortraits();
patch_t *facerank = faceprefix[players[tab[i].num].skin][hires ? FACE_WANTED : FACE_RANK];
INT16 leftoffset = facerank->leftoffset;
INT16 topoffset = facerank->topoffset;
V_DrawFixedPatch((x+leftoffset)<<FRACBITS, (y-4+topoffset)<<FRACBITS, hires ? FRACUNIT/2 : FRACUNIT, 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 || cv_newspeedometer.value == 3) && !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);
boolean hires = K_UseHighResPortraits();
patch_t *facerank = faceprefix[stplyr->skin][hires ? FACE_WANTED : FACE_RANK];
INT16 topoffset = hires ? facerank->topoffset / 2 : facerank->topoffset;
INT16 leftoffset = hires ? facerank->leftoffset / 2 : facerank->leftoffset;
V_DrawFixedPatch((fx+59+offsetx+leftoffset)<<FRACBITS, (fy-16+offsety+topoffset)<<FRACBITS, hires ? FRACUNIT/2 : FRACUNIT, 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+leftoffset)<< FRACBITS, (fy-19+offsety+topoffset)<< FRACBITS, FRACUNIT, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartspeed % 10)], colormapstat);
V_DrawFixedPatch((fx+69+offsetx+leftoffset)<< FRACBITS, (fy-4+offsety+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;
}
}
#ifdef ROTSPRITE
#define DIALSPDDIV 97090 // 1.48148; converts 200 to 135
#define MPHDIV 68283 // 1.04192; converts 141 to 135
static void K_DrawDialNum(INT32 x, INT32 y, boolean colorized, INT32 flags, INT32 num, INT32 digits, const UINT8 *colormap)
{
INT32 w = 0;
w = kp_dialnum[0]->width;
if (flags & V_NOSCALESTART)
w *= vid.dup;
if (num < 0)
num = -num;
// draw the number
do
{
x -= (w);
if (colorized)
V_DrawFixedPatch(x << FRACBITS, y << FRACBITS, FRACUNIT, flags, kp_dialnum[(num % 10) + 10], colormap);
else
V_DrawFixedPatch(x << FRACBITS, y << FRACBITS, FRACUNIT, flags, kp_dialnum[num % 10], colormap);
num /= 10;
} while (--digits);
}
static void K_DrawDialLaps(INT32 x, INT32 y, INT32 num, INT32 total, INT32 flags)
{
INT32 fx;
fx = x + ((num < 10) ? 0 : 6);
V_DrawRankNum(fx, y, flags, num, (num < 10) ? 1 : 2, NULL);
V_DrawScaledPatch(fx + 2, y, flags, frameslash);
V_DrawRankNum(fx + 13 + ((total < 10) ? 0 : 6), y, flags, total, (total < 10) ? 1 : 2, NULL);
}
static void K_DrawDialSpeedometer(fixed_t speed,
fixed_t divisor,
UINT16 labeln,
INT32 splitflags,
boolean battlemode,
boolean infoactive,
boolean colorized)
{
const UINT8* colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE);
INT32 ringoffset = 0;
const UINT8 infoidx = (infoactive) ? 1 : 0;
const fixed_t spd = FixedDiv(speed, divisor);
const angle_t speedangle =
FixedAngle(((min(135 * FRACUNIT, spd) - (45 * FRACUNIT))));
if (K_RingsActive() == true)
{
ringoffset = -16;
}
if (colorized) // Colourized hud
{
V_DrawMappedPatch(
SPDM_X, SPDM_Y - 25, (V_HUDTRANS | splitflags), kp_dialbase[infoidx + 2], colormap);
V_DrawMappedPatch(SPDM_X,
SPDM_Y + 14,
V_HUDTRANS | splitflags,
kp_speedpatchesdial[labeln + 4],
colormap);
}
else
{
V_DrawScaledPatch(SPDM_X, SPDM_Y - 25, (V_HUDTRANS | splitflags), kp_dialbase[infoidx]);
V_DrawScaledPatch(SPDM_X,
SPDM_Y + 14,
V_HUDTRANS | splitflags,
kp_speedpatchesdial[labeln]);
}
K_DrawDialNum(SPDM_X + 10,
SPDM_Y + 9,
colorized,
V_HUDTRANS | splitflags,
speed / FRACUNIT,
3,
colormap);
// gotta center the dial manually
V_DrawRotatedPatch((SPDM_X - 19) << FRACBITS,
(SPDM_Y - 1) << FRACBITS,
speedangle,
FRACUNIT,
FRACUNIT,
(V_HUDTRANS | splitflags),
kp_dial,
colormap);
if (!infoactive)
{
// no need to draw info if we're not supposed to
return;
}
// draw the info
if (battlemode)
{
if (itembreaker)
{
V_DrawMappedPatch(SPDM_X + 28, SPDM_Y + 12, V_HUDTRANS | splitflags, kp_itemboxminimap, NULL);
K_DrawDialLaps(SPDM_X + 46,
SPDM_Y + 13,
numtargets,
nummapboxes,
V_HUDTRANS | splitflags);
}
else
{
if (stplyr->bumper <= 0 && (gametypes[gametype]->rules & GTR_KARMA) && comeback)
{
V_DrawMappedPatch(
SPDM_X + 28, SPDM_Y + 12, V_HUDTRANS | splitflags, kp_splitkarmabomb, colormap);
K_DrawDialLaps(SPDM_X + 46,
SPDM_Y + 13,
stplyr->karmapoints,
2,
V_HUDTRANS | splitflags);
}
else // the above doesn't need to account for weird stuff since the max amount of karma
// necessary is always 2 ^^^^
{
INT32 maxbumper = K_StartingBumperCount();
V_DrawMappedPatch(
SPDM_X + 28, SPDM_Y + 12, V_HUDTRANS | splitflags, kp_rankbumper, colormap);
K_DrawDialLaps(SPDM_X + 46,
SPDM_Y + 13,
stplyr->bumper,
maxbumper,
V_HUDTRANS | splitflags);
}
}
}
else
{
V_DrawScaledPatch(SPDM_X + 28, SPDM_Y + 12, V_HUDTRANS | splitflags, kp_splitlapflag);
if (stplyr->exiting)
V_DrawScaledPatch(SPDM_X + 39, SPDM_Y + 13, V_HUDTRANS | splitflags, kp_dialfinish);
else
K_DrawDialLaps(SPDM_X + 46,
SPDM_Y + 13,
stplyr->laps,
numlaps,
V_HUDTRANS | splitflags);
}
}
#endif
static void K_drawKartSpeedometer(void)
{
static fixed_t convSpeed[2] = {0};
UINT8 labeln = 0;
UINT8 numbers[3];
INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN;
INT32 battleoffset = 0;
INT32 ringoffset = 0;
INT32 oldringoffset = 0;
#ifdef ROTSPRITE
fixed_t dial_divisor = DIALSPDDIV;
#endif
switch (cv_kartspeedometer.value)
{
case 1: // Kilometers
convSpeed[0] = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale); // 2.172409058
convSpeed[1] = convSpeed[0] / FRACUNIT;
labeln = 0;
break;
case 2: // Miles
convSpeed[0] = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale); // 1.349868774
convSpeed[1] = convSpeed[0] / FRACUNIT;
labeln = 1;
break;
case 3: // Fracunits
convSpeed[0] = FixedDiv(stplyr->speed, mapobjectscale); // 1.0. duh.
convSpeed[1] = convSpeed[0] / FRACUNIT;
labeln = 2;
break;
case 4: // Sonic Drift 2 style percentage
if (stplyr->mo)
{
convSpeed[0] = (FixedDiv(stplyr->speed, FixedMul(K_GetKartSpeed(stplyr, false, false), K_PlayerBaseFriction(stplyr, ORIG_FRICTION)))*100);
convSpeed[1] = convSpeed[0] >> FRACBITS;
}
labeln = 3;
break;
default:
break;
}
// Don't overflow
// (negative speed IS really high speed :V)
if (convSpeed[1] > 999 || convSpeed[1] < 0)
{
convSpeed[1] = 999;
convSpeed[0] = convSpeed[1] * FRACUNIT;
}
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[1]));
break;
case 2:
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d mph", convSpeed[1]));
break;
case 3:
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d fu/t", convSpeed[1]));
break;
case 4:
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%4d %%", convSpeed[1]));
break;
default:
break;
}
}
else if (cv_newspeedometer.value == 1)
{
numbers[0] = ((convSpeed[1] / 100) % 10);
numbers[1] = ((convSpeed[1] / 10) % 10);
numbers[2] = (convSpeed[1] % 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]);
}
#ifdef ROTSPRITE
else if (cv_newspeedometer.value == 3)
{
K_DrawDialSpeedometer(convSpeed[0],
dial_divisor,
labeln,
splitflags,
(boolean)((gametypes[gametype]->rules & GTR_BUMPERS) == GTR_BUMPERS),
(LUA_HudEnabled(hud_gametypeinfo)),
(K_UseColorHud()));
}
#endif
K_drawKartAccessibilityIcons((cv_newspeedometer.value == 0 || cv_newspeedometer.value == 2) ? 50 : 56);
}
#ifdef ROTSPRITE
#undef DIALSPDDIV
#undef MPHDIV
#endif
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)
{
INT32 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);
}
// determines if gametype info (laps/bumpers) should be hidden
static boolean K_DisableGametypeInfo(void)
{
if (!LUA_HudEnabled(hud_gametypeinfo))
{
return true;
}
#ifdef ROTSPRITE
if (r_splitscreen || (!cv_kartspeedometer.value))
{
// don't need to run checks if we're in splitscreen, or not using the speedometer
return false;
}
if (cv_newspeedometer.value == 3)
return true;
#endif
return false;
}
void K_drawKartHUD(void)
{
boolean islonesome = false;
boolean battlefullscreen = false;
boolean gameinfovisible = 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
{
// get gametype info visibility ahead of time
gameinfovisible = (!K_DisableGametypeInfo());
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)
{
if (gameinfovisible)
K_drawKartLaps();
K_drawKartStatsnLives();
}
else if (gametypes[gametype]->rules & GTR_BUMPERS)
{
if (gameinfovisible)
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);
}