Revert "Kill Alt. Invin."

This reverts commit f414006e66.
This commit is contained in:
NepDisk 2025-12-22 20:23:45 -05:00
parent f414006e66
commit 7249273ca3
20 changed files with 772 additions and 67 deletions

View file

@ -490,9 +490,18 @@ consvar_t cv_kartstacking_sneaker_stackable = CVAR_INIT ("vanillaboost_sneaker_s
consvar_t cv_kartstacking_panel_separate = CVAR_INIT ("vanillaboost_panel_separate", "Off", CV_NETVAR|CV_GUARD, CV_OnOff, NULL);
consvar_t cv_kartstacking_panel_maxgrade = CVAR_INIT ("vanillaboost_panel_maxgrade", "2", CV_NETVAR|CV_CHEAT|CV_GUARD, CV_Natural, NULL);
consvar_t cv_kartstacking_invincibility_speedboost = CVAR_INIT ("vanillaboost_invincibility_speedboost", "0.375", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartstacking_invincibility_accelboost = CVAR_INIT ("vanillaboost_invincibility_accelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartstacking_invincibility_handleboost = CVAR_INIT ("vanillaboost_invincibility_handleboost", "0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
//
// Invincibility
//
// Classic boosts
consvar_t cv_kartstacking_invincibility_classicspeedboost = CVAR_INIT ("vanillaboost_invincibility_classicspeedboost", "0.375", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartstacking_invincibility_classicaccelboost = CVAR_INIT ("vanillaboost_invincibility_classicaccelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartstacking_invincibility_classichandleboost = CVAR_INIT ("vanillaboost_invincibility_classichandleboost", "0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
// Alternate boosts
consvar_t cv_kartstacking_invincibility_alternatespeedboost = CVAR_INIT ("vanillaboost_invincibility_alternatespeedboost", "0.67", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartstacking_invincibility_alternateaccelboost = CVAR_INIT ("vanillaboost_invincibility_alternateaccelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartstacking_invincibility_alternatehandleboost = CVAR_INIT ("vanillaboost_invincibility_alternatehandleboost", "0.75", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartstacking_invincibility_stackable = CVAR_INIT ("vanillaboost_invincibility_stackable", "On", CV_NETVAR|CV_GUARD, CV_OnOff, NULL);
consvar_t cv_kartstacking_grow_speedboost = CVAR_INIT ("vanillaboost_grow_speedboost", "0.2", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
@ -634,9 +643,13 @@ static CV_PossibleValue_t kartinvintheme_cons_t[] = {{0, "Standard"}, {1, "Full"
consvar_t cv_kartinvintheme = CVAR_INIT ("kartinvintheme", "Standard", CV_SAVE, kartinvintheme_cons_t, NULL);
// 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}};
static CV_PossibleValue_t invindist_cons_t[] = {{1, "MIN"}, {32000, "MAX"}, {0, NULL}};
consvar_t cv_kartinvindist = CVAR_INIT ("kartinvindist", "6800", CV_NETVAR|CV_CHEAT|CV_GUARD, invindist_cons_t, NULL);
*/
consvar_t cv_kartinvindistmul = CVAR_INIT ("kartinvindistmul", "0.54", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartinvin_maxtime = CVAR_INIT ("kartinvin_maxtime", "35.0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
consvar_t cv_kartinvin_midtime = CVAR_INIT ("kartinvin_midtime", "23.333", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL);
// opinionated stuff for testing balance tweaks on the shields
consvar_t cv_kartbubble_defense_canidle = CVAR_INIT ("kartbubble_defense_canidle", "On", CV_NETVAR, CV_OnOff, NULL);

View file

@ -118,9 +118,12 @@ extern consvar_t cv_kartstacking_panel_separate;
extern consvar_t cv_kartstacking_panel_maxgrade;
extern consvar_t cv_kartstacking_invincibility_speedboost;
extern consvar_t cv_kartstacking_invincibility_accelboost;
extern consvar_t cv_kartstacking_invincibility_handleboost;
extern consvar_t cv_kartstacking_invincibility_classicspeedboost;
extern consvar_t cv_kartstacking_invincibility_classicaccelboost;
extern consvar_t cv_kartstacking_invincibility_classichandleboost;
extern consvar_t cv_kartstacking_invincibility_alternatespeedboost;
extern consvar_t cv_kartstacking_invincibility_alternateaccelboost;
extern consvar_t cv_kartstacking_invincibility_alternatehandleboost;
extern consvar_t cv_kartstacking_invincibility_stackable;
extern consvar_t cv_kartstacking_flame_speedval;
@ -191,7 +194,10 @@ extern consvar_t cv_kartexplosion_limitlifetime_cap;
extern consvar_t cv_kartslipdash;
extern consvar_t cv_kartslopeboost;
extern consvar_t cv_kartinvintheme;
//extern consvar_t cv_kartinvindist;
extern consvar_t cv_kartinvindist;
extern consvar_t cv_kartinvindistmul;
extern consvar_t cv_kartinvin_maxtime;
extern consvar_t cv_kartinvin_midtime;
// opinionated stuff for testing
extern consvar_t cv_kartbubble_defense_canidle;

View file

@ -552,7 +552,7 @@ struct player_t
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.
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
@ -679,6 +679,10 @@ struct player_t
UINT16 rocketsneakertimer; // Rocket Sneaker duration timer
UINT16 invincibilitytimer; // Invincibility timer
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 invincibilitywarning; // (Alternate) "Timer warning" boolean to signal Alt. Invin. is running out.
UINT8 eggmanexplode; // Fake item recieved, explode in a few seconds
SINT8 eggmanblame; // (-1 to 15) - Fake item recieved, who set this fake

View file

@ -862,6 +862,7 @@ struct menu_drawer_s const MENU_DRAWERS[] = {
};
struct odds_func_s const USEODDS_FUNCS[] = {
{ "ALTINVINODDS", &KO_AltInvinOdds },
{ "SPBRACEODDS", &KO_SPBRaceOdds },
{ NULL, NULL }
};
@ -1745,6 +1746,7 @@ struct int_const_s const INT_CONST[] = {
// invin constants
{"KART_NUMINVSPARKLESANIM", KART_NUMINVSPARKLESANIM},
{"BASEINVINTIME", BASEINVINTIME},
{"MININVINTIME", MININVINTIME},
// grow/shrink scale
{"GROW_SCALE", GROW_SCALE},

View file

@ -4772,6 +4772,12 @@ static void HWR_ProjectSprite(mobj_t *thing)
interptarg = thing;
if (R_IsOverlayingInvinciblePlayer(thing))
{
// Kill overlay misalignment
interptarg = thing->target;
}
R_InterpolateMobjState(interptarg, R_GetTimeFrac(RTF_LEVEL), &interp);
dispoffset = thing->dispoffset;
@ -5210,7 +5216,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
// New colormap stuff for skins Tails 06-07-2002
if (thing->colorized)
{
vis->colormap = R_GetTranslationColormap(TC_RAINBOW,
vis->colormap = R_GetTranslationColormap(
R_IsOverlayingInvinciblePlayer(thing) ? TC_BLINK : TC_RAINBOW,
thing->color,
GTC_CACHE);
}

View file

@ -679,7 +679,7 @@ static boolean K_BubbleReflectingTrapItem(const mobj_t *t)
static boolean K_StrongPlayerBump(const player_t *player)
{
return ((player->invincibilitytimer)
return (((!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (player->invincibilitytimer))
|| (player->growshrinktimer > 0));
}
@ -923,8 +923,15 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
const boolean hyudoroT1 = (t1->player->hyudorotimer > 0);
const boolean hyudoroT2 = (t2->player->hyudorotimer > 0);
boolean t1Condition = (t1->player->invincibilitytimer > 0);
boolean t2Condition = (t2->player->invincibilitytimer > 0);
boolean t1Condition = false;
boolean t2Condition = false;
// Rim suggestion: Flipover damage is negligible at best, just cull it from Invincibility as a whole.
if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
{
t1Condition = (t1->player->invincibilitytimer > 0);
t2Condition = (t2->player->invincibilitytimer > 0);
}
if ((t1Condition == true || flameT1 == true) && (t2Condition == true || flameT2 == true))
{
@ -932,7 +939,7 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
K_DoInstashield(t2->player);
return false;
}
else
else if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
{
if (t1Condition == true && t2Condition == false)
{
@ -945,10 +952,10 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
return true;
}
}
t1Condition = (t1->scale > t2->scale + (mapobjectscale/8));
t2Condition = (t2->scale > t1->scale + (mapobjectscale/8));
if ((t1Condition == true || flameT1 == true) && (t2Condition == true || flameT2 == true))
{
return false;

View file

@ -161,6 +161,9 @@ static patch_t *kp_flamefire[18];
// Frames of animation for the fire
#define MAXFLAMOFIRETICS 8
// Rotating Alt. Invin. sparkles
static patch_t *kp_altinvinsparkle;
static patch_t *kp_rankbumper;
static patch_t *kp_tinybumper[2];
static patch_t *kp_ranknobumpers;
@ -416,6 +419,13 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_driftgaugeparts[2], "K_WDGM3");
HU_UpdatePatch(&kp_driftgaugeparts[3], "K_WDGM4");
HU_UpdatePatch(&kp_driftgaugeparts[4], "K_DGAU3M");
// Alt. Invin. Sparkles
HU_UpdatePatch(&kp_altinvinsparkle, "ALTISPRK");
kp_altinvinsparkle->pivot.x = kp_altinvinsparkle->width / 2;
kp_altinvinsparkle->pivot.y = kp_altinvinsparkle->height / 2;
kp_altinvinsparkle->alignflags |= PATCHALIGN_USEPIVOTS;
// Flamometer UI Elements
HU_UpdatePatch(&kp_flamometer[0], "THERMOBACK");
@ -1284,6 +1294,21 @@ static void K_drawKartItem(void)
else
localpatch = kp_nodraw;
}
else if ((stplyr->invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY)))
{
itembar = FixedDiv(stplyr->invincibilitytimer, max(1, stplyr->maxinvincibilitytime));
if (stplyr->invincibilitycancel > 0)
flamebar = FixedDiv(stplyr->invincibilitycancel, 26);
if (leveltime & 1)
{
localpatch = K_GetCachedItemPatch(KITEM_INVINCIBILITY, tiny, 0);
isalt = true;
}
else
localpatch = kp_nodraw;
}
else if (K_GetShieldFromPlayer(stplyr) == KSHIELD_BUBBLE)
{
localpatch = K_GetCachedItemPatch(KITEM_BUBBLESHIELD, tiny, 0);
@ -1439,7 +1464,7 @@ static void K_drawKartItem(void)
V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, FRACUNIT, V_HUDTRANS|fflags, K_getItemAltPatch(tiny, false), colmap);
}
// Extensible meter, currently used by Grow, Rocket Sneakers and Flame Shield
// Extensible meter, currently used by Invincibilty, Grow, Rocket Sneakers and Flame Shield
// ...aren't you forgetting something?
if (itembar != -1)
K_DrawItemBar(fx, fy, fflags|V_HUDTRANS, itembar, (UINT8 []){0, 8, 12});
@ -1801,6 +1826,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)<<V_ALPHASHIFT;
}
static boolean K_RankIconCanSpinout(void)
{
return ((cv_spinoutroll.value) & 2);
@ -1822,6 +1856,7 @@ static boolean K_drawKartPositionFaces(void)
INT32 rankplayer[MAXPLAYERS];
INT32 bumperx, numplayersingame = 0;
UINT8 *colormap;
UINT32 invinchudtrans;
vector2_t offsets;
#ifdef ROTSPRITE
@ -1943,6 +1978,14 @@ static boolean K_drawKartPositionFaces(void)
V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, V_HUDTRANS|V_SNAPTOLEFT, facerank, colormap);
if ((players[rankplayer[i]].invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY)))
{
colormap = R_GetTranslationColormap(TC_BLINK, K_AltInvincibilityColor(leveltime / 2), GTC_CACHE);
invinchudtrans = K_InvincibilityHUDVisibility(players[rankplayer[i]].invincibilitytimer);
V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, invinchudtrans|V_SNAPTOLEFT|V_ADD, facerank, colormap);
}
if (LUA_HudEnabled(hud_battlebumpers))
{
if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumper > 0)
@ -2302,7 +2345,7 @@ void K_SetScoreboardModStatus(const char *name, SINT8 active)
CONS_Alert(CONS_WARNING, "Server mod '%s' does not exist so status cannot be changed.\n", name);
}
#define BASEMODS 15
#define BASEMODS 16
static void K_DrawServerMods(INT32 x, INT32 y)
{
UINT8 i, j;
@ -2325,8 +2368,9 @@ static void K_DrawServerMods(INT32 x, INT32 y)
{"Bump Spark", 0, &cv_kartbumpspark, -1, true},
{"Bump Drift", 0, NULL, K_GetBumpSpark() > 0, true},
{"Bump Spark", 0, NULL, K_GetBumpSpark() > BUMPSPARK_NOCHARGE, true},
{"Bump Spring", 0, &cv_kartbumpspring, -1, true}
{"Bump Spring", 0, &cv_kartbumpspring, -1, true},
//TODO: separate drawer that enumerates item changes?
{"Alt. Invin.", 0, NULL, K_IsKartItemAlternate(KITEM_INVINCIBILITY), true}
};
for (j = 0; j < 2; j++)
@ -3959,10 +4003,16 @@ static void K_drawKartMinimap(void)
spbdraw_t spb;
UINT16 usecolor;
boolean colorizeplayer;
fixed_t invingradient = 0;
#ifdef ROTSPRITE
angle_t rollangle = 0;
INT32 rot = 0;
INT32 sparkleflags;
patch_t *rotsparkle;
boolean halftrans = false;
fixed_t transmul = 0;
UINT32 invintrans = 0;
#endif
vector2_t iconoffsets;
@ -4176,9 +4226,11 @@ static void K_drawKartMinimap(void)
colorizeplayer = mobj->colorized;
if (players[i].invincibilitytimer)
invingradient = K_InvincibilityGradient(players[i].invincibilitytimer);
if ((players[i].invincibilitytimer) && ((invingradient > (FRACUNIT/2)) || (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))))
{
usecolor = K_RainbowColor(leveltime / 2);
usecolor = ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) ? K_AltInvincibilityColor(leveltime / 2) : K_RainbowColor(leveltime / 2));
colorizeplayer = true;
}
else
@ -4210,6 +4262,93 @@ static void K_drawKartMinimap(void)
K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, &iconoffsets);
#ifdef ROTSPRITE
if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) &&
(players[i].invincibilitytimer) && (invingradient))
{
// Draw Alt. Invin. sparkles
halftrans =
((splitflags & V_HUDTRANSHALF) == V_HUDTRANSHALF);
transmul = 0;
invintrans = 0;
sparkleflags =
splitflags & (~(V_HUDTRANS | V_HUDTRANSHALF));
if (halftrans)
{
transmul = FRACUNIT -
(V_GetHudTransHalf() * FRACUNIT / 10);
}
else
{
transmul =
FRACUNIT - (V_GetHudTrans() * FRACUNIT / 10);
}
transmul *= 2;
invintrans =
max(0,
min(9,
10 - FixedMul(FixedMul(10, invingradient),
transmul)))
<< V_ALPHASHIFT;
sparkleflags |= invintrans;
if (kp_altinvinsparkle)
{
rot = R_GetRollAngle(-TICTOANGLE(leveltime));
if (rot)
{
rotsparkle = Patch_GetRotated(
kp_altinvinsparkle, rot, false);
}
else
{
rotsparkle = kp_altinvinsparkle;
}
if (rotsparkle)
{
widthhalf = ((kp_altinvinsparkle->width) / 2);
heighthalf = ((kp_altinvinsparkle->height) / 2);
if (cv_minihead.value)
{
adjustx = FixedMul(
4,
(widthhalf -
kp_altinvinsparkle->leftoffset) *
FRACUNIT / widthhalf);
adjusty = FixedMul(
4,
(heighthalf -
kp_altinvinsparkle->topoffset) *
FRACUNIT / heighthalf);
}
iconoffsets.x = widthhalf -
kp_altinvinsparkle->leftoffset -
adjustx;
iconoffsets.y = heighthalf -
kp_altinvinsparkle->topoffset -
adjusty;
K_drawKartMinimapIcon(interpx,
interpy,
x,
y,
sparkleflags | V_ADD,
rotsparkle,
colormap,
&iconoffsets);
}
}
}
#endif
if (mobj->player)
{
// Draw the Nametag
@ -4413,8 +4552,13 @@ static void K_drawKartMinimap(void)
#endif
colorizeplayer = mobj->colorized;
invingradient =
K_InvincibilityGradient(
players[localplayers[i]].invincibilitytimer);
if (players[localplayers[i]].invincibilitytimer)
if ((players[localplayers[i]].invincibilitytimer) &&
((invingradient > (FRACUNIT / 2)) ||
(!K_IsKartItemAlternate(KITEM_INVINCIBILITY))))
{
usecolor = (K_RainbowColor(leveltime / 2));
colorizeplayer = true;
@ -4483,6 +4627,84 @@ static void K_drawKartMinimap(void)
K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, &iconoffsets);
#ifdef ROTSPRITE
if ((!nocontest) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY)) &&
(players[localplayers[i]].invincibilitytimer) && (invingradient))
{
// Draw Alt. Invin. sparkles
halftrans = ((splitflags & V_HUDTRANSHALF) == V_HUDTRANSHALF);
transmul = 0;
invintrans = 0;
sparkleflags = splitflags & (~(V_HUDTRANS | V_HUDTRANSHALF));
if (halftrans)
{
transmul = FRACUNIT - (V_GetHudTransHalf() * FRACUNIT / 10);
}
else
{
transmul = FRACUNIT - (V_GetHudTrans() * FRACUNIT / 10);
}
transmul *= 2;
invintrans =
max(0,
min(9,
10 - FixedMul(FixedMul(10, invingradient), transmul)))
<< V_ALPHASHIFT;
sparkleflags |= invintrans;
if (kp_altinvinsparkle)
{
rot = R_GetRollAngle(-TICTOANGLE(leveltime));
if (rot)
{
rotsparkle = Patch_GetRotated(
kp_altinvinsparkle, rot, false);
}
else
{
rotsparkle = kp_altinvinsparkle;
}
if (rotsparkle)
{
widthhalf = ((kp_altinvinsparkle->width) / 2);
heighthalf = ((kp_altinvinsparkle->height) / 2);
if (cv_minihead.value)
{
adjustx = FixedMul(
4,
(widthhalf - kp_altinvinsparkle->leftoffset) *
FRACUNIT / widthhalf);
adjusty = FixedMul(
4,
(heighthalf - kp_altinvinsparkle->topoffset) *
FRACUNIT / heighthalf);
}
iconoffsets.x =
widthhalf - kp_altinvinsparkle->leftoffset - adjustx;
iconoffsets.y =
heighthalf - kp_altinvinsparkle->topoffset - adjusty;
K_drawKartMinimapIcon(interpx,
interpy,
x,
y,
sparkleflags | V_ADD,
rotsparkle,
colormap,
&iconoffsets);
}
}
}
#endif
// Target reticule
if ((gametype == GT_RACE && players[localplayers[i]].position == spbplace)
|| ((gametyperules & GTR_WANTED) && K_IsPlayerWanted(&players[localplayers[i]])))

View file

@ -19,7 +19,7 @@
#include "d_player.h"
#include "g_game.h"
#include "info.h"
/* include "m_easing.h" */
#include "m_easing.h" // Invincibility gradienting
#include "m_fixed.h"
#include "m_random.h"
#include "p_local.h"
@ -470,7 +470,6 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush)
return distance;
}
/*
#define INVODDS 30
// Prevent integer overflows; don't let this go past 16383
@ -514,7 +513,6 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist)
}
#undef FRAC_95pct
*/
// updates all result cooldown timers, and sets cooldowns for "unique" items
void K_UpdateItemCooldown(void)
@ -1518,9 +1516,8 @@ void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force)
// Unique odds functions, for REAL this time
// Alt. Invin. odds.
// Leaving this around because it might have the chance to be repurposed in the future.
/*INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme)
// Alt. Invin. odds
INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme)
{
(void)result;
odds = K_KartGetInvincibilityOdds(roulette->clusterDist);
@ -1538,7 +1535,7 @@ void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force)
odds *= BASEODDSMUL;
return odds;
}*/
}
// SPB odds
INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme)
@ -1994,7 +1991,7 @@ void K_PlayerItemThink(player_t *player, boolean onground)
}
break;
case KITEM_INVINCIBILITY:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage, 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);

