From 27ea5021b469324130c534c80580d8c222ee2c89 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Sat, 31 May 2025 05:10:46 -0400 Subject: [PATCH 01/13] Add Flipover damage, change Invincibility interactions --- src/d_player.h | 6 ++++ src/deh_tables.c | 1 + src/info/sounds.h | 3 ++ src/k_collide.c | 4 +-- src/k_kart.c | 90 +++++++++++++++++++++++++++++++++++++++++++---- src/k_kart.h | 27 ++++++++++++++ src/k_stats.c | 4 ++- src/p_enemy.c | 6 ++-- src/p_inter.c | 12 +++++-- src/p_local.h | 13 +++---- src/p_map.c | 10 +++++- src/p_mobj.c | 2 +- src/p_saveg.c | 4 +++ src/p_user.c | 14 +++++--- 14 files changed, 167 insertions(+), 29 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index df70e2dd1..548138cc1 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -110,6 +110,8 @@ typedef enum PF_SHRINKME = 1<<25, // "Shrink me" cheat preference PF_SHRINKACTIVE = 1<<26, // "Shrink me" cheat is in effect. (Can't be disabled mid-race) + PF_JUSTFLIPPED = 1<<27, // Just got flipped over, handle the bump interaction. + // up to 1<<29 is free PF_ATTACKDOWN = 1<<30, // For lua compat, don't use! PF_SLIDING = 1<<31, // For lua compat, don't use! @@ -619,6 +621,10 @@ struct player_t UINT16 flashing; UINT16 spinouttimer; // Spin-out from a banana peel or oil slick (was "pw_bananacam") UINT8 spinouttype; // Determines the mode of spinout/wipeout, see kartspinoutflags_t + + UINT16 flipovertimer; // Flipped over by a player using Invincibility. + angle_t flipoverangle; // Movement angle for a flipped-over player. + UINT8 instashield; // Instashield no-damage animation timer UINT8 wipeoutslow; // Timer before you slowdown when getting wiped out UINT8 justbumped; // Prevent players from endlessly bumping into each other diff --git a/src/deh_tables.c b/src/deh_tables.c index d7f3deeee..1d94c60e0 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1039,6 +1039,7 @@ struct int_const_s const INT_CONST[] = { {"DMG_SQUISH",DMG_SQUISH}, {"DMG_VOLTAGE",DMG_VOLTAGE}, {"DMG_KARMA",DMG_KARMA}, + {"DMG_FLIPOVER",DMG_FLIPOVER}, //// Death types {"DMG_INSTAKILL",DMG_INSTAKILL}, {"DMG_DEATHPIT",DMG_DEATHPIT}, diff --git a/src/info/sounds.h b/src/info/sounds.h index 25d8694aa..97f198731 100644 --- a/src/info/sounds.h +++ b/src/info/sounds.h @@ -786,6 +786,9 @@ _(kc6c) _(kc6d) _(kc6e) +// MKDS sounds +_(mdse8) + // SRB2kart _(slip) _(screec) diff --git a/src/k_collide.c b/src/k_collide.c index 0298c0dff..a8552a9f1 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -706,12 +706,12 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) } else if (t1Condition == true && t2Condition == false) { - P_DamageMobj(t2, t1, t1, 1, DMG_WIPEOUT); + P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER); return true; } else if (t1Condition == false && t2Condition == true) { - P_DamageMobj(t1, t2, t2, 1, DMG_WIPEOUT); + P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER); return true; } diff --git a/src/k_kart.c b/src/k_kart.c index 27dcb174d..0dd245d74 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1896,6 +1896,13 @@ fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) return weight; } +// Checks if the bump interaction was a flip-over. +static boolean K_CheckMobjFlippedOver(mobj_t* mobj1, mobj_t* mobj2) +{ + return ((mobj1->player && (mobj1->player->pflags & PF_JUSTFLIPPED)) || + (mobj2->player && (mobj2->player->pflags & PF_JUSTFLIPPED))); +} + boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) { mobj_t *fx; @@ -1954,6 +1961,19 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol return false; } + // Don't bump if you're flipping over + if (mobj1->player && mobj1->player->flipovertimer && (!(mobj1->player->pflags & PF_JUSTFLIPPED))) + { + mobj1->player->justbumped = bumptime; + return false; + } + + if (mobj2->player && mobj2->player->flipovertimer && (!(mobj2->player->pflags & PF_JUSTFLIPPED))) + { + mobj2->player->justbumped = bumptime; + return false; + } + mass1 = K_GetMobjWeight(mobj1, mobj2); if (solid == true && mass1 > 0) @@ -2040,7 +2060,7 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol // Do the bump fx when we've CONFIRMED we can bump. if ((mobj1->player && mobj1->player->itemtype == KITEM_BUBBLESHIELD) || (mobj2->player && mobj2->player->itemtype == KITEM_BUBBLESHIELD)) S_StartSound(mobj1, sfx_s3k44); - else + else if (!K_CheckMobjFlippedOver(mobj1, mobj2)) S_StartSound(mobj1, sfx_s3k49); fx = P_SpawnMobj(mobj1->x/2 + mobj2->x/2, mobj1->y/2 + mobj2->y/2, mobj1->z/2 + mobj2->z/2, MT_BUMP); @@ -2065,6 +2085,15 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol P_PlayerRingBurst(mobj1->player, 1); } + if (mobj1->player->pflags & PF_JUSTFLIPPED) + { + S_StartSound(mobj2, sfx_mdse8); + mobj1->player->flipoverangle = R_PointToAngle2(0,0,mobj1->player->rmomx,mobj1->player->rmomy); + P_InstaThrust(mobj1, mobj1->player->flipoverangle, FixedMul(FLIPOVERSPEED, mobj1->scale)); + mobj1->momz = FixedMul(FlipOverZMomentum(gravity), mobj1->scale); + mobj1->player->pflags &= ~PF_JUSTFLIPPED; + } + if (mobj1->player->spinouttimer) { mobj1->player->wipeoutslow = wipeoutslowtime+1; @@ -2091,6 +2120,15 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol mobj2->player->spinouttimer = max(wipeoutslowtime+1, mobj2->player->spinouttimer); //mobj2->player->spinouttype = KSPIN_WIPEOUT; // Enforce type } + + if (mobj2->player->pflags & PF_JUSTFLIPPED) + { + S_StartSound(mobj1, sfx_mdse8); + mobj2->player->flipoverangle = R_PointToAngle2(0,0,mobj2->player->rmomx,mobj2->player->rmomy); + P_InstaThrust(mobj2, mobj2->player->flipoverangle, FixedMul(FLIPOVERSPEED, mobj2->scale)); + mobj2->momz = FixedMul(FlipOverZMomentum(gravity), mobj2->scale); + mobj2->player->pflags &= ~PF_JUSTFLIPPED; + } } return true; @@ -3878,7 +3916,10 @@ SINT8 K_GetForwardMove(player_t *player) } } - if ((player->exiting || mapreset) || player->pflags & PF_STASIS || player->spinouttimer) // pw_introcam? + if ((player->exiting || mapreset) || + player->pflags & PF_STASIS || + player->spinouttimer || + player->flipovertimer) // pw_introcam? { forwardmove = 0; if (player->sneakertimer) @@ -4088,6 +4129,19 @@ void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 typ P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); } +void K_FlipPlayer(player_t *player, mobj_t *inflictor, mobj_t *source) +{ + (void)inflictor; + (void)source; + + K_DirectorFollowAttack(player, inflictor, source); + + K_StatPlayerHit(player, source ? source->player : NULL); + player->flipovertimer = 1; + player->pflags |= PF_JUSTFLIPPED; + P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); +} + static void K_RemoveGrowShrink(player_t *player) { if (player->mo && !P_MobjWasRemoved(player->mo)) @@ -4194,6 +4248,11 @@ INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source) // A return ringburst; } +boolean K_IsPlayerDamaged(player_t *player) +{ + return ((player->spinouttimer > 0) || (player->flipovertimer > 0)); +} + void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) { if (!(gametyperules & GTR_BUMPERS)) @@ -7398,7 +7457,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_UpdateEngineSounds(player); // Thanks, VAda! // update boost angle if not spun out - if (!player->spinouttimer && !player->wipeoutslow) + if (!K_IsPlayerDamaged(player) && !player->wipeoutslow) player->boostangle = player->mo->angle; K_GetKartBoostPower(player); @@ -7473,7 +7532,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) // Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations. if (player->spinouttimer != 0 || player->wipeoutslow != 0 - || player->squishedtimer != 0) + || player->squishedtimer != 0 + || player->flipovertimer != 0) { player->flashing = K_GetKartFlashing(player); } @@ -7482,6 +7542,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->flashing--; } + if (player->flipovertimer) + { + player->flipovertimer++; + + // Kill off any extra damage values. + player->spinouttimer = 0; + player->wipeoutslow = 0; + + if (P_IsObjectOnGround(player->mo) && + ((INT32)(player->flipovertimer - 1) > 2)) + { + player->flipovertimer = 0; + player->mo->rollangle = 0; + } + } + if (player->spinouttimer) { if ((P_IsObjectOnGround(player->mo) @@ -10009,12 +10085,12 @@ static void K_AdjustPlayerFriction(player_t *player) player->mo->movefactor = 32; } - // Wipeout slowdown - if (player->speed > 0 && player->spinouttimer && player->wipeoutslow) + // Wipeout slowdown, or getting flipped over + if ((player->speed > 0 && player->spinouttimer && player->wipeoutslow) || (player->flipovertimer)) { if (player->offroad) player->mo->friction -= 4912; - if (player->wipeoutslow == 1) + if ((player->wipeoutslow == 1) || (player->flipovertimer)) player->mo->friction -= 9824; } } diff --git a/src/k_kart.h b/src/k_kart.h index f3d42d726..095cbd7d7 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -37,6 +37,31 @@ Make sure this matches the actual number of states #define FLAMESTOREMAX TICRATE*2 +// Fixed distance of a flipover. +#define FLIPOVERDIST (112<>1) + +#define FLIPOVER_90DEGTIME ((FLIPOVERTICS>>2)+1) // 0.1666 * FRACUNIT + +// Angle movement for a flipover +// ANGLE_90 / FLIPOVER_90DEGTIME +// Precalculating it here for some slight optimization. +#define FLIPOVERANG 0x0CCCCCCC + +#define FLIPOVERSPEED (FLIPOVERDIST / FLIPOVERTICS) + +#define FLIPOVERHEIGHT (32 << FRACBITS) + +// By the middle of a jump arc, you'll reach the apex. +#define FlipOverZMomentum(grav) \ + ((FLIPOVERHEIGHT + \ + ((grav >> 1) * (FLIPOVERHALFTICS * FLIPOVERHALFTICS))) / \ + FLIPOVERHALFTICS) + // Precalculated constants for stacked boost diminishing // *Somewhat* matches old calc but doesn't use arrays, which makes it faster and more memory efficent #define DIMINISHPARAM K_RAGuard(cv_kartstacking_diminishparam) @@ -153,7 +178,9 @@ void K_AwardPlayerRings(player_t *player, UINT16 rings, boolean overload); void K_DoInstashield(player_t *player); void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UINT8 bumpersRemoved); void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 type); +void K_FlipPlayer(player_t *player, mobj_t *inflictor, mobj_t *source); INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source); +boolean K_IsPlayerDamaged(player_t *player); void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers); void K_DestroyBumpers(player_t *player, UINT8 amount); void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount); diff --git a/src/k_stats.c b/src/k_stats.c index 8ddcffa6a..bfa177167 100644 --- a/src/k_stats.c +++ b/src/k_stats.c @@ -31,7 +31,9 @@ void K_StatTicker(void) if (p->position == spbplace) kartstats.spbtargettime++; - if (max(p->spinouttimer, p->wipeoutslow) > 0) + if (p->flipovertimer > 0) + kartstats.spinouttime++; + else if (max(p->spinouttimer, p->wipeoutslow) > 0) kartstats.spinouttime++; } } diff --git a/src/p_enemy.c b/src/p_enemy.c index 9f4fa86c6..532eba9c1 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -11324,7 +11324,7 @@ void A_RandomShadowFrame(mobj_t *actor) if (actor->target && !actor->target->player->flashing && !actor->target->player->invincibilitytimer && !actor->target->player->growshrinktimer - && !actor->target->player->spinouttimer + && !K_IsPlayerDamaged(actor->target->player) && P_IsObjectOnGround(actor->target) && actor->z == actor->target->z) { @@ -11369,7 +11369,7 @@ void A_RoamingShadowThinker(mobj_t *actor) if (actor->target && !actor->target->player->flashing && !actor->target->player->invincibilitytimer && !actor->target->player->growshrinktimer - && !actor->target->player->spinouttimer) + && !K_IsPlayerDamaged(actor->target->player)) { // send them flying and spawn the WIND! P_InstaThrust(actor->target, 0, 0); @@ -11611,7 +11611,7 @@ void A_ReaperThinker(mobj_t *actor) if (!(actor->target == targetplayermo && actor->target && !actor->target->player->flashing && !actor->target->player->invincibilitytimer && !actor->target->player->growshrinktimer - && !actor->target->player->spinouttimer)) + && !K_IsPlayerDamaged(actor->target->player))) P_SetTarget(&actor->target, actor->hnext); // if the above isn't correct, then we should go back to targetting waypoints or something. } diff --git a/src/p_inter.c b/src/p_inter.c index 4e2af4a9c..347b54887 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -127,7 +127,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) { // Invulnerable if (player->flashing > 0 - || player->spinouttimer > 0 + || K_IsPlayerDamaged(player) || player->squishedtimer > 0 || player->invincibilitytimer > 0 || player->growshrinktimer > 0 @@ -2055,7 +2055,7 @@ static UINT8 P_ShouldHookDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sourc status = shouldDamage; - if (shouldSpin > 0 && ((type == DMG_NORMAL) || (type == DMG_WIPEOUT))) + if (shouldSpin > 0 && ((type == DMG_NORMAL) || (type == DMG_WIPEOUT) || (type == DMG_FLIPOVER))) status = shouldSpin; else if (shouldExplode > 0 && ((type == DMG_EXPLODE) || (type == DMG_KARMA))) status = shouldExplode; @@ -2266,7 +2266,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_DestroyBumpers(player, 1); } - if (!(type == DMG_NORMAL || type == DMG_WIPEOUT || type == DMG_VOLTAGE)) + if (!(type == DMG_NORMAL || type == DMG_WIPEOUT || type == DMG_VOLTAGE || type == DMG_FLIPOVER)) { player->sneakertimer = 0; player->mo->flags2 &= ~MF2_WATERRUN; @@ -2345,6 +2345,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_SquishPlayer(player, inflictor, source); LUA_HookPlayerSquish(target, inflictor, source, damage, damagetype); break; + case DMG_FLIPOVER: + if (P_IsDisplayPlayer(player)) + P_StartQuake(32<flags & MF_NOCLIPTHING) || !(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE|MF_SPRING))) return BMIT_CONTINUE; + // Get rid of the "just flipped" flag. + // We only use that to confirm flipover hits. + if (thing->player) + thing->player->pflags &= ~PF_JUSTFLIPPED; + + if (g_tm.thing->player) + g_tm.thing->player->pflags &= ~PF_JUSTFLIPPED; + blockdist = thing->radius + g_tm.thing->radius; if (abs(thing->x - g_tm.x) >= blockdist || abs(thing->y - g_tm.y) >= blockdist) @@ -1267,7 +1275,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) } // no interaction - if (g_tm.thing->player->flashing > 0 || g_tm.thing->player->hyudorotimer > 0 || g_tm.thing->player->spinouttimer > 0) + if (g_tm.thing->player->flashing > 0 || g_tm.thing->player->hyudorotimer > 0 || K_IsPlayerDamaged(g_tm.thing->player)) return BMIT_CONTINUE; // collide diff --git a/src/p_mobj.c b/src/p_mobj.c index e5053ac82..35f31fe0c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -2765,7 +2765,7 @@ void P_PlayerZMovement(mobj_t *mo) K_UpdateMobjTerrain(mo, (mo->eflags & MFE_VERTICALFLIP ? g_tm.ceilingpic : g_tm.floorpic)); // Get up if you fell. - if (mo->player->panim == PA_HURT && mo->player->spinouttimer == 0 && mo->player->squishedtimer == 0) + if (mo->player->panim == PA_HURT && !K_IsPlayerDamaged(mo->player) && mo->player->squishedtimer == 0) { P_SetPlayerMobjState(mo, S_KART_STILL); } diff --git a/src/p_saveg.c b/src/p_saveg.c index 3db594de2..e9758af49 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -256,6 +256,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT16(save->p, players[i].flashing); WRITEUINT16(save->p, players[i].spinouttimer); WRITEUINT8(save->p, players[i].spinouttype); + WRITEUINT16(save->p, players[i].flipovertimer); + WRITEANGLE(save->p, players[i].flipoverangle); WRITEINT16(save->p, players[i].squishedtimer); WRITEUINT8(save->p, players[i].instashield); WRITEUINT8(save->p, players[i].wipeoutslow); @@ -595,6 +597,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].flashing = READUINT16(save->p); players[i].spinouttimer = READUINT16(save->p); players[i].spinouttype = READUINT8(save->p); + players[i].flipoverangle = READUINT16(save->p); + players[i].flipovertimer = READANGLE(save->p); players[i].squishedtimer = READINT16(save->p); players[i].instashield = READUINT8(save->p); players[i].wipeoutslow = READUINT8(save->p); diff --git a/src/p_user.c b/src/p_user.c index dc9b55e50..eb9d5207f 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -464,7 +464,7 @@ UINT8 P_FindHighestLap(void) // boolean P_PlayerInPain(player_t *player) { - if (player->spinouttimer || player->squishedtimer) + if (K_IsPlayerDamaged(player) || player->squishedtimer) return true; return false; @@ -1374,7 +1374,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean fromAir) // Cut momentum in half when you hit the ground and // aren't pressing any controls. if (!(player->cmd.forwardmove || player->cmd.sidemove) && !player->cmomx && !player->cmomy - && !(player->spinouttimer)) + && !K_IsPlayerDamaged(player)) { player->mo->momx = player->mo->momx/2; player->mo->momy = player->mo->momy/2; @@ -1806,7 +1806,7 @@ static void P_3dMovement(player_t *player) { movepushangle = player->mo->angle - (ANGLE_45/5) * player->drift; } - else if (player->spinouttimer || player->wipeoutslow) // if spun out, use the boost angle + else if (K_IsPlayerDamaged(player) || player->wipeoutslow) // if spun out, use the boost angle { movepushangle = (angle_t)player->boostangle; } @@ -1896,13 +1896,13 @@ static void P_3dMovement(player_t *player) totalthrust.x += P_ReturnThrustX(player->mo, movepushangle, movepushforward); totalthrust.y += P_ReturnThrustY(player->mo, movepushangle, movepushforward); } - else if (!(player->spinouttimer)) + else if (!K_IsPlayerDamaged(player)) { K_MomentumToFacing(player); } // Sideways movement - if (cmd->sidemove != 0 && !((player->exiting || mapreset) || player->spinouttimer)) + if (cmd->sidemove != 0 && !((player->exiting || mapreset) || K_IsPlayerDamaged(player))) { if (cmd->sidemove > 0) movepushside = (cmd->sidemove * FRACUNIT/128) + FixedDiv(player->speed, K_GetKartSpeed(player, true, true)); @@ -2169,6 +2169,10 @@ void P_MovePlayer(player_t *player) player->glanceDir = 0; player->pflags &= ~PF_GAINAX; } + else if (player->flipovertimer > 0) + { + player->mo->rollangle = (angle_t)(player->flipovertimer - 1) * FLIPOVERANG; + } else if ((player->spinouttimer > 0)) { UINT16 speed = player->spinouttimer/8; From 6788db93b437f93564f11d03cc468b2dc0090e03 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Mon, 2 Jun 2025 14:21:35 -0400 Subject: [PATCH 02/13] Add clustering Using a DBSCAN-based algorithm, this gathers "player clusters" to determine the most packed area in the race. --- src/Sourcefile | 1 + src/k_cluster.cpp | 238 ++++++++++++++++++++++++++++++++++++++++++++++ src/k_cluster.hpp | 18 ++++ 3 files changed, 257 insertions(+) create mode 100644 src/k_cluster.cpp create mode 100644 src/k_cluster.hpp diff --git a/src/Sourcefile b/src/Sourcefile index 05c35b7a3..7d9d18c75 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -122,6 +122,7 @@ k_bheap.c k_bot.cpp k_botitem.cpp k_botsearch.cpp +k_cluster.cpp k_grandprix.c k_boss.c k_hud.c diff --git a/src/k_cluster.cpp b/src/k_cluster.cpp new file mode 100644 index 000000000..ec04b33a2 --- /dev/null +++ b/src/k_cluster.cpp @@ -0,0 +1,238 @@ +#include "doomdef.h" +#include "doomstat.h" +#include "hu_stuff.h" +#include "d_player.h" +#include "m_fixed.h" +#include "k_kart.h" +#include "r_main.h" +#include "g_game.h" + +#include "k_cluster.hpp" +#include + +static fixed_t K_Distance3D(fixed_t x1, fixed_t y1, fixed_t z1, fixed_t x2, fixed_t y2, fixed_t z2) +{ + fixed_t dist_xy = R_PointToDist2(x1,y1,x2,y2); + + return R_PointToDist2(0,z1,dist_xy,z2); +} + +static fixed_t K_PlayerDistance3D(player_t *source, player_t *destination) +{ + if ((!source->mo) || (!destination->mo)) + return INT32_MAX; // Return a garbage value. + + return K_Distance3D(source->mo->x,source->mo->y,source->mo->z,destination->mo->x,destination->mo->y,destination->mo->z); +} + +static UINT32 K_GetDTFDifference(player_t *source, player_t *destination) +{ + if ((!source->mo) || (!destination->mo)) + return INT16_MAX; // Return a garbage value. + + if (destination->distancetofinish > source->distancetofinish) + return (destination->distancetofinish - source->distancetofinish); + + return (source->distancetofinish - destination->distancetofinish); +} + +extern "C" { +player_t *closesttocluster; + +INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, vector3_t *out, void *nodevec) +{ + INT64 meanx, meany, meanz; + INT32 N; + N = 0; + vector3_t neighborvector; + std::vectorclusternodes; + std::vector *realvec; + + if (nodevec != nullptr) + realvec = static_cast *>(nodevec); + + if (!sourcePlayer->mo) + return 0; + + meanx = meany = meanz = 0; + + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player; + + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player == sourcePlayer) + continue; // Ignore ourselves. + + if (clusterplayer[i]) + continue; // Ignore players we've scanned already. + + if (player->spectator) + continue; // spectator + + if (!player->mo) + continue; + + // Scan all points in the database + if (K_PlayerDistance3D(sourcePlayer, player) <= eps) + { + clusterplayer[i] = true; + clusternodes.push_back(player); + + if (realvec) + realvec->push_back(player); + + // Scan our neighbor for any players close to them. + K_CountNeighboringPlayersByDistance(player, eps, &neighborvector, &clusternodes); + } + } + + N = clusternodes.size(); + + if (N != 0) + { + // Return the average center point of this cluster. + for (i = 0; i < N; i++) + { + if (!clusternodes[i]) + continue; + + if (!clusternodes[i]->mo) + continue; + + meanx += clusternodes[i]->mo->x / FRACUNIT; + meany += clusternodes[i]->mo->y / FRACUNIT; + meanz += clusternodes[i]->mo->z / FRACUNIT; + } + + meanx /= N; + meany /= N; + meanz /= N; + + //CONS_Printf("mean x: %lld, mean y: %lld, mean z: %lld\n", meanx, meany, meanz); + + out->x = (fixed_t)(meanx) << FRACBITS; + out->y = (fixed_t)(meany) << FRACBITS; + out->z = (fixed_t)(meanz) << FRACBITS; + + // Get the player closest to the cluster. + fixed_t disttocluster, bestdist; + bestdist = INT32_MAX; + for (i = 0; i < N; i++) + { + disttocluster = K_Distance3D(clusternodes[i]->mo->x,clusternodes[i]->mo->y,clusternodes[i]->mo->z,out->x,out->y,out->z); + + if (disttocluster < bestdist) + { + closesttocluster = clusternodes[i]; + bestdist = disttocluster; + } + } + } + else + { + out->z = out->y = out->x = 0; + } + + return N; +} + +static std::vector dtf_vec; +static boolean cleardtf = true; + +INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector3_t *out) +{ + INT64 mean; + INT32 N = 0; + vector3_t neighborvector; + + if (!sourcePlayer->mo) + return 0; + + if (cleardtf) + dtf_vec.clear(); + + mean = 0; + + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player; + + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player == sourcePlayer) + continue; // Ignore ourselves. + + if (clusterplayer[i]) + continue; // Ignore players we've scanned already. + + if (player->spectator) + continue; // spectator + + if (!player->mo) + continue; + + // Scan all points in the database + if ((fixed_t)K_GetDTFDifference(sourcePlayer, player) <= eps) + { + //CONS_Printf("%d is less than %d, adding to nodes vector\n", K_GetDTFDifference(sourcePlayer, player), eps); + clusterplayer[i] = true; + dtf_vec.push_back(player); // Add to result + + cleardtf = false; + + // Scan our neighbor for any players close to them. + K_CountNeighboringPlayersByDTF(player, eps, &neighborvector); + + cleardtf = true; + } + } + + N = dtf_vec.size(); + + if (N != 0) + { + // Return the average center point of this cluster. + for (i = 0; i < N; i++) + { + if (!dtf_vec[i]) + continue; + + if (!dtf_vec[i]->mo) + continue; + + mean += dtf_vec[i]->distancetofinish; + } + mean /= N; + + out->x = (fixed_t)(mean); + out->y = 0; + out->z = 0; + } + else + { + out->z = out->y = out->x = 0; + } + + return N; +} + +INT32 K_CountNeighboringPlayers(player_t *sourcePlayer, fixed_t eps, vector3_t *out) +{ + // Dummy vector so the game doesn't SIGSEGV. + // There's probably a better solution to this. + std::vector dummy = {0}; + + return K_CountNeighboringPlayersByDistance(sourcePlayer, eps, out, &dummy); +} + +} \ No newline at end of file diff --git a/src/k_cluster.hpp b/src/k_cluster.hpp new file mode 100644 index 000000000..874cac269 --- /dev/null +++ b/src/k_cluster.hpp @@ -0,0 +1,18 @@ +#include "doomdef.h" +#include "d_player.h" +#include "m_fixed.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern player_t *closesttocluster; + +INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, vector3_t *out, void *nodevec); +INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector3_t *out); +INT32 K_CountNeighboringPlayers(player_t *sourcePlayer, fixed_t eps, vector3_t *out); + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 99056a47fa143d802b0d6022f54dd7ec39b3beb1 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Mon, 2 Jun 2025 14:23:12 -0400 Subject: [PATCH 03/13] Tie Invincibility to clustering, implement new visual * Invincibility's time limit is now directly tied to a player's distance from the cluster. * The visual for Invincibility has been reworked to being an additive layer over the player, fading out as you run out of Invincibility. --- src/d_netcmd.c | 9 +- src/d_netcmd.h | 4 +- src/d_player.h | 1 + src/k_collide.c | 9 +- src/k_hud.c | 101 +++++++++++++++++++-- src/k_kart.c | 227 ++++++++++++++++++++++++++++++++++++++++++++++-- src/k_kart.h | 13 ++- src/p_mobj.c | 76 ++++++++++++++++ src/p_setup.c | 1 + src/p_tick.c | 1 + src/p_user.c | 10 ++- 11 files changed, 432 insertions(+), 20 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index d22cea64b..a03713fe4 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -462,7 +462,7 @@ consvar_t cv_kartstacking_sneaker_accelboost = CVAR_INIT ("vanillaboost_sneaker_ consvar_t cv_kartstacking_sneaker_maxgrade = CVAR_INIT ("vanillaboost_sneaker_maxgrade", "3", CV_NETVAR|CV_CHEAT, CV_Natural, NULL); consvar_t cv_kartstacking_sneaker_stackable = CVAR_INIT ("vanillaboost_sneaker_stackable", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_kartstacking_invincibility_speedboost = CVAR_INIT ("vanillaboost_invincibility_speedboost", "0.375", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_speedboost = CVAR_INIT ("vanillaboost_invincibility_speedboost", "0.68", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); consvar_t cv_kartstacking_invincibility_accelboost = CVAR_INIT ("vanillaboost_invincibility_accelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); consvar_t cv_kartstacking_invincibility_stackable = CVAR_INIT ("vanillaboost_invincibility_stackable", "Off", CV_NETVAR, CV_OnOff, NULL); @@ -503,6 +503,12 @@ consvar_t cv_kartbumpspring = CVAR_INIT ("kartbumpspring", "No", CV_NETVAR, CV_Y consvar_t cv_kartslipdash = CVAR_INIT ("kartslipdash", "No", CV_NETVAR|CV_CALL|CV_NOINIT, CV_YesNo, KartSlipdash_OnChange); +// How far the player must be from the cluster to begin frequently rolling Invincibility. +static CV_PossibleValue_t invindist_cons_t[] = {{1, "MIN"}, {32000, "MAX"}, {0, NULL}}; +consvar_t cv_kartinvindist = CVAR_INIT ("kartinvindist", "8400", CV_NETVAR|CV_CHEAT, invindist_cons_t, NULL); + +consvar_t cv_kartinvindistmul = CVAR_INIT ("kartinvindistmul", "0.54", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); + static CV_PossibleValue_t kartdebugitem_cons_t[] = { #define FOREACH( name, n ) { n, #name } @@ -527,6 +533,7 @@ static CV_PossibleValue_t kartdebugwaypoint_cons_t[] = {{0, "Off"}, {1, "Forward consvar_t cv_kartdebugwaypoints = CVAR_INIT ("kartdebugwaypoints", "Off", CV_NETVAR|CV_CHEAT, kartdebugwaypoint_cons_t, NULL); consvar_t cv_kartdebuglap = CVAR_INIT ("kartdebuglap", "Off", CV_NETVAR|CV_CHEAT, kartdebugwaypoint_cons_t, NULL); consvar_t cv_kartdebugbot = CVAR_INIT ("kartdebugbot", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); +consvar_t cv_kartdebugcluster = CVAR_INIT ("kartdebugcluster", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); consvar_t cv_kartdebugcheckpoint = CVAR_INIT ("kartdebugcheckpoint", "Off", 0, CV_OnOff, NULL); consvar_t cv_kartdebugnodes = CVAR_INIT ("kartdebugnodes", "Off", 0, CV_OnOff, NULL); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index e5228922c..77099e1b4 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -171,6 +171,8 @@ extern consvar_t cv_kartpurpledrift; extern consvar_t cv_kartbumpspark; extern consvar_t cv_kartbumpspring; extern consvar_t cv_kartslipdash; +extern consvar_t cv_kartinvindist; +extern consvar_t cv_kartinvindistmul; extern consvar_t cv_encorevotes; @@ -179,7 +181,7 @@ extern consvar_t cv_votetime; extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution, cv_kartdebughuddrop; extern consvar_t cv_kartdebugshrink; extern consvar_t cv_kartdebugcheckpoint, cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; -extern consvar_t cv_kartdebugwaypoints, cv_kartdebuglap, cv_kartdebugbot; +extern consvar_t cv_kartdebugwaypoints, cv_kartdebuglap, cv_kartdebugbot, cv_kartdebugcluster; extern consvar_t cv_itemfinder; diff --git a/src/d_player.h b/src/d_player.h index 548138cc1..fdaf9ac09 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -607,6 +607,7 @@ struct player_t UINT8 positiondelay; // Used for position number, so it can grow when passing/being passed UINT32 distancetofinish; UINT32 distancetofinishprev; + UINT32 distancefromcluster; waypoint_t *currentwaypoint; waypoint_t *nextwaypoint; UINT16 bigwaypointgap; diff --git a/src/k_collide.c b/src/k_collide.c index a8552a9f1..b734db9d8 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -687,6 +687,11 @@ boolean K_SMKIceBlockCollide(mobj_t *t1, mobj_t *t2) return false; } +boolean K_CanInvincibilityDamage(UINT16 timer) +{ + return (K_InvincibilityGradient(timer) > (FRACUNIT/2)); +} + boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) { const boolean flameT1 = ((t1->player->flamedash > 0) && (t1->player->flametimer > 0)); @@ -695,8 +700,8 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) boolean t1Condition = false; boolean t2Condition = false; - t1Condition = (t1->player->invincibilitytimer > 0); - t2Condition = (t2->player->invincibilitytimer > 0); + t1Condition = ((t1->player->invincibilitytimer > 0) && (K_CanInvincibilityDamage(t1->player->invincibilitytimer))); + t2Condition = ((t2->player->invincibilitytimer > 0) && (K_CanInvincibilityDamage(t2->player->invincibilitytimer))); if ((t1Condition == true || flameT1 == true) && (t2Condition == true || flameT2 == true)) { diff --git a/src/k_hud.c b/src/k_hud.c index 020c9a4e3..b1169e0d5 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1758,6 +1758,15 @@ static void K_DrawKartPositionNum(INT32 num) } } +static UINT32 K_InvincibilityHUDVisibility(UINT16 t) +{ + UINT32 alphalevel = st_translucency; + + alphalevel = min(10, FixedMul(alphalevel, K_InvincibilityGradient(t))); + + return min(9, 10 - alphalevel)<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 @@ -1862,6 +1873,14 @@ static boolean K_drawKartPositionFaces(void) V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, faceprefix[players[rankplayer[i]].skin][FACE_RANK], colormap); + if (players[rankplayer[i]].invincibilitytimer) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, K_RainbowColor(leveltime / 2), GTC_CACHE); + invinchudtrans = K_InvincibilityHUDVisibility(players[rankplayer[i]].invincibilitytimer); + + V_DrawMappedPatch(FACE_X, Y, invinchudtrans|V_SNAPTOLEFT|V_ADD, faceprefix[players[rankplayer[i]].skin][FACE_RANK], colormap); + } + if (LUA_HudEnabled(hud_battlebumpers)) { if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumper > 0) @@ -3438,6 +3457,20 @@ static void K_drawKartMinimapWaypoint(waypoint_t *wp, INT32 hudx, INT32 hudy, IN K_drawKartMinimapDot(wp->mobj->x, wp->mobj->y, hudx, hudy, flags | V_NOSCALESTART, pal, size); } +static void K_drawKartMinimapCluster(INT32 hudx, INT32 hudy, INT32 flags) +{ + UINT8 pal = 180; // Strong pink color. + UINT8 size = 6; + + if (!(flags & V_NOSCALESTART)) + { + hudx *= vid.dupx; + hudy *= vid.dupy; + } + + K_drawKartMinimapDot(clusterpoint.x, clusterpoint.y, hudx, hudy, flags | V_NOSCALESTART, pal, size); +} + #define ICON_DOT_RADIUS (cv_minihead.value && !cv_showminimapnames.value) ? 8 : 10 typedef struct @@ -3487,6 +3520,8 @@ static void K_drawKartMinimap(void) 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; // Draw the HUD only when playing in a level. // hu_stuff needs this, unlike st_stuff. @@ -3533,6 +3568,8 @@ static void K_drawKartMinimap(void) if (!minimaptrans) return; + colorizeplayer = false; + minimaptrans = ((10-minimaptrans)<colorized; + + if ((players[i].invincibilitytimer) && (K_InvincibilityGradient(players[i].invincibilitytimer) > (FRACUNIT/2))) + { + usecolor = (K_RainbowColor(leveltime / 2)); + colorizeplayer = true; + } + else + { + usecolor = players[i].mo->color; + } + if (players[i].mo->color) { - if (players[i].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[i].mo->color, GTC_CACHE); + if (colorizeplayer) + colormap = R_GetTranslationColormap(TC_RAINBOW, usecolor, GTC_CACHE); else - colormap = R_GetTranslationColormap(skin, players[i].mo->color, GTC_CACHE); + colormap = R_GetTranslationColormap(skin, usecolor, GTC_CACHE); } else colormap = NULL; @@ -3769,12 +3818,24 @@ static void K_drawKartMinimap(void) workingPic = faceprefix[skin][FACE_MINIMAP]; + colorizeplayer = players[localplayers[i]].mo->colorized; + + if ((players[localplayers[i]].invincibilitytimer) && (K_InvincibilityGradient(players[localplayers[i]].invincibilitytimer) > (FRACUNIT/2))) + { + usecolor = (K_RainbowColor(leveltime / 2)); + colorizeplayer = true; + } + else + { + usecolor = players[localplayers[i]].mo->color; + } + if (players[localplayers[i]].mo->color) { - if (players[localplayers[i]].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[localplayers[i]].mo->color, GTC_CACHE); + if (colorizeplayer) + colormap = R_GetTranslationColormap(TC_RAINBOW, usecolor, GTC_CACHE); else - colormap = R_GetTranslationColormap(skin, players[localplayers[i]].mo->color, GTC_CACHE); + colormap = R_GetTranslationColormap(skin, usecolor, GTC_CACHE); } else colormap = NULL; @@ -3841,6 +3902,11 @@ static void K_drawKartMinimap(void) K_drawKartMinimapWaypoint(stplyr->nextwaypoint, x, y, splitflags); } } + + if (cv_kartdebugcluster.value != 0) + { + K_drawKartMinimapCluster(x, y, splitflags); + } } @@ -4853,6 +4919,28 @@ static void K_DrawWaypointDebugger(void) V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); } +static void K_DrawClusterDebugger(void) +{ + if (cv_kartdebugcluster.value == 0) + return; + + if (stplyr != &players[displayplayers[0]]) // only for p1 + return; + + INT32 vflags = V_6WIDTHSPACE|V_ALLOWLOWERCASE; + + if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) + { + 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, 136, vflags, va("Cluster DtF: %d, Your DtF: %d", clusterdtf.x, stplyr->distancetofinish)); + V_DrawThinString(8, 146, vflags, va("Distance from Cluster: %d", stplyr->distancefromcluster)); + } +} + static void K_DrawBotDebugger(void) { if (!cv_kartdebugbot.value || !stplyr->bot) @@ -5112,5 +5200,6 @@ void K_drawKartHUD(void) K_DrawWaypointDebugger(); K_DrawBotDebugger(); + K_DrawClusterDebugger(); K_DrawDirectorDebugger(); } diff --git a/src/k_kart.c b/src/k_kart.c index 0dd245d74..cbc27566c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -51,6 +51,7 @@ #include "lua_hook.h" // For MobjDamage and ShouldDamage #include "m_cheat.h" // objectplacing #include "p_spec.h" +#include "m_easing.h" // Invincibility gradienting #include "k_stats.h" @@ -62,6 +63,7 @@ #include "k_collide.h" #include "k_follower.h" #include "k_grandprix.h" +#include "k_cluster.hpp" #include "blan/b_soc.h" @@ -83,6 +85,13 @@ consvar_t cv_saltyhop = CVAR_INIT ("hardcodehop", "Off", CV_SAVE, CV_OnOff, NULL // indirectitemcooldown is timer before anyone's allowed another Shrink/SPB // mapreset is set when enough players fill an empty server +// Cluster point. +// During a legacy race, this is an actual 3D vector. +// During a waypointed race, this is simply a storage point for the "cluster distance"; +// the distance to finish with the most active number of players. +boolean clusterplayer[MAXPLAYERS]; +vector3_t clusterpoint, clusterdtf; + void K_TimerInit(void) { UINT8 i; @@ -268,6 +277,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartdebugwaypoints); CV_RegisterVar(&cv_kartdebuglap); CV_RegisterVar(&cv_kartdebugbot); + CV_RegisterVar(&cv_kartdebugcluster); CV_RegisterVar(&cv_kartdebugcheckpoint); CV_RegisterVar(&cv_kartdebugnodes); @@ -342,6 +352,9 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartslipdash); + CV_RegisterVar(&cv_kartinvindist); + CV_RegisterVar(&cv_kartinvindistmul); + CV_RegisterVar(&cv_kartdriftsounds); CV_RegisterVar(&cv_kartdriftefx); CV_RegisterVar(&cv_driftsparkpulse); @@ -3562,6 +3575,65 @@ static inline fixed_t K_GetSneakerBoostSpeed(void) } } +// Used to determine the speed and power of Invincibility. +fixed_t K_InvincibilityGradient(UINT16 time) +{ + return (min(936, (fixed_t)time) * FRACUNIT / BASEINVINTIME); +} + +static fixed_t K_InvincibilityEasing(fixed_t x) +{ + fixed_t u = max(FRACUNIT / 4, (min(32000, x) * FRACUNIT) / INVINDIST); + + if (x < INVINDIST) + return u; + + u = max(0, (u - FRACUNIT)); + + if (u < FRACUNIT) + return Easing_InCubic(u, FRACUNIT, 22 * FRACUNIT / 10); + + return Easing_OutCubic( + min(FRACUNIT, FixedMul(u - FRACUNIT, FRACUNIT/4)), 22 * FRACUNIT / 10, 4 * FRACUNIT); +} + + +UINT16 K_GetInvincibilityTime(player_t *player) +{ + UINT32 i, pingame; + + pingame = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) + pingame++; + + if (pingame > 1) // We only want to see if one player is playing. + continue; + } + + if (pingame <= 1) + return BASEINVINTIME; + + fixed_t clustermul = K_InvincibilityEasing(player->distancefromcluster); + return FixedMul(BASEINVINTIME, clustermul); +} + +static fixed_t K_GetInvincibilitySpeed(UINT16 time) +{ + fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); + return Easing_OutCubic(t, 0, INVINSPEEDBOOST); +} + +static fixed_t K_GetInvincibilityAccel(UINT16 time) +{ + fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); + return Easing_OutCubic(t, 0, INVINACCELBOOST); +} + static fixed_t diminish(fixed_t speedboost) { return FixedSqrt(speedboost + DIMINISHPARAM) - FixedSqrt(DIMINISHPARAM); @@ -3661,7 +3733,9 @@ static void K_GetKartBoostPower(player_t *player) if (player->invincibilitytimer) // Invincibility { - K_DoBoost(player, INVINSPEEDBOOST, INVINACCELBOOST, INVINSTACKABLE, INVINSTACKABLE); // + 37.5% top speed, + 300% acceleration + fixed_t invspeedboost = K_GetInvincibilitySpeed(player->invincibilitytimer); + fixed_t invaccelboost = K_GetInvincibilityAccel(player->invincibilitytimer); + K_DoBoost(player, invspeedboost, invaccelboost, INVINSTACKABLE, INVINSTACKABLE); // + 37.5% top speed, + 300% acceleration } if (player->growshrinktimer > 0) // Grow @@ -5801,6 +5875,12 @@ void K_DoInvincibility(player_t *player, tic_t time) P_SetTarget(&overlay->target, player->mo); overlay->destscale = player->mo->scale; P_SetScale(overlay, player->mo->scale); + + mobj_t *aura = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_OVERLAY); + P_SetTarget(&aura->target, player->mo); + aura->destscale = player->mo->scale; + P_SetScale(aura, player->mo->scale); + aura->extravalue2 = 1; } player->invincibilitytimer = time; @@ -7650,7 +7730,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->startboost = K_ChainOrDeincrementTime(player, player->startboost, 1, false); if (player->invincibilitytimer) - player->invincibilitytimer--; + { + player->invincibilitytimer--; + } if (player->checkskip) player->checkskip--; @@ -7957,10 +8039,10 @@ void K_KartResetPlayerColor(player_t *player) { boolean skip = false; - fullbright = true; + //fullbright = true; - player->mo->color = K_RainbowColor(leveltime / 2); - player->mo->colorized = true; + // player->mo->color = K_RainbowColor(leveltime / 2); + // player->mo->colorized = true; skip = true; if (skip) @@ -9680,6 +9762,49 @@ INT32 K_GetDriftAngleOffset(player_t *player) return a; } +static fixed_t K_Distance3D(fixed_t x1, fixed_t y1, fixed_t z1, fixed_t x2, fixed_t y2, fixed_t z2) +{ + fixed_t dist_xy = R_PointToDist2(x1,y1,x2,y2); + + return R_PointToDist2(0,z1,dist_xy,z2); +} + +static fixed_t K_PlayerDistance3D(player_t *source, player_t *destination) +{ + if ((!source->mo) || (!destination->mo)) + return INT32_MAX; // Return a garbage value. + + return K_Distance3D(source->mo->x,source->mo->y,source->mo->z,destination->mo->x,destination->mo->y,destination->mo->z); +} + +static UINT32 K_UpdateDistanceFromCluster(player_t *player) +{ + player_t *cluster_p; + + if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) + { + // Compare yourself against the cluster player to determine the distance. + cluster_p = &players[clusterid]; + + if (player == cluster_p) + return 0; // We ARE the cluster player. + + if (player->position <= cluster_p->position) + return 0; // Ahead, or tying. + + // Return the 3D distance from the cluster player. + return K_PlayerDistance3D(player, cluster_p) / FRACUNIT; + } + else + { + if (player->distancetofinish <= clusterdtf.x) + return 0; // Ahead, or tying. + + // Return the difference between us and the cluster distance. + return (player->distancetofinish - clusterdtf.x); + } +} + // // K_KartUpdatePosition // @@ -9857,10 +9982,95 @@ void K_KartLegacyUpdatePosition(player_t *player) player->position = position; } +// Brute-force finds the area on the course with the highest player density in a given radius. +// Based on DBSCAN, so it "chain-scans" neighboring players as well for a more accurate result. +UINT32 clusterid = 0; + +static vector3_t *K_FindPlayerCluster(fixed_t eps, INT32 (*func)(player_t *, fixed_t, vector3_t *), vector3_t *out) +{ + INT32 bestdensity = 0; // Cluster counter + vector3_t tempclusterpoint; + player_t *findme; + player_t *player; + + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + clusterplayer[i] = false; + + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player->spectator) + continue; // spectator + + if (!player->mo) + continue; + + // Find neighbors + INT32 N = func(player, eps, &tempclusterpoint); + + // Double-remove the clusterplayer flag. Bad hack, I know... + clusterplayer[i] = false; + + // Density check + if (N > bestdensity) + { + bestdensity = N; + + findme = closesttocluster; + + out->x = tempclusterpoint.x; + out->y = tempclusterpoint.y; + out->z = tempclusterpoint.z; + continue; + } + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player == findme) + { + clusterid = i; + break; + } + } + + return out; +} + +#define CLUSTER_EPSILON (160*FRACUNIT) +#define K_FindPlayerClusterLegacy() (K_FindPlayerCluster(CLUSTER_EPSILON, K_CountNeighboringPlayers, &clusterpoint)) +#define K_FindPlayerClusterDTF() (K_FindPlayerCluster((CLUSTER_EPSILON / FRACUNIT), K_CountNeighboringPlayersByDTF, &clusterdtf)) + +void K_UpdateClusterPoints(void) +{ + // Get the player cluster. + if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) + { + K_FindPlayerClusterLegacy(); + } + else + { + if (cv_kartdebugcluster.value) + { + K_FindPlayerClusterLegacy(); + } + + K_FindPlayerClusterDTF(); + } +} + void K_UpdateAllPlayerPositions(void) { INT32 i; - if (!K_UsingLegacyCheckpoints()) { // First loop: Ensure all players' distance to the finish line are all accurate @@ -9887,6 +10097,9 @@ void K_UpdateAllPlayerPositions(void) { if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) { + // Get the cluster distance. + players[i].distancefromcluster = K_UpdateDistanceFromCluster(&players[i]); + if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) K_KartLegacyUpdatePosition(&players[i]); else @@ -10378,7 +10591,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) case KITEM_INVINCIBILITY: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage normally, so you're free to waste it if you have multiple { - K_DoInvincibility(player, 10 * TICRATE); + K_DoInvincibility(player, K_GetInvincibilityTime(player)); K_PlayPowerGloatSound(player->mo); player->itemamount--; player->botvars.itemconfirm = 0; diff --git a/src/k_kart.h b/src/k_kart.h index 095cbd7d7..fda29de1e 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -32,13 +32,15 @@ Make sure this matches the actual number of states #define GROW_SCALE ((3*FRACUNIT)/2) #define SHRINK_SCALE ((6*FRACUNIT)/8) +#define BASEINVINTIME (10 * TICRATE) + #define AUTORESPAWN_TIME (10 * TICRATE) #define AUTORESPAWN_THRESHOLD (7 * TICRATE) #define FLAMESTOREMAX TICRATE*2 // Fixed distance of a flipover. -#define FLIPOVERDIST (112<> 1) * (FLIPOVERHALFTICS * FLIPOVERHALFTICS))) / \ FLIPOVERHALFTICS) +extern boolean clusterplayer[MAXPLAYERS]; +extern UINT32 clusterid; // ID of the "cluster player", the one closest to the cluster point. +extern vector3_t clusterpoint, clusterdtf; + // Precalculated constants for stacked boost diminishing // *Somewhat* matches old calc but doesn't use arrays, which makes it faster and more memory efficent #define DIMINISHPARAM K_RAGuard(cv_kartstacking_diminishparam) @@ -78,6 +84,8 @@ Make sure this matches the actual number of states #define SNEAKERACCELBOOST K_RAGuard(cv_kartstacking_sneaker_accelboost) #define MAXSNEAKERSTACK K_RAGuard(cv_kartstacking_sneaker_maxgrade) #define SNEAKERSTACKABLE K_RAGuard(cv_kartstacking_sneaker_stackable) +#define INVINDIST K_RAGuard(cv_kartinvindist) +#define INVINDISTMUL K_RAGuard(cv_kartinvindistmul) #define INVINSPEEDBOOST K_RAGuard(cv_kartstacking_invincibility_speedboost) #define INVINACCELBOOST K_RAGuard(cv_kartstacking_invincibility_accelboost) #define INVINSTACKABLE K_RAGuard(cv_kartstacking_invincibility_stackable) @@ -205,6 +213,8 @@ typedef enum void K_DoSneaker(player_t *player, INT32 type); void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound); +fixed_t K_InvincibilityGradient(UINT16 time); +UINT16 K_GetInvincibilityTime(player_t *player); void K_DoInvincibility(player_t *player, tic_t time); void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source); void K_UpdateHnextList(player_t *player, boolean clean); @@ -224,6 +234,7 @@ void K_SpawnDriftElectricSparks(player_t *player); INT32 K_GetDriftAngleOffset(player_t *player); void K_KartUpdatePosition(player_t *player); void K_KartLegacyUpdatePosition(player_t *player); +void K_UpdateClusterPoints(void); void K_UpdateAllPlayerPositions(void); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount); void K_DropItems(player_t *player); diff --git a/src/p_mobj.c b/src/p_mobj.c index 35f31fe0c..86bd2f962 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6715,6 +6715,75 @@ static void P_RemoveOverlay(mobj_t *thing) } } +static UINT32 P_GetTranslucencyForInvincibility(mobj_t *mo) +{ + if (!mo->player) + { + // Get out. + return 0; + } + + return max(0, min(9, 10 - FixedMul(10, K_InvincibilityGradient(mo->player->invincibilitytimer))))<target != NULL); + I_Assert(thing->target->player != NULL); + + if (!thing->target->player->invincibilitytimer) + { + P_SetTarget(&thing->target, NULL); + + I_Assert(thing->target == NULL); + return; + } + + thing->destscale = thing->target->scale; + + if (thing->target->eflags & MFE_VERTICALFLIP) + { + thing->eflags |= MFE_VERTICALFLIP; + thing->z += thing->target->height - thing->height; + } + + thing->color = K_RainbowColor(leveltime / 2); + thing->colorized = true; + thing->dispoffset = min(2, thing->target->dispoffset + 1); + + thing->angle = (thing->target->player ? thing->target->player->drawangle : thing->target->angle); + + thing->roll = thing->target->roll; + thing->pitch = thing->target->pitch; + thing->sloperoll = thing->target->sloperoll; + thing->slopepitch = thing->target->slopepitch; + + thing->sprite = thing->target->sprite; + thing->sprite2 = thing->target->sprite2; + thing->frame = thing->target->frame | FF_FULLBRIGHT | FF_ADD; + thing->tics = -1; + thing->renderflags = (thing->target->renderflags & ~RF_TRANSMASK)|P_GetTranslucencyForInvincibility(thing->target); + thing->skin = thing->target->skin; + thing->standingslope = thing->target->standingslope; + + thing->sprxoff = thing->target->sprxoff; + thing->spryoff = thing->target->spryoff; + thing->sprzoff = thing->target->sprzoff; + thing->rollangle = thing->target->rollangle; + + thing->spritexscale = thing->target->spritexscale; + thing->spriteyscale = thing->target->spriteyscale; + thing->spritexoffset = thing->target->spritexoffset; + thing->spriteyoffset = thing->target->spriteyoffset; + + if (thing->target->flags2 & MF2_OBJECTFLIP) + thing->flags2 |= MF2_OBJECTFLIP; + + if (!(thing->target->flags & MF_DONTENCOREMAP)) + thing->flags &= ~MF_DONTENCOREMAP; +} + static void P_MobjScaleThink(mobj_t *mobj) { fixed_t oldheight = mobj->height; @@ -7051,7 +7120,14 @@ static void P_MobjSceneryThink(mobj_t *mobj) return; } else + { P_AddOverlay(mobj); + if ((mobj->extravalue2) && (mobj->target->player)) + { + // Could be overlaying an invincible player. + P_PlayerInvincibilityOverlay(mobj); + } + } break; case MT_WATERDROP: P_SceneryCheckWater(mobj); diff --git a/src/p_setup.c b/src/p_setup.c index 59ab8e0a8..2b0e73309 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8293,6 +8293,7 @@ static void P_InitPlayers(void) G_SpawnPlayer(i, false); } } + K_UpdateClusterPoints(); K_UpdateAllPlayerPositions(); } diff --git a/src/p_tick.c b/src/p_tick.c index 79c7978f8..a4114bb42 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -734,6 +734,7 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime(); + K_UpdateClusterPoints(); K_UpdateAllPlayerPositions(); // OK! Now that we got all of that sorted, players can think! diff --git a/src/p_user.c b/src/p_user.c index eb9d5207f..e0fa678f4 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2241,8 +2241,14 @@ void P_MovePlayer(player_t *player) && onground && (leveltime & 1)) K_SpawnBoostTrail(player); - if (player->invincibilitytimer > 0) - K_SpawnSparkleTrail(player->mo); + if ((player->invincibilitytimer > 0) && + ((leveltime % max(1, + 10 - FixedMul(9, + K_InvincibilityGradient( + player->invincibilitytimer)))) == 0)) + { + K_SpawnSparkleTrail(player->mo); + } if (player->wipeoutslow > 1 && (leveltime & 1)) K_SpawnWipeoutTrail(player->mo, false); From 4603a246284c3ee5aa2a0f4ab912a3c87993fcec Mon Sep 17 00:00:00 2001 From: Anonimus Date: Mon, 2 Jun 2025 16:40:06 -0400 Subject: [PATCH 04/13] Unique Invincibility odds Also preventing lower Invincibility times from overriding a higher one. --- src/k_hud.c | 3 +- src/k_kart.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++----- src/k_kart.h | 7 ++- 3 files changed, 116 insertions(+), 15 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index b1169e0d5..377fec8ee 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4839,11 +4839,12 @@ static void K_drawDistributionDebugger(void) INT32 amount; if (K_UsingLegacyCheckpoints()) - itemodds = K_KartGetLegacyItemOdds(useodds, item, 0, spbrush); + itemodds = K_KartGetLegacyItemOdds(useodds, item, stplyr->distancefromcluster, 0, spbrush); else itemodds = K_KartGetItemOdds( useodds, item, stplyr->distancetofinish, + stplyr->distancefromcluster, 0, spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival) ); diff --git a/src/k_kart.c b/src/k_kart.c index cbc27566c..a38bf556e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -441,10 +441,10 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_dualjawz }; -#define NUMKARTODDS 80 +#define NUMKARTODDS (MAXODDS*10) // Less ugly 2D arrays -static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = +static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = { //B C D E F G H I { 0, 0, 3, 3, 2, 0, 0, 0 }, // Sneaker @@ -701,6 +701,32 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) return distance; } +#define INVODDS 4 +#define INVINDESPERATION 4 + +static INT32 K_KartGetInvincibilityOdds(UINT32 dist) +{ + if (dist < (INVINDIST/2)) + return 0; + + INT32 finodds = 0; + fixed_t fac = (min(32000, (fixed_t)dist) * FRACUNIT) / (INVINDIST); + + if (fac > FRACUNIT) + { + // Desperation! Climb exponentially until Invincibility is practically guaranteed. + fac = (((min(32000, (fixed_t)dist) * FRACUNIT) / (INVINDIST)) - FRACUNIT) >> 1; + finodds = Easing_InCubic(fac, INVODDS, 20 * INVINDESPERATION); + } + else + { + // Basic linear climb to "reasonable" odds. + finodds = FixedMul(INVODDS, fac); + } + + return min(20 * INVINDESPERATION, finodds); +} + /** \brief Item Roulette for Kart \param player player object passed from P_KartPlayerThink @@ -711,6 +737,7 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) INT32 K_KartGetItemOdds( UINT8 pos, SINT8 item, UINT32 ourDist, + UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival) { @@ -761,7 +788,7 @@ INT32 K_KartGetItemOdds( } else if (gametyperules & GTR_RACEODDS) { - I_Assert(pos < 8); // Ditto + I_Assert(pos < MAXODDS); // Ditto newodds = K_KartItemOddsRace[item-1][pos]; } else @@ -847,6 +874,16 @@ INT32 K_KartGetItemOdds( notNearEnd = true; break; case KITEM_INVINCIBILITY: + // It's a power item, yes, but we don't want mashing to lessen + // its chances, so we lie to the game's face. + // Nonetheless, apply the start cooldown. + cooldownOnStart = true; + + // Unique odds for Invincibility. + newodds = K_KartGetInvincibilityOdds(clusterDist); + + newodds *= 4; + break; case KITEM_MINE: case KITEM_GROW: case KITEM_BUBBLESHIELD: @@ -957,7 +994,7 @@ INT32 K_KartGetItemOdds( return newodds; } -INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush) +INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t clusterDist, fixed_t mashed, boolean spbrush) { INT32 newodds; INT32 i; @@ -1037,6 +1074,16 @@ INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spb powerItem = true; break; case KITEM_INVINCIBILITY: + // It's a power item, yes, but we don't want mashing to lessen + // its chances, so we lie to the game's face. + // Nonetheless, apply the start cooldown. + cooldownOnStart = true; + + // Unique odds for Invincibility. + newodds = K_KartGetInvincibilityOdds(clusterDist); + + newodds *= 4; + break; case KITEM_MINE: case KITEM_GROW: case KITEM_BUBBLESHIELD: @@ -1163,6 +1210,7 @@ UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbum if (K_KartGetItemOdds( i, j, player->distancetofinish, + player->distancefromcluster, mashed, spbrush, player->bot, (player->bot && player->botvars.rival) ) > 0) @@ -1263,7 +1311,7 @@ INT32 K_FindLegacyUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 for (j = 1; j < NUMKARTRESULTS; j++) { - if (K_KartGetLegacyItemOdds(i, j, mashed, spbrush) > 0) + if (K_KartGetLegacyItemOdds(i, j, player->distancefromcluster, mashed, spbrush) > 0) { available = true; break; @@ -1785,7 +1833,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { if (K_UsingLegacyCheckpoints()) { - spawnchance[i] = (totalspawnchance += K_KartGetLegacyItemOdds(useodds, i, mashed, spbrush)); + spawnchance[i] = (totalspawnchance += K_KartGetLegacyItemOdds(useodds, i, player->distancefromcluster, mashed, spbrush)); } else @@ -1793,6 +1841,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( useodds, i, player->distancetofinish, + player->distancefromcluster, mashed, spbrush, player->bot, (player->bot && player->botvars.rival)) ); @@ -5883,7 +5932,9 @@ void K_DoInvincibility(player_t *player, tic_t time) aura->extravalue2 = 1; } - player->invincibilitytimer = time; + // Don't punish a player for spamming. + // That sounds stupid, yes. + player->invincibilitytimer = max(player->invincibilitytimer, time); if (P_IsLocalPlayer(player) == true) { @@ -6178,6 +6229,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( useodds, i, UINT32_MAX, + 0, 0, false, false, false ) @@ -9780,11 +9832,43 @@ static fixed_t K_PlayerDistance3D(player_t *source, player_t *destination) static UINT32 K_UpdateDistanceFromCluster(player_t *player) { player_t *cluster_p; + UINT32 i, pingame, first; + INT32 divmul; + + pingame = 0; + first = -1; + divmul = 1; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) + pingame++; + + if (players[i].mo && gametype == GT_RACE) + { + if (players[i].position == 1 && first == -1) + first = i; + } + } if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) { // Compare yourself against the cluster player to determine the distance. - cluster_p = &players[clusterid]; + + if ((pingame == 2) && (first != -1)) + { + // 1v1: The cluster player is first place. + // Half the cluster distance so that the losing player + // doesn't get overly pitied. + cluster_p = &players[first]; + divmul = 2; + } + else + { + cluster_p = &players[clusterid]; + } if (player == cluster_p) return 0; // We ARE the cluster player. @@ -9793,10 +9877,23 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) return 0; // Ahead, or tying. // Return the 3D distance from the cluster player. - return K_PlayerDistance3D(player, cluster_p) / FRACUNIT; + return K_PlayerDistance3D(player, cluster_p) / (FRACUNIT*divmul); } else { + if ((pingame == 2) && (first != -1)) + { + // 1v1: Compare against first place's distance to finish. + // Half the cluster distance so that the losing player + // doesn't get overly pitied. + + // Theoretically impossible, but what do I know? + if (player->distancetofinish <= players[first].distancetofinish) + return 0; // Ahead, or tying. + + return (player->distancetofinish - players[first].distancetofinish) / 2; + } + if (player->distancetofinish <= clusterdtf.x) return 0; // Ahead, or tying. @@ -10097,13 +10194,13 @@ void K_UpdateAllPlayerPositions(void) { if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) { - // Get the cluster distance. - players[i].distancefromcluster = K_UpdateDistanceFromCluster(&players[i]); - if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) K_KartLegacyUpdatePosition(&players[i]); else K_KartUpdatePosition(&players[i]); + + // Get the cluster distance. + players[i].distancefromcluster = K_UpdateDistanceFromCluster(&players[i]); } } } diff --git a/src/k_kart.h b/src/k_kart.h index fda29de1e..1356bb335 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -68,6 +68,9 @@ extern boolean clusterplayer[MAXPLAYERS]; extern UINT32 clusterid; // ID of the "cluster player", the one closest to the cluster point. extern vector3_t clusterpoint, clusterdtf; +// :) +#define MAXODDS 8 + // Precalculated constants for stacked boost diminishing // *Somewhat* matches old calc but doesn't use arrays, which makes it faster and more memory efficent #define DIMINISHPARAM K_RAGuard(cv_kartstacking_diminishparam) @@ -157,8 +160,8 @@ UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbum INT32 K_FindLegacyUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 bestbumper, boolean spbrush, boolean dontforcespb); fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush); UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); -INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival); -INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush); +INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival); +INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t clusterDist, fixed_t mashed, boolean spbrush); INT32 K_GetRollingRouletteItem(player_t *player); INT32 K_GetShieldFromPlayer(player_t *player); INT32 K_GetShieldFromItem(INT32 item); From f2acaab98b23c2e085437dddb088622deab6b045 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Mon, 2 Jun 2025 17:29:51 -0400 Subject: [PATCH 05/13] Zero-out cluster distance in 1P --- src/k_kart.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index a38bf556e..0354c75d7 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9853,6 +9853,9 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) } } + if (pingame <= 1) + return 0; // There's only us around. + if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) { // Compare yourself against the cluster player to determine the distance. From c07ccf475f10dc18e531c03151dce4c15fff8913 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Tue, 3 Jun 2025 13:33:59 -0400 Subject: [PATCH 06/13] Add toggle for legacy Invincibility Also adds customizable time values for Alternative --- src/command.h | 3 ++ src/d_netcmd.c | 32 +++++++++++++- src/d_netcmd.h | 9 +++- src/doomstat.h | 1 + src/g_game.c | 1 + src/k_collide.c | 32 +++++++------- src/k_hud.c | 8 ++-- src/k_kart.c | 112 ++++++++++++++++++++++++++++++++---------------- src/k_kart.h | 14 ++++-- src/p_setup.c | 2 + src/p_user.c | 13 +++--- 11 files changed, 157 insertions(+), 70 deletions(-) diff --git a/src/command.h b/src/command.h index c8b90f0a1..dc99f4a2f 100644 --- a/src/command.h +++ b/src/command.h @@ -186,6 +186,9 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTGP_MASTER 4 // Not a speed setting, gives hard speed with maxed out bots #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots extern CV_PossibleValue_t kartspeed_cons_t[]; +// Invincibility types. +#define KARTINVIN_LEGACY 0 +#define KARTINVIN_ALTERN 1 extern consvar_t cv_execversion; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a03713fe4..03f9762fd 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -154,6 +154,7 @@ static void KartStacking_OnChange(void); static void KartChaining_OnChange(void); static void KartSlipdash_OnChange(void); static void KartItemBreaker_OnChange(void); +static void KartInvinType_OnChange(void); static void Schedule_OnChange(void); @@ -462,8 +463,10 @@ consvar_t cv_kartstacking_sneaker_accelboost = CVAR_INIT ("vanillaboost_sneaker_ consvar_t cv_kartstacking_sneaker_maxgrade = CVAR_INIT ("vanillaboost_sneaker_maxgrade", "3", CV_NETVAR|CV_CHEAT, CV_Natural, NULL); consvar_t cv_kartstacking_sneaker_stackable = CVAR_INIT ("vanillaboost_sneaker_stackable", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_kartstacking_invincibility_speedboost = CVAR_INIT ("vanillaboost_invincibility_speedboost", "0.68", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); -consvar_t cv_kartstacking_invincibility_accelboost = CVAR_INIT ("vanillaboost_invincibility_accelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_legacyspeedboost = CVAR_INIT ("vanillaboost_invincibility_legacyspeedboost", "0.375", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_legacyaccelboost = CVAR_INIT ("vanillaboost_invincibility_legacyaccelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_alternatespeedboost = CVAR_INIT ("vanillaboost_invincibility_alternatespeedboost", "0.68", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_alternateaccelboost = CVAR_INIT ("vanillaboost_invincibility_alternateaccelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); consvar_t cv_kartstacking_invincibility_stackable = CVAR_INIT ("vanillaboost_invincibility_stackable", "Off", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_kartstacking_grow_speedboost = CVAR_INIT ("vanillaboost_grow_speedboost", "0.2", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); @@ -503,12 +506,19 @@ consvar_t cv_kartbumpspring = CVAR_INIT ("kartbumpspring", "No", CV_NETVAR, CV_Y consvar_t cv_kartslipdash = CVAR_INIT ("kartslipdash", "No", CV_NETVAR|CV_CALL|CV_NOINIT, CV_YesNo, KartSlipdash_OnChange); +// Invincibility modifiers +static CV_PossibleValue_t invintype_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; +consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, invintype_cons_t, KartInvinType_OnChange); + // How far the player must be from the cluster to begin frequently rolling Invincibility. static CV_PossibleValue_t invindist_cons_t[] = {{1, "MIN"}, {32000, "MAX"}, {0, NULL}}; consvar_t cv_kartinvindist = CVAR_INIT ("kartinvindist", "8400", CV_NETVAR|CV_CHEAT, invindist_cons_t, NULL); consvar_t cv_kartinvindistmul = CVAR_INIT ("kartinvindistmul", "0.54", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartinvin_maxtime = CVAR_INIT ("kartinvin_maxtime", "21.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartinvin_midtime = CVAR_INIT ("kartinvin_midtime", "14.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); + static CV_PossibleValue_t kartdebugitem_cons_t[] = { #define FOREACH( name, n ) { n, #name } @@ -7318,6 +7328,24 @@ static void KartItemBreaker_OnChange(void) } } +static void KartInvinType_OnChange(void) +{ + if (K_CanChangeRules() == false) + { + return; + } + + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Invincibility type has been changed to \"%s\".\n"), cv_kartinvintype.string); + invintype = (UINT8)cv_kartinvintype.value; + } + else + { + CONS_Printf(M_GetText("Invincibility type will be changed to \"%s\" next round.\n"), cv_kartinvintype.string); + } +} + static void Schedule_OnChange(void) { size_t i; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 77099e1b4..b6b5a367f 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -138,8 +138,10 @@ extern consvar_t cv_kartstacking_sneaker_accelboost; extern consvar_t cv_kartstacking_sneaker_maxgrade; extern consvar_t cv_kartstacking_sneaker_stackable; -extern consvar_t cv_kartstacking_invincibility_speedboost; -extern consvar_t cv_kartstacking_invincibility_accelboost; +extern consvar_t cv_kartstacking_invincibility_legacyspeedboost; +extern consvar_t cv_kartstacking_invincibility_legacyaccelboost; +extern consvar_t cv_kartstacking_invincibility_alternatespeedboost; +extern consvar_t cv_kartstacking_invincibility_alternateaccelboost; extern consvar_t cv_kartstacking_invincibility_stackable; extern consvar_t cv_kartstacking_flame_speedval; @@ -171,8 +173,11 @@ extern consvar_t cv_kartpurpledrift; extern consvar_t cv_kartbumpspark; extern consvar_t cv_kartbumpspring; extern consvar_t cv_kartslipdash; +extern consvar_t cv_kartinvintype; extern consvar_t cv_kartinvindist; extern consvar_t cv_kartinvindistmul; +extern consvar_t cv_kartinvin_maxtime; +extern consvar_t cv_kartinvin_midtime; extern consvar_t cv_encorevotes; diff --git a/src/doomstat.h b/src/doomstat.h index 5301c1b8b..41194842c 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -664,6 +664,7 @@ extern INT32 cheats; // SRB2kart extern UINT8 numlaps; extern UINT8 gamespeed; +extern UINT8 invintype; extern boolean franticitems; extern boolean encoremode, prevencoremode; extern boolean comeback; diff --git a/src/g_game.c b/src/g_game.c index 0e9076a0c..2bc02c72f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -282,6 +282,7 @@ INT32 cheats; //for multiplayer cheat commands // Cvars that we don't want changed mid-game UINT8 numlaps; // Removed from Cvar hell UINT8 gamespeed; // Game's current speed (or difficulty, or cc, or etc); 0 for easy, 1 for normal, 2 for hard +UINT8 invintype; // How Invincibility functions. 0 for Legacy/Vanilla, 1 for Alternative. boolean encoremode = false; // Encore Mode currently enabled? boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? diff --git a/src/k_collide.c b/src/k_collide.c index b734db9d8..57441cad0 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -687,11 +687,6 @@ boolean K_SMKIceBlockCollide(mobj_t *t1, mobj_t *t2) return false; } -boolean K_CanInvincibilityDamage(UINT16 timer) -{ - return (K_InvincibilityGradient(timer) > (FRACUNIT/2)); -} - boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) { const boolean flameT1 = ((t1->player->flamedash > 0) && (t1->player->flametimer > 0)); @@ -700,8 +695,12 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) boolean t1Condition = false; boolean t2Condition = false; - t1Condition = ((t1->player->invincibilitytimer > 0) && (K_CanInvincibilityDamage(t1->player->invincibilitytimer))); - t2Condition = ((t2->player->invincibilitytimer > 0) && (K_CanInvincibilityDamage(t2->player->invincibilitytimer))); + // Rim suggestion: Flipover damage is negligible at best, just cull it from Invincibility as a whole. + if (invintype == KARTINVIN_LEGACY) + { + t1Condition = (t1->player->invincibilitytimer > 0); + t2Condition = (t2->player->invincibilitytimer > 0); + } if ((t1Condition == true || flameT1 == true) && (t2Condition == true || flameT2 == true)) { @@ -709,15 +708,18 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) K_DoInstashield(t2->player); return false; } - else if (t1Condition == true && t2Condition == false) + else if (invintype == KARTINVIN_LEGACY) { - P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER); - return true; - } - else if (t1Condition == false && t2Condition == true) - { - P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER); - return true; + if (t1Condition == true && t2Condition == false) + { + P_DamageMobj(t2, t1, t1, 1, DMG_WIPEOUT); + return true; + } + else if (t1Condition == false && t2Condition == true) + { + P_DamageMobj(t1, t2, t2, 1, DMG_WIPEOUT); + return true; + } } t1Condition = (t1->scale > t2->scale + (mapobjectscale/8)); diff --git a/src/k_hud.c b/src/k_hud.c index 377fec8ee..b83a1b0dd 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1873,7 +1873,7 @@ static boolean K_drawKartPositionFaces(void) V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, faceprefix[players[rankplayer[i]].skin][FACE_RANK], colormap); - if (players[rankplayer[i]].invincibilitytimer) + if ((players[rankplayer[i]].invincibilitytimer) && (invintype == KARTINVIN_ALTERN)) { colormap = R_GetTranslationColormap(TC_RAINBOW, K_RainbowColor(leveltime / 2), GTC_CACHE); invinchudtrans = K_InvincibilityHUDVisibility(players[rankplayer[i]].invincibilitytimer); @@ -3657,7 +3657,7 @@ static void K_drawKartMinimap(void) colorizeplayer = players[i].mo->colorized; - if ((players[i].invincibilitytimer) && (K_InvincibilityGradient(players[i].invincibilitytimer) > (FRACUNIT/2))) + if ((players[i].invincibilitytimer) && ((K_InvincibilityGradient(players[i].invincibilitytimer) > (FRACUNIT/2)) || (invintype == KARTINVIN_LEGACY))) { usecolor = (K_RainbowColor(leveltime / 2)); colorizeplayer = true; @@ -3820,7 +3820,9 @@ static void K_drawKartMinimap(void) colorizeplayer = players[localplayers[i]].mo->colorized; - if ((players[localplayers[i]].invincibilitytimer) && (K_InvincibilityGradient(players[localplayers[i]].invincibilitytimer) > (FRACUNIT/2))) + if ((players[localplayers[i]].invincibilitytimer) && + ((K_InvincibilityGradient(players[localplayers[i]].invincibilitytimer) > + (FRACUNIT / 2)) || (invintype == KARTINVIN_LEGACY))) { usecolor = (K_RainbowColor(leveltime / 2)); colorizeplayer = true; diff --git a/src/k_kart.c b/src/k_kart.c index 0354c75d7..94b6a9e4a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -312,8 +312,10 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartstacking_sneaker_maxgrade); CV_RegisterVar(&cv_kartstacking_sneaker_stackable); - CV_RegisterVar(&cv_kartstacking_invincibility_speedboost); - CV_RegisterVar(&cv_kartstacking_invincibility_accelboost); + CV_RegisterVar(&cv_kartstacking_invincibility_legacyspeedboost); + CV_RegisterVar(&cv_kartstacking_invincibility_legacyaccelboost); + CV_RegisterVar(&cv_kartstacking_invincibility_alternatespeedboost); + CV_RegisterVar(&cv_kartstacking_invincibility_alternateaccelboost); CV_RegisterVar(&cv_kartstacking_invincibility_stackable); CV_RegisterVar(&cv_kartstacking_grow_speedboost); @@ -352,9 +354,13 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartslipdash); + CV_RegisterVar(&cv_kartinvintype); CV_RegisterVar(&cv_kartinvindist); CV_RegisterVar(&cv_kartinvindistmul); + CV_RegisterVar(&cv_kartinvin_maxtime); + CV_RegisterVar(&cv_kartinvin_midtime); + CV_RegisterVar(&cv_kartdriftsounds); CV_RegisterVar(&cv_kartdriftefx); CV_RegisterVar(&cv_driftsparkpulse); @@ -874,16 +880,19 @@ INT32 K_KartGetItemOdds( notNearEnd = true; break; case KITEM_INVINCIBILITY: - // It's a power item, yes, but we don't want mashing to lessen - // its chances, so we lie to the game's face. - // Nonetheless, apply the start cooldown. - cooldownOnStart = true; + if (invintype == KARTINVIN_ALTERN) + { + // It's a power item, yes, but we don't want mashing to lessen + // its chances, so we lie to the game's face. + // Nonetheless, apply the start cooldown. + cooldownOnStart = true; - // Unique odds for Invincibility. - newodds = K_KartGetInvincibilityOdds(clusterDist); - - newodds *= 4; - break; + // Unique odds for Invincibility. + newodds = K_KartGetInvincibilityOdds(clusterDist); + + newodds *= 4; + break; + } case KITEM_MINE: case KITEM_GROW: case KITEM_BUBBLESHIELD: @@ -1074,16 +1083,19 @@ INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t clusterDist, fixed_ powerItem = true; break; case KITEM_INVINCIBILITY: - // It's a power item, yes, but we don't want mashing to lessen - // its chances, so we lie to the game's face. - // Nonetheless, apply the start cooldown. - cooldownOnStart = true; + if (invintype == KARTINVIN_ALTERN) + { + // It's a power item, yes, but we don't want mashing to lessen + // its chances, so we lie to the game's face. + // Nonetheless, apply the start cooldown. + cooldownOnStart = true; - // Unique odds for Invincibility. - newodds = K_KartGetInvincibilityOdds(clusterDist); - - newodds *= 4; - break; + // Unique odds for Invincibility. + newodds = K_KartGetInvincibilityOdds(clusterDist); + + newodds *= 4; + break; + } case KITEM_MINE: case KITEM_GROW: case KITEM_BUBBLESHIELD: @@ -3640,10 +3652,10 @@ static fixed_t K_InvincibilityEasing(fixed_t x) u = max(0, (u - FRACUNIT)); if (u < FRACUNIT) - return Easing_InCubic(u, FRACUNIT, 22 * FRACUNIT / 10); + return Easing_InCubic(u, FRACUNIT, (INVINMIDTIME/10)); return Easing_OutCubic( - min(FRACUNIT, FixedMul(u - FRACUNIT, FRACUNIT/4)), 22 * FRACUNIT / 10, 4 * FRACUNIT); + min(FRACUNIT, FixedMul(u - FRACUNIT, FRACUNIT/4)), (INVINMIDTIME/10), (INVINMAXTIME/10)); } @@ -3651,6 +3663,9 @@ UINT16 K_GetInvincibilityTime(player_t *player) { UINT32 i, pingame; + if (invintype == KARTINVIN_LEGACY) + return BASEINVINTIME; + pingame = 0; for (i = 0; i < MAXPLAYERS; i++) { @@ -3673,14 +3688,20 @@ UINT16 K_GetInvincibilityTime(player_t *player) static fixed_t K_GetInvincibilitySpeed(UINT16 time) { + if (invintype == KARTINVIN_LEGACY) + return INVINSPEEDBOOSTLGC; + fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); - return Easing_OutCubic(t, 0, INVINSPEEDBOOST); + return Easing_OutCubic(t, 0, INVINSPEEDBOOSTALT); } static fixed_t K_GetInvincibilityAccel(UINT16 time) { + if (invintype == KARTINVIN_LEGACY) + return INVINACCELBOOSTLGC; + fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); - return Easing_OutCubic(t, 0, INVINACCELBOOST); + return Easing_OutCubic(t, 0, INVINACCELBOOSTALT); } static fixed_t diminish(fixed_t speedboost) @@ -5925,16 +5946,29 @@ void K_DoInvincibility(player_t *player, tic_t time) overlay->destscale = player->mo->scale; P_SetScale(overlay, player->mo->scale); - mobj_t *aura = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_OVERLAY); - P_SetTarget(&aura->target, player->mo); - aura->destscale = player->mo->scale; - P_SetScale(aura, player->mo->scale); - aura->extravalue2 = 1; + if (invintype == KARTINVIN_ALTERN) + { + mobj_t *aura = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_OVERLAY); + P_SetTarget(&aura->target, player->mo); + aura->destscale = player->mo->scale; + P_SetScale(aura, player->mo->scale); + aura->extravalue2 = 1; + } } - // Don't punish a player for spamming. - // That sounds stupid, yes. - player->invincibilitytimer = max(player->invincibilitytimer, time); + if (invintype == KARTINVIN_ALTERN) + { + // Rim suggestion: Don't allow already invincible players to chain. + if (K_InvincibilityGradient(player->invincibilitytimer) < (FRACUNIT/2)) + { + // Be nice to players at half-power or less. + player->invincibilitytimer = max(player->invincibilitytimer, time); + } + } + else + { + player->invincibilitytimer = time; + } if (P_IsLocalPlayer(player) == true) { @@ -7782,7 +7816,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->startboost = K_ChainOrDeincrementTime(player, player->startboost, 1, false); if (player->invincibilitytimer) - { + { player->invincibilitytimer--; } @@ -8091,11 +8125,15 @@ void K_KartResetPlayerColor(player_t *player) { boolean skip = false; - //fullbright = true; + if (invintype == KARTINVIN_LEGACY) + { + fullbright = true; - // player->mo->color = K_RainbowColor(leveltime / 2); - // player->mo->colorized = true; - skip = true; + player->mo->color = K_RainbowColor(leveltime / 2); + player->mo->colorized = true; + + skip = true; + } if (skip) { diff --git a/src/k_kart.h b/src/k_kart.h index 1356bb335..33b1b6c17 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -71,6 +71,12 @@ extern vector3_t clusterpoint, clusterdtf; // :) #define MAXODDS 8 +// Invincibility-related constants +#define INVINDIST K_RAGuard(cv_kartinvindist) +#define INVINDISTMUL K_RAGuard(cv_kartinvindistmul) +#define INVINMIDTIME K_RAGuard(cv_kartinvin_midtime) +#define INVINMAXTIME K_RAGuard(cv_kartinvin_maxtime) + // Precalculated constants for stacked boost diminishing // *Somewhat* matches old calc but doesn't use arrays, which makes it faster and more memory efficent #define DIMINISHPARAM K_RAGuard(cv_kartstacking_diminishparam) @@ -87,10 +93,10 @@ extern vector3_t clusterpoint, clusterdtf; #define SNEAKERACCELBOOST K_RAGuard(cv_kartstacking_sneaker_accelboost) #define MAXSNEAKERSTACK K_RAGuard(cv_kartstacking_sneaker_maxgrade) #define SNEAKERSTACKABLE K_RAGuard(cv_kartstacking_sneaker_stackable) -#define INVINDIST K_RAGuard(cv_kartinvindist) -#define INVINDISTMUL K_RAGuard(cv_kartinvindistmul) -#define INVINSPEEDBOOST K_RAGuard(cv_kartstacking_invincibility_speedboost) -#define INVINACCELBOOST K_RAGuard(cv_kartstacking_invincibility_accelboost) +#define INVINSPEEDBOOSTLGC K_RAGuard(cv_kartstacking_invincibility_legacyspeedboost) +#define INVINACCELBOOSTLGC K_RAGuard(cv_kartstacking_invincibility_legacyaccelboost) +#define INVINSPEEDBOOSTALT K_RAGuard(cv_kartstacking_invincibility_alternatespeedboost) +#define INVINACCELBOOSTALT K_RAGuard(cv_kartstacking_invincibility_alternateaccelboost) #define INVINSTACKABLE K_RAGuard(cv_kartstacking_invincibility_stackable) #define GROWSPEEDBOOST K_RAGuard(cv_kartstacking_grow_speedboost) #define GROWACCELBOOST K_RAGuard(cv_kartstacking_grow_accelboost) diff --git a/src/p_setup.c b/src/p_setup.c index 2b0e73309..9c1f5bbad 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7961,6 +7961,8 @@ static void P_InitLevelSettings(boolean reloadinggamestate) if (cv_kartslipdash.value) slipdashactive = true; + invintype = (UINT8)cv_kartinvintype.value; + // emerald hunt hunt1 = hunt2 = hunt3 = NULL; diff --git a/src/p_user.c b/src/p_user.c index e0fa678f4..26ab5879e 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2242,13 +2242,12 @@ void P_MovePlayer(player_t *player) K_SpawnBoostTrail(player); if ((player->invincibilitytimer > 0) && - ((leveltime % max(1, - 10 - FixedMul(9, - K_InvincibilityGradient( - player->invincibilitytimer)))) == 0)) - { - K_SpawnSparkleTrail(player->mo); - } + (((leveltime % + max(1, 10 - FixedMul(9, K_InvincibilityGradient(player->invincibilitytimer)))) == 0) || + (invintype == KARTINVIN_LEGACY))) + { + K_SpawnSparkleTrail(player->mo); + } if (player->wipeoutslow > 1 && (leveltime & 1)) K_SpawnWipeoutTrail(player->mo, false); From 1e05aa83089bea9a72f3b7b5f116c4451cb28449 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Tue, 3 Jun 2025 13:35:32 -0400 Subject: [PATCH 07/13] Add Rim and Luna to the credits Nep, please... My name... --- src/f_finale.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/f_finale.c b/src/f_finale.c index d5da44d9f..a004f8087 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -914,7 +914,7 @@ static const char *blancredits[] = { "\1Support Programming", "\"hayaunderscore\" aka \"DeltaKaynx\"", "\"WumboSpasm\"", - "\"Anonimous\"", + "\"Anonimus\"", "", "\1External Programming", "\"Hanicef\"", @@ -934,6 +934,10 @@ static const char *blancredits[] = { "\"Jin\"", "\"NepDisk\"", "", + "\1Debug", + "\"Rim Jobless\"", + "\"luna\"", + "", "\1Special Thanks", "\"Sunflower\" aka \"AnimeSonic\"", "Sunflower's Garden", From 5e15f7a68a04d66bf652b923f98d7de2e4de8532 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Mon, 9 Jun 2025 19:40:43 -0400 Subject: [PATCH 08/13] Update Alternative Invincibility Invincibility now begins to get affected by offroad as it runs out A "bottlenecker" begins to kick in as an invincible player closes in on the cluster, causing their invincibility to expire at an increasing rate Prevent Invincibility from being rolled at extremely close cluster distances, to prevent the potential for rolling it as a "dud" item --- src/d_player.h | 1 + src/k_kart.c | 77 ++++++++++++++++++++++++++++++++++++++++++-------- src/m_fixed.h | 1 + src/p_saveg.c | 2 ++ 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index fdaf9ac09..aa2a5e013 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -712,6 +712,7 @@ struct player_t INT16 squishedtimer; // Duration of being squished UINT16 rocketsneakertimer; // Rocket Sneaker duration timer UINT16 invincibilitytimer; // Invincibility timer + UINT16 invincibilitybottleneck; // (Alternate) Prevents breakaways by gradienting towards a heavier decrement. UINT8 eggmanexplode; // Fake item recieved, explode in a few seconds SINT8 eggmanblame; // (-1 to 15) - Fake item recieved, who set this fake diff --git a/src/k_kart.c b/src/k_kart.c index 94b6a9e4a..361c27a05 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -726,6 +726,12 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist) } else { + if (fac <= FRACHALF) + { + // Invincibility is practically useless at lower distances. + // At below half, remove it from the item pool. + return 0; + } // Basic linear climb to "reasonable" odds. finodds = FixedMul(INVODDS, fac); } @@ -2271,6 +2277,15 @@ static fixed_t K_CheckOffroadCollide(mobj_t *mo) return 0; // couldn't find any offroad } +static fixed_t K_OffroadGradient(player_t *player, fixed_t offroad) +{ + // At 50% or lower Invincibility, offroad creeps up on you. + fixed_t invinoffroad = (invintype == KARTINVIN_LEGACY) ? ((player->invincibilitytimer) ? FRACUNIT : 0) : min(FRACUNIT, K_InvincibilityGradient(player->invincibilitytimer) << 1); + fixed_t fac = CLAMP(FRACUNIT - invinoffroad, 0, FRACUNIT); + + return FixedMul(offroad, fac); +} + /** \brief Updates the Player's offroad value once per frame \param player player object passed from K_KartPlayerThink @@ -2292,6 +2307,9 @@ static void K_UpdateOffroad(player_t *player) offroadstrength = K_CheckOffroadCollide(player->mo); } + // Gradient our offroad strength. + offroadstrength = K_OffroadGradient(player, offroadstrength); + // If you are in offroad, a timer starts. if (offroadstrength) { @@ -3317,7 +3335,7 @@ boolean K_ApplyOffroad(player_t *player) if (modeattacking != ATTACKING_NONE) sneakertimer = player->sneakertimer > 0; - if (player->invincibilitytimer || player->hyudorotimer || sneakertimer) + if (player->hyudorotimer || sneakertimer) return false; return true; } @@ -7817,8 +7835,31 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->invincibilitytimer) { - player->invincibilitytimer--; + INT16 invinfac = 1; + if ((invintype == KARTINVIN_ALTERN) && ((INT16)(player->invincibilitybottleneck) != -1) && (player->invincibilitytimer > 2)) + { + if (player->distancefromcluster < (INVINDIST >> 2)) + { + player->invincibilitybottleneck = min(256, player->invincibilitybottleneck + 4); + invinfac = FixedMul(4, max(0, min(FRACUNIT, FRACUNIT - (player->distancefromcluster / (INVINDIST >> 2))))); + } + else + { + player->invincibilitybottleneck = max(0, (INT32)(player->invincibilitybottleneck) - 2); + } + + player->invincibilitytimer = (UINT16)(max(2, (INT32)(player->invincibilitytimer) - max(1, FixedMul((UINT16)invinfac, player->invincibilitybottleneck << 8)))); + } + else + { + player->invincibilitytimer--; + player->invincibilitybottleneck = 0; + } } + else + { + player->invincibilitybottleneck = 0; // No need for bottlenecking. + } if (player->checkskip) player->checkskip--; @@ -9870,11 +9911,15 @@ static fixed_t K_PlayerDistance3D(player_t *source, player_t *destination) static UINT32 K_UpdateDistanceFromCluster(player_t *player) { player_t *cluster_p; - UINT32 i, pingame, first; + UINT32 i, pingame, first, second, tinypoptarget; INT32 divmul; pingame = 0; + first = -1; + second = -1; + tinypoptarget = -1; + divmul = 1; for (i = 0; i < MAXPLAYERS; i++) { @@ -9888,22 +9933,30 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) { if (players[i].position == 1 && first == -1) first = i; + else if (players[i].position == 2 && second == -1) + second = i; } } if (pingame <= 1) - return 0; // There's only us around. + { + // There's only us around. + player->invincibilitybottleneck = (UINT16)(-1); // No bottlenecking! + return 0; + } + + tinypoptarget = (pingame < 3) ? first : second; if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) { // Compare yourself against the cluster player to determine the distance. - if ((pingame == 2) && (first != -1)) + if ((pingame < 4) && (tinypoptarget != -1)) { - // 1v1: The cluster player is first place. + // At under 4P, the cluster player is first/second place. // Half the cluster distance so that the losing player // doesn't get overly pitied. - cluster_p = &players[first]; + cluster_p = &players[tinypoptarget]; divmul = 2; } else @@ -9922,17 +9975,17 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) } else { - if ((pingame == 2) && (first != -1)) + if ((pingame < 4) && (tinypoptarget != -1)) { - // 1v1: Compare against first place's distance to finish. + // At under 4P, compare against first/second place's distance to finish. // Half the cluster distance so that the losing player // doesn't get overly pitied. - // Theoretically impossible, but what do I know? - if (player->distancetofinish <= players[first].distancetofinish) + // In a 1v1, this is theoretically impossible; but what do I know? + if (player->distancetofinish <= players[tinypoptarget].distancetofinish) return 0; // Ahead, or tying. - return (player->distancetofinish - players[first].distancetofinish) / 2; + return (player->distancetofinish - players[tinypoptarget].distancetofinish) / 2; } if (player->distancetofinish <= clusterdtf.x) diff --git a/src/m_fixed.h b/src/m_fixed.h index 1c147c401..8bc49ca5a 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -32,6 +32,7 @@ extern "C" { \brief units of the fraction */ #define FRACUNIT (1<>1)) #define FRACMASK (FRACUNIT -1) /** \brief Redefinition of INT32 as fixed_t unit used as fixed_t diff --git a/src/p_saveg.c b/src/p_saveg.c index e9758af49..cbc9b8e05 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -345,6 +345,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT16(save->p, players[i].growcancel); WRITEUINT16(save->p, players[i].rocketsneakertimer); WRITEUINT16(save->p, players[i].invincibilitytimer); + WRITEUINT16(save->p, players[i].invincibilitybottleneck); WRITEUINT8(save->p, players[i].eggmanexplode); WRITESINT8(save->p, players[i].eggmanblame); @@ -686,6 +687,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].growcancel = READINT16(save->p); players[i].rocketsneakertimer = READUINT16(save->p); players[i].invincibilitytimer = READUINT16(save->p); + players[i].invincibilitybottleneck = READUINT16(save->p); players[i].eggmanexplode = READUINT8(save->p); players[i].eggmanblame = READSINT8(save->p); From cac6bbdd4157149c18a3426be5d9814d2c9d3e49 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Mon, 9 Jun 2025 19:42:27 -0400 Subject: [PATCH 09/13] Update the credits again Adds 'Item Programming', 'Item Design', and 'Design Support' sections, with the respective people in each. --- src/f_finale.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/f_finale.c b/src/f_finale.c index a004f8087..0d536a2c4 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -916,6 +916,10 @@ static const char *blancredits[] = { "\"WumboSpasm\"", "\"Anonimus\"", "", + "\1Item Programming", + "\"NepDisk\"", + "\"Anonimus\"", + "", "\1External Programming", "\"Hanicef\"", "\"Lactozilla\"", @@ -929,6 +933,15 @@ static const char *blancredits[] = { "\"JugadorXEI\"", "\"Kimberly\"", "", + "\1Item Design", + "\"NepDisk\"", + "\"Anonimus\"", + "", + "\1Design Support", + "\"Rim Jobless\"", + "\"luna\"", + "\"Denny\" aka \"shephoron\"", + "", "\1New Graphics Creation", "\"Spee\"", "\"Jin\"", From 7c31ca048326e41afc12e333383991b99f508f37 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 11 Jun 2025 10:27:00 -0400 Subject: [PATCH 10/13] More changes to Alt Invincibility Bottlenecker has been heavily buffed (8-tic deficit to invince timer at full strength) The cluster now specifically only tracks losing players, and in the case that no new cluster point is found, instead reads from the last cluster player Invincibility "hogs the item box" like Grow does, and (currently) can't be cancelled to prevent chaining Maximum time limit has been buffed to 35 seconds --- src/d_netcmd.c | 4 +- src/d_player.h | 11 ++--- src/k_cluster.cpp | 25 ++++++++++++ src/k_hud.c | 12 +++++- src/k_kart.c | 102 ++++++++++++++++++++++++++++++++++++---------- src/p_inter.c | 1 + src/p_saveg.c | 4 ++ 7 files changed, 130 insertions(+), 29 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 03f9762fd..5e869201a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -516,8 +516,8 @@ consvar_t cv_kartinvindist = CVAR_INIT ("kartinvindist", "8400", CV_NETVAR|CV_CH consvar_t cv_kartinvindistmul = CVAR_INIT ("kartinvindistmul", "0.54", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); -consvar_t cv_kartinvin_maxtime = CVAR_INIT ("kartinvin_maxtime", "21.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); -consvar_t cv_kartinvin_midtime = CVAR_INIT ("kartinvin_midtime", "14.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartinvin_maxtime = CVAR_INIT ("kartinvin_maxtime", "35.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartinvin_midtime = CVAR_INIT ("kartinvin_midtime", "23.333", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); static CV_PossibleValue_t kartdebugitem_cons_t[] = { diff --git a/src/d_player.h b/src/d_player.h index aa2a5e013..1a55cfb0d 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -707,11 +707,12 @@ struct player_t UINT8 boostcharge; // Charge during race start - INT16 growshrinktimer; // > 0 = Big, < 0 = small - INT16 growcancel; // Duration of grow canceling - INT16 squishedtimer; // Duration of being squished - UINT16 rocketsneakertimer; // Rocket Sneaker duration timer - UINT16 invincibilitytimer; // Invincibility timer + INT16 growshrinktimer; // > 0 = Big, < 0 = small + INT16 growcancel; // Duration of grow canceling + INT16 squishedtimer; // Duration of being squished + UINT16 rocketsneakertimer; // Rocket Sneaker duration timer + UINT16 invincibilitytimer; // Invincibility timer + UINT16 maxinvincibilitytime; // Initial time for Invincibility, used for the item bar. UINT16 invincibilitybottleneck; // (Alternate) Prevents breakaways by gradienting towards a heavier decrement. UINT8 eggmanexplode; // Fake item recieved, explode in a few seconds diff --git a/src/k_cluster.cpp b/src/k_cluster.cpp index ec04b33a2..aef54033d 100644 --- a/src/k_cluster.cpp +++ b/src/k_cluster.cpp @@ -77,6 +77,9 @@ INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, v if (!player->mo) continue; + + if (!K_IsPlayerLosing(player)) + continue; // Ignore winning players to prevent sandbagging. // Scan all points in the database if (K_PlayerDistance3D(sourcePlayer, player) <= eps) @@ -145,6 +148,11 @@ INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, v static std::vector dtf_vec; static boolean cleardtf = true; +static UINT32 K_GetU32Diff(UINT32 a, UINT32 b) +{ + return (b > a) ? (b - a) : (a - b); +} + INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector3_t *out) { INT64 mean; @@ -180,6 +188,9 @@ INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector if (!player->mo) continue; + + if (!K_IsPlayerLosing(player)) + continue; // Ignore winning players to prevent sandbagging. // Scan all points in the database if ((fixed_t)K_GetDTFDifference(sourcePlayer, player) <= eps) @@ -217,6 +228,20 @@ INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector out->x = (fixed_t)(mean); out->y = 0; out->z = 0; + + // Get the player closest to the cluster. + UINT32 disttocluster, bestdist; + bestdist = UINT32_MAX; + for (i = 0; i < N; i++) + { + disttocluster = K_GetU32Diff(dtf_vec[i]->distancetofinish, mean); + + if (disttocluster < bestdist) + { + closesttocluster = dtf_vec[i]; + bestdist = disttocluster; + } + } } else { diff --git a/src/k_hud.c b/src/k_hud.c index b83a1b0dd..a3c918e97 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1249,6 +1249,16 @@ static void K_drawKartItem(void) else localpatch = kp_nodraw; } + else if ((stplyr->invincibilitytimer) && (invintype == KARTINVIN_ALTERN)) + { + itembar = stplyr->invincibilitytimer; + maxl = max(1, stplyr->maxinvincibilitytime); + + if (leveltime & 1) + localpatch = localinv; + else + localpatch = kp_nodraw; + } else if (K_GetShieldFromPlayer(stplyr) == KSHIELD_BUBBLE) { localpatch = kp_bubbleshield[offset]; @@ -1392,7 +1402,7 @@ static void K_drawKartItem(void) //V_ClearClipRect(); - // Extensible meter, currently used by Grow, Rocket Sneakers and Flame Shield + // Extensible meter, currently used by Invincibilty, Grow, Rocket Sneakers and Flame Shield if (itembar) { const INT32 fill = ((itembar*barlength)/maxl); diff --git a/src/k_kart.c b/src/k_kart.c index 361c27a05..99ab47115 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5988,6 +5988,8 @@ void K_DoInvincibility(player_t *player, tic_t time) player->invincibilitytimer = time; } + player->maxinvincibilitytime = time; + if (P_IsLocalPlayer(player) == true) { S_ChangeMusicSpecial("kinvnc"); @@ -7841,7 +7843,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->distancefromcluster < (INVINDIST >> 2)) { player->invincibilitybottleneck = min(256, player->invincibilitybottleneck + 4); - invinfac = FixedMul(4, max(0, min(FRACUNIT, FRACUNIT - (player->distancefromcluster / (INVINDIST >> 2))))); + invinfac = FixedMul(8, max(0, min(FRACUNIT, FRACUNIT - (player->distancefromcluster / (INVINDIST >> 2))))); } else { @@ -7859,6 +7861,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) else { player->invincibilitybottleneck = 0; // No need for bottlenecking. + player->maxinvincibilitytime = 0; } if (player->checkskip) @@ -9908,6 +9911,8 @@ static fixed_t K_PlayerDistance3D(player_t *source, player_t *destination) return K_Distance3D(source->mo->x,source->mo->y,source->mo->z,destination->mo->x,destination->mo->y,destination->mo->z); } +#define MINCLUSTERPLAYERS 6 + static UINT32 K_UpdateDistanceFromCluster(player_t *player) { player_t *cluster_p; @@ -9944,6 +9949,26 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) player->invincibilitybottleneck = (UINT16)(-1); // No bottlenecking! return 0; } + else if ((pingame > 3) && (pingame < MINCLUSTERPLAYERS)) + { + // "Second place" in this case is actually second to last. + // In very small lobbies, it's harder for players to cluster together. + second = -1; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (players[i].mo && gametype == GT_RACE) + { + if (players[i].position == (pingame - 1) && second == -1) + { + second = i; + break; + } + } + } + } tinypoptarget = (pingame < 3) ? first : second; @@ -9951,19 +9976,22 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) { // Compare yourself against the cluster player to determine the distance. - if ((pingame < 4) && (tinypoptarget != -1)) + if ((pingame < MINCLUSTERPLAYERS) && (tinypoptarget != -1)) { - // At under 4P, the cluster player is first/second place. + // In very small lobbies, the cluster player is first place/second to last. // Half the cluster distance so that the losing player // doesn't get overly pitied. cluster_p = &players[tinypoptarget]; divmul = 2; } - else + else if (clusterid != -1) { cluster_p = &players[clusterid]; } + if ((clusterid == -1) || (!cluster_p)) + return 0; // No cluster player; not good! + if (player == cluster_p) return 0; // We ARE the cluster player. @@ -9975,9 +10003,10 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) } else { - if ((pingame < 4) && (tinypoptarget != -1)) + if ((pingame < MINCLUSTERPLAYERS) && (tinypoptarget != -1)) { - // At under 4P, compare against first/second place's distance to finish. + // In very small lobbies, compare against first place/second to last's + // distance to finish. // Half the cluster distance so that the losing player // doesn't get overly pitied. @@ -9988,14 +10017,28 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) return (player->distancetofinish - players[tinypoptarget].distancetofinish) / 2; } - if (player->distancetofinish <= clusterdtf.x) + UINT32 targetdtf; + + if (clusterid != -1) + { + // Use the cluster player's DtF. + targetdtf = players[clusterid].distancetofinish; + } + else + { + targetdtf = (UINT32)(clusterdtf.x); + } + + if (player->distancetofinish <= targetdtf) return 0; // Ahead, or tying. // Return the difference between us and the cluster distance. - return (player->distancetofinish - clusterdtf.x); + return (player->distancetofinish - targetdtf); } } +#undef MINCLUSTERPLAYERS + // // K_KartUpdatePosition // @@ -10220,19 +10263,23 @@ static vector3_t *K_FindPlayerCluster(fixed_t eps, INT32 (*func)(player_t *, fix } } - for (i = 0; i < MAXPLAYERS; i++) + if (bestdensity) { - if (!playeringame[i]) - continue; - - player = &players[i]; - - if (player == findme) - { - clusterid = i; - break; - } - } + // Only find the cluster player if a cluster's populated. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player == findme) + { + clusterid = i; + break; + } + } + } return out; } @@ -10518,6 +10565,14 @@ void K_UnsetItemOut(player_t *player) player->bananadrag = 0; } +static boolean K_InvincibilitySlotHogging(player_t *player) +{ + if (invintype == KARTINVIN_LEGACY) + return false; + + return ((player->invincibilitytimer) != 0); +} + // // K_MoveKartPlayer // @@ -10728,6 +10783,11 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->growcancel = 0; } } + // Invincibility + else if (K_InvincibilitySlotHogging(player)) + { + // Does nothing, for now. + } else if (player->itemamount == 0) { K_UnsetItemOut(player); @@ -10780,7 +10840,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } break; case KITEM_INVINCIBILITY: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage normally, so you're free to waste it if you have multiple + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage in Legacy, so you're free to waste it if you have multiple { K_DoInvincibility(player, K_GetInvincibilityTime(player)); K_PlayPowerGloatSound(player->mo); diff --git a/src/p_inter.c b/src/p_inter.c index 347b54887..695874c08 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -146,6 +146,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) if (player->stealingtimer || player->stolentimer || player->rocketsneakertimer || player->eggmanexplode + || ((invintype == KARTINVIN_ALTERN) && (player->invincibilitytimer)) || (player->growshrinktimer > 0) || player->flametimer) return false; diff --git a/src/p_saveg.c b/src/p_saveg.c index cbc9b8e05..a64557d51 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -344,7 +344,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT16(save->p, players[i].growshrinktimer); WRITEINT16(save->p, players[i].growcancel); WRITEUINT16(save->p, players[i].rocketsneakertimer); + WRITEUINT16(save->p, players[i].invincibilitytimer); + WRITEUINT16(save->p, players[i].maxinvincibilitytime); WRITEUINT16(save->p, players[i].invincibilitybottleneck); WRITEUINT8(save->p, players[i].eggmanexplode); @@ -686,7 +688,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].growshrinktimer = READINT16(save->p); players[i].growcancel = READINT16(save->p); players[i].rocketsneakertimer = READUINT16(save->p); + players[i].invincibilitytimer = READUINT16(save->p); + players[i].maxinvincibilitytime = READUINT16(save->p); players[i].invincibilitybottleneck = READUINT16(save->p); players[i].eggmanexplode = READUINT8(save->p); From b81fcaf7c701fbc7f7e4e010d690bea92cbd8ffe Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 11 Jun 2025 10:27:44 -0400 Subject: [PATCH 11/13] Add Denny to the 'Item Design' credits Assisted with development and design of Alt Invincibility. --- src/f_finale.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/f_finale.c b/src/f_finale.c index 0d536a2c4..05021f8f7 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -936,6 +936,7 @@ static const char *blancredits[] = { "\1Item Design", "\"NepDisk\"", "\"Anonimus\"", + "\"Denny\" aka \"shephoron\"", "", "\1Design Support", "\"Rim Jobless\"", From 207e5011dceb8710cf97da7115b0bcfb9112f309 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Thu, 19 Jun 2025 22:05:35 -0400 Subject: [PATCH 12/13] Add a cancel system to Alt Invincibility Can be cancelled now, similarly to Grow --- src/d_player.h | 5 ++++- src/k_hud.c | 6 ++++++ src/k_kart.c | 26 +++++++++++++++++++++++++- src/p_saveg.c | 4 ++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 1a55cfb0d..84bd491a3 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -710,10 +710,13 @@ struct player_t INT16 growshrinktimer; // > 0 = Big, < 0 = small INT16 growcancel; // Duration of grow canceling INT16 squishedtimer; // Duration of being squished + UINT16 rocketsneakertimer; // Rocket Sneaker duration timer + UINT16 invincibilitytimer; // Invincibility timer - UINT16 maxinvincibilitytime; // Initial time for Invincibility, used for the item bar. + UINT16 maxinvincibilitytime; // (Alternate) Initial time for Invincibility, used for the item bar. UINT16 invincibilitybottleneck; // (Alternate) Prevents breakaways by gradienting towards a heavier decrement. + INT16 invincibilitycancel; // (Alternate) Duration of Invincibility canceling. UINT8 eggmanexplode; // Fake item recieved, explode in a few seconds SINT8 eggmanblame; // (-1 to 15) - Fake item recieved, who set this fake diff --git a/src/k_hud.c b/src/k_hud.c index a3c918e97..b34d32a42 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1254,6 +1254,12 @@ static void K_drawKartItem(void) itembar = stplyr->invincibilitytimer; maxl = max(1, stplyr->maxinvincibilitytime); + if (stplyr->invincibilitycancel > 0) + { + flamebar = stplyr->invincibilitycancel; + flamemaxl = 26; + } + if (leveltime & 1) localpatch = localinv; else diff --git a/src/k_kart.c b/src/k_kart.c index 99ab47115..7686b1c8f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7862,6 +7862,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { player->invincibilitybottleneck = 0; // No need for bottlenecking. player->maxinvincibilitytime = 0; + player->invincibilitycancel = -1; } if (player->checkskip) @@ -10381,6 +10382,8 @@ void K_StripOther(player_t *player) player->roulettetype = KROULETTETYPE_NORMAL; player->invincibilitytimer = 0; + player->invincibilitycancel = -1; + if (player->growshrinktimer) { K_RemoveGrowShrink(player); @@ -10786,7 +10789,28 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // Invincibility else if (K_InvincibilitySlotHogging(player)) { - // Does nothing, for now. + // (Alternate) Cancel Invincibility + if (player->invincibilitycancel >= 0) + { + if (buttons & BT_ATTACK) + { + player->invincibilitycancel++; + if (player->invincibilitycancel > 25) + { + // Don't fully cancel due to how the music handling works. + player->invincibilitytimer = 1; + } + } + else + player->invincibilitycancel = 0; + } + else + { + if ((buttons & BT_ATTACK) || (player->oldcmd.buttons & BT_ATTACK)) + player->invincibilitycancel = -1; + else + player->invincibilitycancel = 0; + } } else if (player->itemamount == 0) { diff --git a/src/p_saveg.c b/src/p_saveg.c index a64557d51..53f436a82 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -343,11 +343,13 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT16(save->p, players[i].growshrinktimer); WRITEINT16(save->p, players[i].growcancel); + WRITEUINT16(save->p, players[i].rocketsneakertimer); WRITEUINT16(save->p, players[i].invincibilitytimer); WRITEUINT16(save->p, players[i].maxinvincibilitytime); WRITEUINT16(save->p, players[i].invincibilitybottleneck); + WRITEINT16(save->p, players[i].invincibilitycancel); WRITEUINT8(save->p, players[i].eggmanexplode); WRITESINT8(save->p, players[i].eggmanblame); @@ -687,11 +689,13 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].growshrinktimer = READINT16(save->p); players[i].growcancel = READINT16(save->p); + players[i].rocketsneakertimer = READUINT16(save->p); players[i].invincibilitytimer = READUINT16(save->p); players[i].maxinvincibilitytime = READUINT16(save->p); players[i].invincibilitybottleneck = READUINT16(save->p); + players[i].invincibilitycancel = READINT16(save->p); players[i].eggmanexplode = READUINT8(save->p); players[i].eggmanblame = READSINT8(save->p); From efba636d9612f92f455c12c612627c1137ccc26b Mon Sep 17 00:00:00 2001 From: Anonimus Date: Sun, 29 Jun 2025 02:19:27 -0400 Subject: [PATCH 13/13] Update main.pk3 hash --- src/d_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 50ba77d9e..3ba1db0d4 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -86,7 +86,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x9a6188063fcdcc93 +#define ASSET_HASH_MAIN_PK3 0x7fbb80904dbc97d7 #define ASSET_HASH_MAPPATCH_PK3 0x93a9213b2b2ba260 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE