Splits in Race gametypes

This commit is contained in:
Antonio Martinez 2025-08-08 20:09:14 -04:00
parent 0918e83873
commit 385257af73
6 changed files with 169 additions and 74 deletions

View file

@ -44,6 +44,8 @@ extern "C" {
// (done here as p_local.h, the previous host, has this as a dependency - but we must use it here)
#define MAX_LAPS 99
#define MAXRACESPLITS 32
// Extra abilities/settings for skins (combinable stuff)
typedef enum
{
@ -351,12 +353,11 @@ typedef enum
khud_exptimer,
// Splits
khud_splittime,
khud_splitwin,
khud_splittimer,
khud_splitskin,
khud_splitcolor,
khud_splitlast,
khud_splittime, // Delta between you and highest split
khud_splitwin, // How to color/flag the split based on gaining/losing | ahead/behind
khud_splittimer, // How long to show splits HUD
khud_splitskin, // Skin index of the leading player
khud_splitcolor, // Skincolor of the leading player
NUMKARTHUD
} karthudtype_t;
@ -1132,6 +1133,9 @@ struct player_t
fixed_t transfer; // Tired of Ramp Park fastfalls
tic_t splits[MAXRACESPLITS]; // Times we crossed checkpoint
INT32 pace; // Last split delta, used for checking whether gaining or losing time
uint8_t public_key[PUBKEYLENGTH];
#ifdef HWRENDER

View file

@ -2292,7 +2292,7 @@ void G_SetDemoCheckpointTiming(player_t *player, tic_t time, UINT8 checkpoint)
player->karthud[khud_splitwin] = 2; // ahead and gaining
}
INT32 last = player->karthud[khud_splitlast];
INT32 last = player->pace;
INT32 now = player->karthud[khud_splittime];
if (checkpoint != 0)
@ -2303,7 +2303,7 @@ void G_SetDemoCheckpointTiming(player_t *player, tic_t time, UINT8 checkpoint)
player->karthud[khud_splitwin] = -1; // behind but gaining
}
player->karthud[khud_splitlast] = player->karthud[khud_splittime];
player->pace = player->karthud[khud_splittime];
}
}

View file