View file

@ -211,7 +211,7 @@ void K_StartRoulette(player_t *player, kartroulettetype_e roulettetype);
void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force);
//useoddsfunc_f KO_AltInvinOdds;
useoddsfunc_f KO_AltInvinOdds;
useoddsfunc_f KO_SPBRaceOdds;
void K_DoThunderShield(player_t *player);

View file

@ -309,9 +309,12 @@ void K_RegisterKartStuff(void)
CV_RegisterVar(&cv_kartstacking_panel_separate);
CV_RegisterVar(&cv_kartstacking_panel_maxgrade);
CV_RegisterVar(&cv_kartstacking_invincibility_speedboost);
CV_RegisterVar(&cv_kartstacking_invincibility_accelboost);
CV_RegisterVar(&cv_kartstacking_invincibility_handleboost);
CV_RegisterVar(&cv_kartstacking_invincibility_classicspeedboost);
CV_RegisterVar(&cv_kartstacking_invincibility_classicaccelboost);
CV_RegisterVar(&cv_kartstacking_invincibility_classichandleboost);
CV_RegisterVar(&cv_kartstacking_invincibility_alternatespeedboost);
CV_RegisterVar(&cv_kartstacking_invincibility_alternateaccelboost);
CV_RegisterVar(&cv_kartstacking_invincibility_alternatehandleboost);
CV_RegisterVar(&cv_kartstacking_invincibility_stackable);
CV_RegisterVar(&cv_kartstacking_grow_speedboost);
@ -408,6 +411,12 @@ void K_RegisterKartStuff(void)
CV_RegisterVar(&cv_kartinvintheme);
CV_RegisterVar(&cv_kartinvindist);
CV_RegisterVar(&cv_kartinvindistmul);
CV_RegisterVar(&cv_kartinvin_maxtime);
CV_RegisterVar(&cv_kartinvin_midtime);
// experimental stuff
CV_RegisterVar(&cv_kartbubble_defense_canidle);
CV_RegisterVar(&cv_kartbubble_defense_damagerate);
@ -490,12 +499,12 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value)
#define WORSTINVBUMPPOWER (FRACUNIT / 14)
#define INVBUMPBOTTLENECK (FRACUNIT - (WORSTINVBUMPPOWER))
#define BUBBLEBUMPBOTTLENECK (FRACUNIT / 150)
static fixed_t K_PlayerWeight(mobj_t* mobj, mobj_t* against)
{
fixed_t weight = 5 * FRACUNIT;
fixed_t bubbleMultiplier = FRACUNIT;
const boolean invinisalt = K_IsKartItemAlternate(KITEM_INVINCIBILITY);
if (!mobj->player)
return weight;
@ -511,6 +520,12 @@ static fixed_t K_PlayerWeight(mobj_t* mobj, mobj_t* against)
{
return 0; // This player does not cause any bump action
}
else if (invinisalt && against->player->invincibilitytimer)
{
// Scale Alt. Invin. weight to their power. At full power, you get shoved
// off! Might cause less shitty-feeling bumpcheck moments.
return max(0, FRACUNIT - against->player->kartweight * K_InvincibilityGradient(against->player->invincibilitytimer));
}
}
// Applies rubberbanding, to prevent rubberbanding bots
@ -519,24 +534,36 @@ static fixed_t K_PlayerWeight(mobj_t* mobj, mobj_t* against)
weight = (mobj->player->kartweight) * FRACUNIT;
if (K_GetShieldFromPlayer(mobj->player) == KSHIELD_BUBBLE)
if (invinisalt && mobj->player->invincibilitytimer)
{
// Cap the gradient at 1.0 to prevent exaggerated nonsense.
fixed_t mygradient = min(FRACUNIT, K_InvincibilityGradient(mobj->player->invincibilitytimer));
// Scale Alt. Invin. weight to your power. At full power, they get shoved off!
// Might cause less shitty-feeling bumpcheck moments.
weight = FixedMul(weight, mygradient);
// Like the Bubble Shield, nerf bumps a good bit to make them feel less ridiculous.
// As your power depletes, this nerf gets less necessary, so scale it to match.
weight = FixedMul(weight, FRACUNIT - FixedMul(INVBUMPBOTTLENECK, mygradient));
}
else if (K_GetShieldFromPlayer(mobj->player) == KSHIELD_BUBBLE && mobj->player->bubblecool == 0)
{
weight = max(BUBBLEMINWEIGHT, weight);
bubbleMultiplier = BUBBLEBUMPBOTTLENECK;
weight = FixedMul(weight, FRACUNIT / 16);
}
if (mobj->player->speed > spd)
weight += (mobj->player->speed - spd) / 8;
if (bubbleMultiplier)
{
weight = FixedMul(weight, bubbleMultiplier);
}
if (mobj->player->speed > spd)
weight += (mobj->player->speed - spd) / 8;
return weight;
}
#undef BUBBLEBUMPBOTTLENECK
#undef INVBUMPBOTTLENECK
#undef WORSTINVBUMPPOWER
@ -960,6 +987,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 = (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) ? ((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
@ -981,6 +1017,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)
{
@ -2118,7 +2157,7 @@ boolean K_ApplyOffroad(const 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;
}
@ -2439,24 +2478,93 @@ 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, (INVINMIDTIME/10));
return Easing_OutCubic(
min(FRACUNIT, FixedMul(u - FRACUNIT, FRACUNIT/4)), (INVINMIDTIME/10), (INVINMAXTIME/10));
}
UINT16 K_GetInvincibilityTime(player_t *player)
{
return BASEINVINTIME;
UINT32 i, pingame;
fixed_t distmul = FRACUNIT;
if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
return BASEINVINTIME;
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;
}
if (K_LegacyOddsMode())
{
// Legacy waypointing is janky and finicky, so tack on a safety-net multiplier.
// If an invincible player gets ahead of the cluster player, bottlenecking activates
// regardless.
distmul = LEGACYALTINVINMUL;
}
fixed_t clustermul = K_InvincibilityEasing(FixedMul(player->distancefromcluster,distmul));
UINT16 invintics = FixedMul(BASEINVINTIME, clustermul);
return max(MININVINTIME, invintics);
}
fixed_t K_GetInvincibilitySpeed(UINT16 time)
{
return INVINSPEEDBOOST;
if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
return INVINSPEEDBOOSTCLS;
fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time));
return Easing_OutCubic(t, 0, INVINSPEEDBOOSTALT);
}
fixed_t K_GetInvincibilityAccel(UINT16 time)
{
return INVINACCELBOOST;
if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
return INVINACCELBOOSTCLS;
fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time));
return Easing_OutCubic(t, 0, INVINACCELBOOSTALT);
}
fixed_t K_GetInvincibilityHandling(UINT16 time)
{
return INVINHANDLEBOOST;
if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
return INVINHANDLEBOOSTCLS;
fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time));
return Easing_OutCubic(t, 0, INVINHANDLEBOOSTALT);
}
static fixed_t diminish(fixed_t speedboost)
@ -4853,15 +4961,47 @@ boolean K_PlayFullInvinTheme(void)
void K_DoInvincibility(player_t *player, tic_t time)
{
const boolean isalt = K_IsKartItemAlternate(KITEM_INVINCIBILITY);
if (!player->invincibilitytimer)
{
mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INVULNFLASH);
P_SetTarget(&overlay->target, player->mo);
overlay->destscale = player->mo->scale;
P_SetScale(overlay, player->mo->scale);
if (isalt)
{
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;
if (isalt)
{
// 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;
}
player->maxinvincibilitytime = player->invincibilitytimer;
if (player->maxinvincibilitytime <= MININVINTIME && isalt)
{
// Merritt suggestion: Kill bottlenecking if you get a short Invincibility.
// Anti-bottleneck code 2: Signify to play the warning signal later than usual!
player->invincibilitybottleneck = (UINT16)(-2);
}
if (P_IsLocalPlayer(player) == true)
{
@ -7085,11 +7225,98 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->invincibilitytimer)
{
player->invincibilitytimer--;
INT16 invinfac = 1;
if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) &&
(player->invincibilitytimer > 2))
{
UINT32 invindist = INVINDIST >> 2;
// Value to subtract from the Invincibility timer during bottlenecking.
INT16 invin_subtrahend = 1;
if ((INT16)(player->invincibilitybottleneck) >= 0)
{
if (player->distancefromcluster < invindist)
{
player->invincibilitybottleneck =
min(256, player->invincibilitybottleneck + 4);
invinfac = FixedMul(
8,
max(min(FRACUNIT,
FRACUNIT - (player->distancefromcluster /
(INVINDIST >> 2))),
0));
}
else
{
player->invincibilitybottleneck =
max(0, (INT32)(player->invincibilitybottleneck) - 2);
}
invin_subtrahend =
max(1,
FixedMul((UINT16)invinfac,
max(0, player->invincibilitybottleneck) << 8));
}
player->invincibilitytimer = (UINT16)(max(
2, (INT32)(player->invincibilitytimer) - invin_subtrahend));
const boolean warning_cond_standard =
((K_InvincibilityGradient(player->invincibilitytimer) <
(FRACHALF * invin_subtrahend)) &&
(player->maxinvincibilitytime >= (BASEINVINTIME - TICRATE)));
const boolean warning_cond_nobottleneck =
((K_InvincibilityGradient(player->invincibilitytimer) <
SecsToFixedTens(MININVINTIME / 2)) &&
(player->maxinvincibilitytime >= (MININVINTIME - TICRATE)));
const boolean warning_cond =
((INT16)(player->invincibilitybottleneck) == -2)
? warning_cond_nobottleneck
: warning_cond_standard;
if (warning_cond)
{
if (!player->invincibilitywarning)
{
S_StartSound(player->mo, sfx_cdfm71);
player->invincibilitywarning = 1;
}
}
if (((INT16)(player->invincibilitybottleneck) > 127) &&
(!S_SoundPlaying(player->mo, sfx_s3kbes)))
{
S_StartSound(player->mo, sfx_s3kbes);
}
}
else
{
player->invincibilitytimer--;
if (S_SoundPlaying(player->mo, sfx_s3kbes) &&
K_IsKartItemAlternate(KITEM_INVINCIBILITY))
{
// Shut off the bottlenecker sound.
S_StopSoundByID(player->mo, sfx_s3kbes);
}
player->invincibilitybottleneck = 0;
player->invincibilitywarning = 0;
}
if (!player->invincibilitytimer)
K_KartResetPlayerColor(player,true);
}
else
{
player->invincibilitybottleneck = 0; // No need for bottlenecking.
player->maxinvincibilitytime = 0;
player->invincibilitywarning = 0;
player->invincibilitycancel = -1;
}
if (player->checkskip)
player->checkskip--;
@ -7432,16 +7659,26 @@ void K_KartResetPlayerColor(player_t *player, boolean disablecolor)
if (player->invincibilitytimer || player->powers[pw_invulnerability]) // You're gonna kiiiiill
{
fullbright = true;
boolean skip = false;
player->mo->color = K_RainbowColor(leveltime / 2);
if (player->invincibilitytimer)
if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
{
player->mo->colorized = true;
}
fullbright = true;
goto finalise;
player->mo->color = K_RainbowColor(leveltime / 2);
if (player->invincibilitytimer)
{
player->mo->colorized = true;
}
skip = true;
}
if (skip)
{
goto finalise;
}
}
if (player->growshrinktimer) // Ditto, for grow/shrink
@ -8759,9 +8996,9 @@ INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue)
if (K_SlipdashActive() && K_Sliptiding(player)) // slight handling boost based on weight
turnvalue = FixedMul(turnvalue, FRACUNIT + (10 - player->kartweight)*FRACUNIT/48);
if (player->invincibilitytimer || player->sneakertimer ||
player->bubbleboost || player->growshrinktimer > 0
|| K_IsAltShrunk(player))
if ((player->invincibilitytimer && (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))
|| player->sneakertimer || player->bubbleboost ||
player->growshrinktimer > 0 || K_IsAltShrunk(player))
{
turnvalue = FixedMul(turnvalue, FixedDiv(5 * FRACUNIT, 4 * FRACUNIT));
}
@ -8843,6 +9080,19 @@ static void K_SpawnDriftEFX(player_t *player,SINT8 level)
}
}
// Sliptide conditions for Alternative Invincibility.
static boolean K_AltInvinSliptideCondition(player_t *player)
{
if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
{
// Not in Alternative, hit the bricks!
return false;
}
// Allow sliptides if you're above half power.
return (K_InvincibilityGradient(player->invincibilitytimer) > (FRACUNIT/2));
}
fixed_t K_GetSpeedPercentage(const player_t *player)
{
if (!player)
@ -8888,7 +9138,7 @@ boolean K_InterceptArrowBullet(player_t *player)
if (!player)
return false;
return ((player->invincibilitytimer) || (player->growshrinktimer > 0) || (player->flamestore));
return ((player->invincibilitytimer && !K_IsKartItemAlternate(KITEM_INVINCIBILITY)) || (player->growshrinktimer > 0) || (player->flamestore));
}
// 0.25 fracunits
@ -8902,7 +9152,7 @@ static boolean K_OtherSliptideCondition(player_t* player)
return false;
return (
K_AltShrinkSliptideCondition(player) ||
K_AltInvinSliptideCondition(player) || K_AltShrinkSliptideCondition(player) ||
(cv_handleboostslip.value && (player->handleboost >= HANDLEBOOSTTHRESHOLD)));
}
@ -9405,6 +9655,7 @@ static UINT32 K_UpdateDistanceFromCluster(player_t* player)
if (pingame <= 1)
{
// There's only us around.
player->invincibilitybottleneck = (UINT16)(-1); // No bottlenecking!
return 0;
}
else if ((pingame == 3))
@ -10140,6 +10391,8 @@ void K_StripOther(player_t *player)
player->roulettetype = KROULETTETYPE_NORMAL;
player->invincibilitytimer = 0;
player->invincibilitywarning = 0;
player->invincibilitycancel = -1;
if (player->growshrinktimer)
{
@ -10360,6 +10613,14 @@ INT32 K_GetShieldFromItem(INT32 item)
}
}
static boolean K_InvincibilitySlotHogging(player_t *player)
{
if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY))
return false;
return ((player->invincibilitytimer) != 0);
}
//
// K_MoveKartPlayer
//
@ -10396,6 +10657,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
|| player->itemroulette
|| player->rocketsneakertimer
|| player->eggmanexplode
|| ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && player->invincibilitytimer)
|| (player->growshrinktimer > 0)
|| player->flametimer
|| (leveltime < starttime)
@ -10606,6 +10868,32 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->growcancel = 0;
}
}
// Invincibility
else if (K_InvincibilitySlotHogging(player))
{
// (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)
{
K_UnsetItemOut(player);

View file

@ -29,6 +29,7 @@ Make sure this matches the actual number of states
*/
#define KART_NUMINVSPARKLESANIM 12
#define BASEINVINTIME (10 * TICRATE)
#define MININVINTIME (7 * TICRATE)
#define GROW_SCALE ((3*FRACUNIT)/2)
#define SHRINK_SCALE ((6*FRACUNIT)/8)
@ -84,6 +85,12 @@ extern vector3_t clusterpoint, clusterdtf;
// Bump weight for a Bubble Shield
#define BUBBLEMINWEIGHT (5 * FRACUNIT)
// Invincibility-related constants
#define INVINDIST CV_Get(&cv_kartinvindist)
#define INVINDISTMUL CV_Get(&cv_kartinvindistmul)
#define INVINMIDTIME CV_Get(&cv_kartinvin_midtime)
#define INVINMAXTIME CV_Get(&cv_kartinvin_maxtime)
// 1.22 * FRACUNIT
#define LEGACYALTINVINMUL (122 * FRACUNIT / 100)
@ -107,9 +114,12 @@ extern vector3_t clusterpoint, clusterdtf;
#define SEPARATEPANELS CV_Get(&cv_kartstacking_panel_separate)
#define MAXPANELSTACK CV_Get(&cv_kartstacking_panel_maxgrade)
#define INVINSPEEDBOOST CV_Get(&cv_kartstacking_invincibility_speedboost)
#define INVINACCELBOOST CV_Get(&cv_kartstacking_invincibility_accelboost)
#define INVINHANDLEBOOST CV_Get(&cv_kartstacking_invincibility_handleboost)
#define INVINSPEEDBOOSTCLS CV_Get(&cv_kartstacking_invincibility_classicspeedboost)
#define INVINACCELBOOSTCLS CV_Get(&cv_kartstacking_invincibility_classicaccelboost)
#define INVINHANDLEBOOSTCLS CV_Get(&cv_kartstacking_invincibility_classichandleboost)
#define INVINSPEEDBOOSTALT CV_Get(&cv_kartstacking_invincibility_alternatespeedboost)
#define INVINACCELBOOSTALT CV_Get(&cv_kartstacking_invincibility_alternateaccelboost)
#define INVINHANDLEBOOSTALT CV_Get(&cv_kartstacking_invincibility_alternatehandleboost)
#define INVINSTACKABLE CV_Get(&cv_kartstacking_invincibility_stackable)
#define GROWSPEEDBOOST CV_Get(&cv_kartstacking_grow_speedboost)
@ -271,6 +281,7 @@ void K_ResetPogoSpring(player_t *player);
extern boolean forcefullinvintheme;
boolean K_PlayFullInvinTheme(void);
void K_DoInvincibility(player_t *player, tic_t time);
fixed_t K_InvincibilityGradient(UINT16 time);
UINT16 K_GetInvincibilityTime(player_t *player);
fixed_t K_GetInvincibilitySpeed(UINT16 time);
fixed_t K_GetInvincibilityAccel(UINT16 time);

View file

@ -4483,6 +4483,15 @@ static int lib_kGetNewSpeed(lua_State *L)
return 1;
}
static int lib_kInvincibilityGradient(lua_State *L)
{
UINT16 time = (UINT16)luaL_checkinteger(L, 1);
// HUDSAFE
lua_pushinteger(L, K_InvincibilityGradient(time));
return 1;
}
static int lib_kGetInvincibilitySpeed(lua_State *L)
{
UINT16 time = (UINT16)luaL_checkinteger(L, 1);
@ -5727,6 +5736,7 @@ static luaL_Reg lib[] = {
{"K_BoostChain",lib_kBoostChain},
{"K_ChainOrDeincrementTime",lib_kChainOrDeincrementTime},
{"K_GetNewSpeed", lib_kGetNewSpeed},
{"K_InvincibilityGradient", lib_kInvincibilityGradient},
{"K_GetInvincibilitySpeed", lib_kGetInvincibilitySpeed},
{"K_GetInvincibilityAccel", lib_kGetInvincibilityAccel},
{"K_3dKartMovement", lib_k3dKartMovement},

View file

@ -413,6 +413,10 @@ static int lib_lenLocalplayers(lua_State *L)
X(arrowbullet) \
X(rocketsneakertimer) \
X(invincibilitytimer) \
X(maxinvincibilitytime) \
X(invincibilitybottleneck) \
X(invincibilitycancel) \
X(invincibilitywarning) \
X(eggmanexplode) \
X(eggmanblame) \
X(bananadrag) \
@ -892,6 +896,19 @@ static int player_get(lua_State *L)
case player_invincibilitytimer:
lua_pushinteger(L, plr->invincibilitytimer);
break;
case player_maxinvincibilitytime:
lua_pushinteger(L, plr->maxinvincibilitytime);
break;
case player_invincibilitybottleneck:
// Push as an INT16 due to the negative value signal systems.
lua_pushinteger(L, (INT16)plr->invincibilitybottleneck);
break;
case player_invincibilitycancel:
lua_pushinteger(L, plr->invincibilitycancel);
break;
case player_invincibilitywarning:
lua_pushboolean(L, (boolean)plr->invincibilitywarning);
break;
case player_eggmanexplode:
lua_pushinteger(L, plr->eggmanexplode);
break;
@ -1673,6 +1690,21 @@ static int player_set(lua_State *L)
case player_invincibilitytimer:
plr->invincibilitytimer = luaL_checkinteger(L, 3);
break;
case player_maxinvincibilitytime:
{
UINT16 maxinv = max(1, (UINT16)luaL_checkinteger(L, 3)); // Prevent zero-divides
plr->maxinvincibilitytime = maxinv;
break;
}
case player_invincibilitybottleneck:
plr->invincibilitybottleneck = (UINT16)luaL_checkinteger(L, 3);
break;
case player_invincibilitycancel:
plr->invincibilitycancel = (INT16)luaL_checkinteger(L, 3);
break;
case player_invincibilitywarning:
plr->invincibilitywarning = (UINT8)luaL_checkboolean(L, 3);
break;
case player_eggmanexplode:
plr->eggmanexplode = luaL_checkinteger(L, 3);
break;

View file

@ -155,6 +155,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon)
if (player->stealingtimer || player->stolentimer
|| player->rocketsneakertimer
|| player->eggmanexplode
|| ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (player->invincibilitytimer))
|| (player->growshrinktimer > 0)
|| player->flametimer)
return false;

View file

@ -6860,6 +6860,71 @@ 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))))<<RF_TRANSSHIFT;
}
// Called only when MT_OVERLAY thinks.
static void P_PlayerInvincibilityOverlay(mobj_t *thing)
{
I_Assert(thing->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_AltInvincibilityColor(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);
// Rotation is handled in r_patchrotation
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->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;
@ -7205,6 +7270,11 @@ static void P_MobjSceneryThink(mobj_t *mobj)
else
{
P_AddOverlay(mobj);
if ((mobj->extravalue2) && (mobj->target->player))
{
// Could be overlaying an invincible player.
P_PlayerInvincibilityOverlay(mobj);
}
}
break;
case MT_WATERDROP:

View file

@ -711,6 +711,10 @@ static void P_NetSyncPlayers(savebuffer_t *save)
SYNC(players[i].rocketsneakertimer);
SYNC(players[i].invincibilitytimer);
SYNC(players[i].maxinvincibilitytime);
SYNC(players[i].invincibilitybottleneck);
SYNC(players[i].invincibilitycancel);
SYNC(players[i].invincibilitywarning);
SYNC(players[i].eggmanexplode);
SYNC(players[i].eggmanblame);

View file

@ -2309,7 +2309,10 @@ void P_MovePlayer(player_t *player)
&& onground && (leveltime & 1))
K_SpawnBoostTrail(player);
if (player->invincibilitytimer > 0)
if ((player->invincibilitytimer > 0) &&
(((leveltime %
max(1, 10 - FixedMul(9, K_InvincibilityGradient(player->invincibilitytimer)))) == 0) ||
(!K_IsKartItemAlternate(KITEM_INVINCIBILITY))))
{
K_SpawnSparkleTrail(player->mo);
}

View file

@ -46,6 +46,7 @@ patch_t *Patch_GetRotatedSprite(
void *info, INT32 rotationangle);
INT32 R_GetRollAngle(angle_t rollangle);
boolean R_IsOverlayingInvinciblePlayer(mobj_t* mobj);
angle_t R_GetPitchRollAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp);
angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer);
angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp);

