Merge branch 'ta-rebalance-gaiden' into 'master'

TA rebalance gaiden

See merge request kart-krew-dev/ring-racers-internal!2710
This commit is contained in:
AJ Martinez 2025-08-08 01:34:11 +00:00
commit 0918e83873
8 changed files with 299 additions and 51 deletions

View file

@ -350,6 +350,14 @@ typedef enum
khud_exp,
khud_exptimer,
// Splits
khud_splittime,
khud_splitwin,
khud_splittimer,
khud_splitskin,
khud_splitcolor,
khud_splitlast,
NUMKARTHUD
} karthudtype_t;

View file

@ -884,7 +884,7 @@ extern boolean thwompsactive;
extern UINT8 lastLowestLap;
extern SINT8 spbplace;
extern boolean rainbowstartavailable;
extern tic_t linecrossed;
extern tic_t attacktimingstarted;
extern boolean inDuel;
extern UINT8 overtimecheckpoints;

View file

@ -118,7 +118,7 @@ size_t copy_fixed_buf(void* p, const void* s, size_t n)
static char demoname[MAX_WADPATH];
static savebuffer_t demobuf = {0};
static UINT8 *demotime_p, *demoinfo_p;
static UINT8 *demotime_p, *demoinfo_p, *demoattack_p, *demosplits_p;
static UINT16 demoflags;
boolean demosynced = true; // console warning message
@ -1251,19 +1251,22 @@ void G_ConsGhostTic(INT32 playernum)
void G_GhostTicker(void)
{
demoghost *g,*p;
for (g = ghosts, p = NULL; g; g = g->next)
{
UINT16 ziptic;
UINT8 xziptic;
tic_t fastforward = 0;
if (g->done)
{
continue;
}
// Pause jhosts that cross until the timer starts.
if (g->linecrossed && leveltime < starttime && G_TimeAttackStart())
if (g->attackstart != INT32_MAX && leveltime < starttime && leveltime >= g->attackstart && G_TimeAttackStart())
{
continue;
}
readghosttic:
#define follow g->mo->tracer
@ -1329,8 +1332,6 @@ fadeghost:
g->p += g->sizes.skin_name + g->sizes.color_name;
if (ziptic & DXD_WEAPONPREF)
g->p++; // ditto
if (ziptic & DXD_START)
g->linecrossed = true;
}
else if (ziptic == DW_RNG)
{
@ -1599,8 +1600,17 @@ skippedghosttic:
I_Error("Ghost is not a record attack ghost GHOSTEND"); //@TODO lmao don't blow up like this
// If the timer started, skip ahead until the ghost starts too.
if (starttime <= leveltime && !g->linecrossed && G_TimeAttackStart())
if (!fastforward && attacktimingstarted && g->attackstart != INT32_MAX && leveltime < g->attackstart && G_TimeAttackStart())
{
fastforward = g->attackstart - leveltime;
g->attackstart = INT32_MAX;
}
if (fastforward)
{
fastforward--;
goto readghosttic;
}
p = g;
#undef follow
@ -2056,6 +2066,17 @@ void G_BeginRecording(void)
demoinfo_p = demobuf.p;
WRITEUINT32(demobuf.p, 0);
// If special attack-start timing applies, we need to know where to skip the ghost to
demoattack_p = demobuf.p;
WRITEUINT32(demobuf.p, INT32_MAX);
demosplits_p = demobuf.p;
for (i = 0; i < MAXSPLITS; i++)
{
WRITEUINT32(demobuf.p, INT32_MAX);
}
// Save netvar data
CV_SaveDemoVars(&demobuf.p);
@ -2222,6 +2243,70 @@ void srb2::write_current_demo_end_marker()
*(UINT32 *)demoinfo_p = demobuf.p - demobuf.buffer;
}
void G_SetDemoAttackTiming(tic_t time)
{
if (demo.playback)
return;
*(UINT32 *)demoattack_p = time;
}
void G_SetDemoCheckpointTiming(player_t *player, tic_t time, UINT8 checkpoint)
{
if (demo.playback)
return;
if (checkpoint >= MAXSPLITS || checkpoint < 0)
return;
UINT32 *splits = (UINT32 *)demosplits_p;
splits[checkpoint] = time;
demoghost *g;
tic_t lowest = INT32_MAX;
UINT32 lowestskin = ((skin_t*)player->mo->skin) - skins;
UINT32 lowestcolor = player->skincolor;
for (g = ghosts; g; g = g->next)
{
if (lowest > g->splits[checkpoint])
{
lowest = g->splits[checkpoint];
lowestskin = ((skin_t*)g->mo->skin)-skins;
lowestcolor = g->mo->color;
}
}
if (lowest != INT32_MAX)
{
player->karthud[khud_splittimer] = 3*TICRATE;
player->karthud[khud_splitskin] = lowestskin;
player->karthud[khud_splitcolor] = lowestcolor;
player->karthud[khud_splittime] = (INT32)time - (INT32)lowest;
if (lowest < time)
{
player->karthud[khud_splitwin] = -2; // behind and losing
}
else
{
player->karthud[khud_splitwin] = 2; // ahead and gaining
}
INT32 last = player->karthud[khud_splitlast];
INT32 now = player->karthud[khud_splittime];
if (checkpoint != 0)
{
if (player->karthud[khud_splitwin] > 0 && now > last)
player->karthud[khud_splitwin] = 1; // ahead but losing
else if (player->karthud[khud_splitwin] < 0 && now < last)
player->karthud[khud_splitwin] = -1; // behind but gaining
}
player->karthud[khud_splitlast] = player->karthud[khud_splittime];
}
}
void G_SetDemoTime(UINT32 ptime, UINT32 plap)
{
if (!demo.recording || !demotime_p)
@ -2576,6 +2661,9 @@ void G_LoadDemoInfo(menudemo_t *pdemo, boolean allownonmultiplayer)
}
extrainfo_p = info.buffer + READUINT32(info.p); // The extra UINT32 read is for a blank 4 bytes?
info.p += 4; // attack start
for (i = 0; i < MAXSPLITS; i++)
info.p += 4; // splits
// Pared down version of CV_LoadNetVars to find the kart speed
pdemo->kartspeed = KARTSPEED_NORMAL; // Default to normal speed
@ -3075,6 +3163,11 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
}
demobuf.p += 4; // Extrainfo location
demobuf.p += 4; // Attack start
for (i = 0; i < MAXSPLITS; i++)
{
demobuf.p += 4; // Splits
}
// ...*map* not loaded?
if (!gamemap || (gamemap > nummapheaders) || !mapheaderinfo[gamemap-1] || mapheaderinfo[gamemap-1]->lumpnum == LUMPERROR)
@ -3491,6 +3584,13 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname)
}
p += 4; // Extra data location reference
tic_t attackstart = READUINT32(p);
UINT8 *splits = p;
for (i = 0; i < MAXSPLITS; i++)
{
p += 4;
}
// net var data
count = READUINT16(p);
@ -3582,6 +3682,9 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname)
gh->numskins = worknumskins;
gh->skinlist = skinlist;
gh->attackstart = attackstart;
std::memcpy(gh->splits, splits, sizeof(tic_t) * MAXSPLITS);
ghosts = gh;
gh->version = ghostversion;
@ -3733,6 +3836,9 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer)
}
p += 4; // Extrainfo location marker
p += 4; // Attack start info
for (i = 0; i < MAXSPLITS; i++)
p += 4; // splits
// Ehhhh don't need ghostversion here (?) so I'll reuse the var here
ghostversion = READUINT16(p);

