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);