View file

@ -82,6 +82,14 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer)
return rollAngle;
}
// Hacky boolean to check if we're the rainbow Invincibility overlay
boolean R_IsOverlayingInvinciblePlayer(mobj_t* mobj)
{
return ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (mobj->type == MT_OVERLAY) &&
(mobj->target) && (mobj->extravalue2) && (mobj->target->player) &&
(mobj->target->player->invincibilitytimer));
}
angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer)
{
angle_t rollAngle = mobj->rollangle;
@ -90,13 +98,26 @@ angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer)
{
rollAngle += R_PlayerSpriteRotation(mobj->player, viewPlayer);
}
else if (R_IsOverlayingInvinciblePlayer(mobj))
{
rollAngle += R_PlayerSpriteRotation(mobj->target->player, viewPlayer);
}
return rollAngle;
}
angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp)
{
angle_t rollOrPitch = R_GetPitchRollAngle(mobj, viewPlayer, interp);
angle_t rollOrPitch;
if (R_IsOverlayingInvinciblePlayer(mobj))
{
rollOrPitch = R_GetPitchRollAngle(mobj->target, viewPlayer, interp);
}
else
{
rollOrPitch = R_GetPitchRollAngle(mobj, viewPlayer, interp);
}
return (rollOrPitch + R_ModelRotationAngle(mobj, viewPlayer));
}

View file

@ -922,7 +922,7 @@ UINT8 *R_GetSpriteTranslation(vissprite_t *vis)
if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
{
return R_GetTranslationColormap(TC_RAINBOW,
return R_GetTranslationColormap(R_IsOverlayingInvinciblePlayer(vis->mobj) ? TC_BLINK : TC_RAINBOW,
static_cast<skincolornum_t>(vis->mobj->color),
GTC_CACHE);
}
@ -1832,6 +1832,12 @@ static void R_ProjectSprite(mobj_t *thing)
interpmobjstate_t interp = {0};
mobj_t *interptarg = thing;
if (R_IsOverlayingInvinciblePlayer(thing))
{
// Kill overlay misalignment
interptarg = thing->target;
}
// do interpolation
R_InterpolateMobjState(interptarg, R_GetTimeFrac(RTF_LEVEL), &interp);