@ -2309,6 +2309,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
UINT16 preffollowercolor;
INT32 preffollower;
tic_t splits[MAXRACESPLITS];
INT32 i;
// This needs to be first, to permit it to wipe extra information
@ -2338,6 +2340,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
preffollower = players[player].preffollower;
preffollowercolor = players[player].preffollowercolor;
memcpy(&splits, &players[player].splits, sizeof(splits));
if (betweenmaps)
{
fakeskin = MAXSKINS;
@ -2676,6 +2680,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
memcpy(&p->public_key, &public_key, sizeof(p->public_key));
memcpy(&p->splits, &splits, sizeof(p->splits));
if (saveroundconditions)
memcpy(&p->roundconditions, &roundconditions, sizeof (p->roundconditions));

View file

@ -7750,6 +7750,74 @@ void K_drawKartHUD(void)
}
}
boolean debug_alwaysdrawsplits = false;
if (
(
!stplyr->karthud[khud_lapanimation] &&
stplyr->karthud[khud_splittimer] &&
(stplyr->karthud[khud_splittimer] > TICRATE/3 || stplyr->karthud[khud_splittimer]%2 || cv_reducevfx.value)
)
|| debug_alwaysdrawsplits
)
{
using srb2::Draw;
INT32 split = stplyr->karthud[khud_splittime];
INT32 skin = stplyr->karthud[khud_splitskin];
INT32 color = stplyr->karthud[khud_splitcolor];
INT32 ahead = stplyr->karthud[khud_splitwin];
// debug
if (!stplyr->karthud[khud_splittimer])
{
ahead = ((leveltime/17)%5) - 2;
split = leveltime;
skin = stplyr->skin;
color = stplyr->skincolor;
}
split = std::abs(split);
UINT8 *skincolor = R_GetTranslationColormap(skin, static_cast<skincolornum_t>(color), GTC_CACHE);
UINT8 textcolor = SKINCOLOR_WHITE;
switch (ahead)
{
case 2:
textcolor = SKINCOLOR_SAPPHIRE; // leading and gaining
break;
case 1:
textcolor = SKINCOLOR_PIGEON; // leading and losing
break;
case -1:
textcolor = SKINCOLOR_RUBY; // trailing and gaining
break;
case -2:
textcolor = SKINCOLOR_CRIMSON; // trailing and losing
break;
}
Draw row = Draw(BASEVIDWIDTH/2, BASEVIDHEIGHT/4).align(Draw::Align::kCenter)
.font(Draw::Font::kThinTimer).flags(V_30TRANS);
std::string arrow = (ahead == 1 || ahead == -2) ? "(" : ")";
// vibes offset
row.x(-35).colormap(skincolor).patch(R_CanShowSkinInDemo(skin) ? faceprefix[skin][FACE_MINIMAP] : kp_unknownminimap);
Draw::TextElement text = Draw::TextElement(
std::string(ahead >= 0 ? "-" : "+") + " " + "{:02}'{:02}\"{:02} " + arrow,
G_TicsToMinutes(split, true),
G_TicsToSeconds(split),
G_TicsToCentiseconds(split)
);
// vibes offset TWO
row.colormap(textcolor).colorize(textcolor).x(15).text(text);
}
if (modeattacking || (gametyperules & GTR_TIMELIMIT) || cv_drawtimer.value)
K_drawKartTimestamp(realtime, TIME_X, TIME_Y + (ta ? 2 : 0), flags, 0);
@ -7763,72 +7831,6 @@ void K_drawKartHUD(void)
.flags(flags | V_ORANGEMAP)
.align(Draw::Align::kRight)
.text(text.string());
boolean debug_alwaysdraw = false;
if (
(
!stplyr->karthud[khud_lapanimation] &&
stplyr->karthud[khud_splittimer] &&
(stplyr->karthud[khud_splittimer] > TICRATE/3 || stplyr->karthud[khud_splittimer]%2 || cv_reducevfx.value)
)
|| debug_alwaysdraw
)
{
INT32 split = stplyr->karthud[khud_splittime];
INT32 skin = stplyr->karthud[khud_splitskin];
INT32 color = stplyr->karthud[khud_splitcolor];
INT32 ahead = stplyr->karthud[khud_splitwin];
// debug
if (!stplyr->karthud[khud_splittimer])
{
ahead = ((leveltime/17)%5) - 2;
split = leveltime;
skin = stplyr->skin;
color = stplyr->skincolor;
}
split = std::abs(split);
UINT8 *skincolor = R_GetTranslationColormap(skin, static_cast<skincolornum_t>(color), GTC_CACHE);
UINT8 textcolor = SKINCOLOR_WHITE;
switch (ahead)
{
case 2:
textcolor = SKINCOLOR_SAPPHIRE; // leading and gaining
break;
case 1:
textcolor = SKINCOLOR_PIGEON; // leading and losing
break;
case -1:
textcolor = SKINCOLOR_RUBY; // trailing and gaining
break;
case -2:
textcolor = SKINCOLOR_CRIMSON; // trailing and losing
break;
}
Draw row = Draw(BASEVIDWIDTH/2, BASEVIDHEIGHT/4).align(Draw::Align::kCenter)
.font(Draw::Font::kThinTimer).flags(V_30TRANS);
std::string arrow = (ahead == 1 || ahead == -2) ? "(" : ")";
// vibes offset
row.x(-35).colormap(skincolor).patch(R_CanShowSkinInDemo(skin) ? faceprefix[skin][FACE_MINIMAP] : kp_unknownminimap);
Draw::TextElement text = Draw::TextElement(
std::string(ahead >= 0 ? "-" : "+") + " " + "{:02}'{:02}\"{:02} " + arrow,
G_TicsToMinutes(split, true),
G_TicsToSeconds(split),
G_TicsToCentiseconds(split)
);
// vibes offset TWO
row.colormap(textcolor).colorize(textcolor).x(15).text(text);
}
}
else
{

View file

@ -4431,6 +4431,80 @@ void K_AwardPlayerRings(player_t *player, UINT16 rings, boolean overload)
}
}
static void K_SetupSplitForPlayer(player_t *us, player_t *them, tic_t ourtime, tic_t theirtime)
{
us->karthud[khud_splittimer] = 3*TICRATE;
INT32 delta = (INT32)theirtime - (INT32)ourtime; // how ahead are we? bigger number = more ahead, negative = behind
us->karthud[khud_splittime] = -1 * delta; // (HUD expects this to be backwards, but this is how i felt today!)
INT32 winning = 0;
if (delta > 0)
winning = 2; // winning aid gaining
else if (delta < 0)
winning = -2; // behind and falling
if (winning > 0 && delta < us->pace)
winning = 1; // winning but falling
else if (winning < 0 && delta > us->pace)
winning = -1; // behind but gaiming
us->pace = delta;
us->karthud[khud_splitwin] = winning;
us->karthud[khud_splitskin] = them->skin;
us->karthud[khud_splitcolor] = them->skincolor;
}
static void K_HandleRaceSplits(player_t *player, tic_t time, UINT8 checkpoint)
{
if (checkpoint >= MAXRACESPLITS)
return;
player->splits[checkpoint] = time;
player_t *lowest = player;
UINT8 numrealsplits = 0;
// find fastest player for this checkpoint and # players who have already crossed
for (UINT8 i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
player_t *check = &players[i];
if (check == player)
continue;
if (check->spectator)
continue;
if (check->splits[checkpoint] == 0)
continue;
numrealsplits++;
if (check->splits[checkpoint] < lowest->splits[checkpoint])
lowest = check;
}
// no one to compare against yet
if (lowest == player || numrealsplits == 0)
return;
// if there's exactly one player ahead of us, they need a blue split generated
// so they can see how far behind we are
if (lowest != player && numrealsplits == 1)
{
K_SetupSplitForPlayer(lowest, player, lowest->splits[checkpoint], player->splits[checkpoint]);
}
if (numrealsplits)
{
K_SetupSplitForPlayer(player, lowest, player->splits[checkpoint], lowest->splits[checkpoint]);
}
}
void K_CheckpointCrossAward(player_t *player)
{
if (gametype != GT_RACE)
@ -4440,6 +4514,10 @@ void K_CheckpointCrossAward(player_t *player)
{
G_SetDemoCheckpointTiming(player, leveltime - starttime, player->gradingpointnum);
}
else if (player->gradingpointnum < MAXRACESPLITS)
{
K_HandleRaceSplits(player, leveltime - starttime, player->gradingpointnum);
}
player->gradingfactor += K_GetGradingFactorAdjustment(player);
player->gradingpointnum++;

View file

@ -664,6 +664,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEMEM(save->p, players[i].public_key, PUBKEYLENGTH);
WRITEMEM(save->p, players[i].splits, sizeof(players[i].splits));
WRITEINT32(save->p, players[i].pace);
WRITESINT8(save->p, players[i].pitblame);
WRITEUINT8(save->p, players[i].instaWhipCharge);
@ -1329,6 +1332,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].vortexBoost = READFIXED(save->p);
READMEM(save->p, players[i].public_key, PUBKEYLENGTH);
READMEM(save->p, players[i].splits, sizeof(players[i].splits));
players[i].pace = READINT32(save->p);
players[i].pitblame = READSINT8(save->p);