View file

@ -159,7 +159,6 @@ extern UINT8 demo_writerng;
#define DXD_NAME 0x08 // name changed
#define DXD_COLOR 0x10 // color changed
#define DXD_FOLLOWER 0x20 // follower was changed
#define DXD_START 0x40 // Crossed the line in TA
#define DXD_ADDPLAYER (DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER)
@ -169,6 +168,8 @@ extern UINT8 demo_writerng;
#define DXD_PST_SPECTATING 0x02
#define DXD_PST_LEFT 0x03
#define MAXSPLITS (32)
boolean G_CompatLevel(UINT16 level);
// Record/playback tics
@ -203,7 +204,8 @@ struct demoghost {
UINT8 fadein;
UINT16 version;
UINT8 numskins;
boolean linecrossed;
tic_t attackstart;
tic_t splits[MAXSPLITS];
boolean done;
democharlist_t *skinlist;
mobj_t oldmo, *mo;
@ -238,6 +240,9 @@ void G_DeferedPlayDemo(const char *demo);
void G_SaveDemo(void);
void G_ResetDemoRecording(void);
void G_SetDemoAttackTiming(tic_t time);
void G_SetDemoCheckpointTiming(player_t *player, tic_t time, UINT8 checkpoint);
boolean G_CheckDemoTitleEntry(void);
typedef enum

View file

@ -314,7 +314,7 @@ boolean thwompsactive; // Thwomps activate on lap 2
UINT8 lastLowestLap; // Last lowest lap, for activating race lap executors
SINT8 spbplace; // SPB exists, give the person behind better items
boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start was gotten
tic_t linecrossed; // For Time Attack
tic_t attacktimingstarted; // For Time Attack
boolean inDuel; // Boolean, keeps track of if it is a 1v1
UINT8 overtimecheckpoints; // Duel overtime speedups!
@ -5133,7 +5133,7 @@ void G_EndGame(void)
return;
}
if (gametype == GT_TUTORIAL && M_GameAboutToStart() && restoreMenu == NULL)
if (gametype == GT_TUTORIAL && M_GameAboutToStart() && restoreMenu == NULL)
{
// Playground Hack
F_StartIntro();

View file

@ -7760,9 +7760,75 @@ void K_drawKartHUD(void)
using srb2::Draw;
Draw::TextElement text = Draw::TextElement().parse("<z> Restart");
Draw(BASEVIDWIDTH - 19, 2)
.flags(flags | V_YELLOWMAP)
.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

@ -195,7 +195,7 @@ void K_TimerReset(void)
memset(&g_musicfade, 0, sizeof g_musicfade);
numbulbs = 1;
inDuel = rainbowstartavailable = false;
linecrossed = 0;
attacktimingstarted = 0;
overtimecheckpoints = 0;
timelimitintics = extratimeintics = secretextratime = 0;
g_pointlimit = 0;
@ -348,6 +348,7 @@ void K_TimerInit(void)
if (G_TimeAttackStart())
{
starttime = TIMEATTACK_START; // Longest permitted start. No half-laps in reverse.
rainbowstartavailable = true;
// (Changed on finish line cross later, don't worry.)
}
@ -3112,6 +3113,9 @@ fixed_t K_PlayerTripwireSpeedThreshold(const player_t *player)
if (specialstageinfo.valid)
required_speed = 3 * K_GetKartSpeed(player, false, false) / 2; // 150%
if (modeattacking && !(gametyperules & GTR_CATCHER))
required_speed = 4 * K_GetKartSpeed(player, false, false);
UINT32 distance = K_GetItemRouletteDistance(player, 8);
if (gametype == GT_RACE && M_NotFreePlay() && !modeattacking)
@ -4432,6 +4436,11 @@ void K_CheckpointCrossAward(player_t *player)
if (gametype != GT_RACE)
return;
if (!demo.playback && G_TimeAttackStart())
{
G_SetDemoCheckpointTiming(player, leveltime - starttime, player->gradingpointnum);
}
player->gradingfactor += K_GetGradingFactorAdjustment(player);
player->gradingpointnum++;
player->exp = K_GetEXP(player);
@ -9049,6 +9058,11 @@ void K_KartPlayerHUDUpdate(player_t *player)
if (player->karthud[khud_trickcool])
player->karthud[khud_trickcool]--;
if (player->karthud[khud_splittimer] && !player->karthud[khud_lapanimation])
{
player->karthud[khud_splittimer]--;
}
if (player->positiondelay)
player->positiondelay--;
@ -9641,6 +9655,53 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
K_DropItems(player);
}
if (G_TimeAttackStart() && !attacktimingstarted && player->speed && leveltime > introtime)
{
attacktimingstarted = leveltime;
/*
if (starttime > leveltime) // Overlong starts shouldn't reset time on cross
{
// Award some Amps for a fast start, to counterbalance Obvious Rainbow Driftboost
tic_t starthaste = starttime - leveltime; // How much time we had left to cross
starthaste = TIMEATTACK_START - starthaste; // How much time we wasted before crossing
tic_t leniency = TICRATE*4; // How long we can take to cross with no penalty to amp payout
if (starthaste <= leniency)
starthaste = 0;
else
starthaste -= leniency;
// fixed_t ampreward = Easing_OutQuart(starthaste*FRACUNIT/TIMEATTACK_START, 60*FRACUNIT, 0);
// K_SpawnAmps(player, ampreward/FRACUNIT, player->mo);
UINT8 baseboost = 125;
player->startboost = Easing_OutQuart(starthaste*FRACUNIT/TIMEATTACK_START, baseboost, 0);
if (player->startboost == baseboost)
{
K_SpawnDriftBoostExplosion(player, 4);
K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false);
}
else
{
K_SpawnDriftBoostExplosion(player, 3);
// K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false);
}
// And reset our time to 0.
starttime = leveltime;
}
*/
starttime = leveltime;
G_SetDemoAttackTiming(leveltime);
Music_Stop("position");
}
if (player->transfer)
{
if (player->fastfall)
@ -13283,6 +13344,9 @@ static void K_KartSpindashDust(mobj_t *parent)
);
flip = P_MobjFlip(dust);
if (G_TimeAttackStart() && leveltime < starttime)
dust->scale = 3 * dust->scale / 2;
dust->momx = FixedMul(hmomentum, FINECOSINE(ang >> ANGLETOFINESHIFT));
dust->momy = FixedMul(hmomentum, FINESINE(ang >> ANGLETOFINESHIFT));
dust->momz = vmomentum * flip;
@ -13345,6 +13409,12 @@ static void K_KartSpindash(player_t *player)
{
fixed_t thrust = FixedMul(player->mo->scale, min(player->spindash, MAXCHARGETIME)*FRACUNIT/5);
if (G_TimeAttackStart() && leveltime < starttime)
{
thrust *= 2;
// player->spindashspeed += FRACUNIT/2;
}
// Old behavior, before emergency zero-ring spindash
/*
if (gametyperules & GTR_CLOSERPLAYERS)
@ -13478,16 +13548,30 @@ static void K_KartSpindash(player_t *player)
{
UINT8 ringdropframes = 2 + (player->kartspeed + player->kartweight);
boolean spawnOldEffect = true;
boolean normalsound = true;
INT16 chargetime = MAXCHARGETIME - ++player->spindash;
if (player->rings <= 0 && chargetime >= 0) // Desperation spindash
{
player->spindash++;
normalsound = false;
if (!S_SoundPlaying(player->mo, sfx_kc38))
S_StartSound(player->mo, sfx_kc38);
}
if (G_TimeAttackStart() && leveltime < starttime && chargetime >= 0)
{
if (player->spindash == 1)
{
S_ReducedVFXSound(player->mo, sfx_s3kab, player);
S_ReducedVFXSound(player->mo, sfx_s3k9c, player);
}
normalsound = false;
player->spindash += 4;
}
if (player->spindash >= SPINDASHTHRUSTTIME)
{
K_KartSpindashDust(player->mo);
@ -13525,7 +13609,7 @@ static void K_KartSpindash(player_t *player)
while ((soundcharge += ++add) < chargetime);
if (soundcharge == chargetime)
if (soundcharge == chargetime && normalsound)
{
if (spawnOldEffect == true)
K_SpawnDashDustRelease(player);
@ -14010,7 +14094,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
player->lastringboost = player->ringboost;
UINT32 award = 5*player->ringboxaward + 10;
award = 23 * award / 20; // 115% Payout Increase
if (!modeattacking)
award = 23 * award / 20; // 115% Payout Increase
if (!K_ThunderDome())
award = 3 * award / 2;
@ -14053,28 +14139,29 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
// To try and help close this gap, we fudge Ring Box payouts to allow weaker characters
// better access to things that make them go fast, without changing core handling.
UINT8 speed = player->kartspeed;
UINT8 accel = 10-player->kartspeed;
UINT8 weight = player->kartweight;
// Relative stat power for bonus TA Ring Box awards.
// AP 1, WP 2 = weight is worth twice what accel is.
// 0 = stat not considered at all!
UINT8 accelPower = 0;
UINT8 weightPower = 4;
UINT8 accelPower = 1;
UINT8 weightPower = 6;
UINT8 total = accelPower*accel + weightPower*weight;
UINT8 maxtotal = accelPower*9 + weightPower*9;
UINT32 baseaward = award;
// Scale from base payout at 9/1 to max payout at 1/9.
award = Easing_InCubic(FRACUNIT*total/maxtotal, 13*award/10, 18*award/10);
award += Easing_Linear(FRACUNIT*total/maxtotal, 0, 11*baseaward/10);
// And, because we don't have to give a damn about sandbagging, up the stakes the longer we progress!
if (gametyperules & GTR_CIRCUIT)
{
UINT8 maxgrade = 10;
UINT8 margin = min(player->gradingpointnum, maxgrade);
award = Easing_Linear(FRACUNIT * margin / maxgrade, award, 2*award);
if (K_GetNumGradingPoints())
award += Easing_Linear(FRACUNIT * player->gradingpointnum / K_GetNumGradingPoints(), 0, baseaward/2);
}
}
else
@ -16475,6 +16562,9 @@ UINT16 K_GetEXP(player_t *player)
UINT16 exp = FixedRescale(player->gradingfactor, factormin, factormax, Easing_Linear, targetminexp, targetmaxexp)>>FRACBITS;
if (modeattacking)
exp = 100 * player->gradingpointnum / numgradingpoints;
// CONS_Printf("Player %s numgradingpoints=%d gradingpoint=%d targetminexp=%d targetmaxexp=%d factor=%.2f factormin=%.2f factormax=%.2f exp=%d\n",
// player_names[player - players], numgradingpoints, player->gradingpointnum, targetminexp, targetmaxexp, FIXED_TO_FLOAT(player->gradingfactor), FIXED_TO_FLOAT(factormin), FIXED_TO_FLOAT(factormax), exp);

View file

@ -2038,34 +2038,6 @@ static void K_HandleLapIncrement(player_t *player)
K_UpdateAllPlayerPositions(); // P_DoPlayerExit calls this
}
if (G_TimeAttackStart() && !linecrossed)
{
linecrossed = leveltime;
if (starttime > leveltime) // Overlong starts shouldn't reset time on cross
{
// Award some Amps for a fast start, to counterbalance Obvious Rainbow Driftboost
tic_t starthaste = starttime - leveltime; // How much time we had left to cross
starthaste = TIMEATTACK_START - starthaste; // How much time we wasted before crossing
tic_t leniency = TICRATE*2; // How long we can take to cross with no penalty to amp payout
if (starthaste <= leniency)
starthaste = 0;
else
starthaste -= leniency;
fixed_t ampreward = Easing_OutQuart(starthaste*FRACUNIT/TIMEATTACK_START, 100*FRACUNIT, 0);
K_SpawnAmps(player, ampreward/FRACUNIT, player->mo);
// And reset our time to 0.
starttime = leveltime;
}
if (demo.recording)
demo_extradata[player-players] |= DXD_START;
Music_Stop("position");
}
if (rainbowstartavailable == true && player->mo->hitlag == 0)
{
if (K_InRaceDuel())
@ -2082,7 +2054,8 @@ static void K_HandleLapIncrement(player_t *player)
K_SpawnDriftBoostExplosion(player, 4);
K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false);
K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 35, player->mo);
if (!G_TimeAttackStart())
K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 35, player->mo);
if (g_teamplay)
{