diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 30f1e3b89..48b6a4759 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -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); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 5cac2529d..63217f254 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -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; diff --git a/src/d_player.h b/src/d_player.h index d45776063..5120717b4 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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 diff --git a/src/deh_tables.c b/src/deh_tables.c index a638ae450..f9d2f45af 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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}, diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 1d070911f..159850888 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -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); } diff --git a/src/k_collide.c b/src/k_collide.c index ece5a9512..cc4966023 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -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; diff --git a/src/k_hud.c b/src/k_hud.c index 49a4be66a..5fc993782 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -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< 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]]))) diff --git a/src/k_items.c b/src/k_items.c index 51bf67a28..b387aa68d 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -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); diff --git a/src/k_items.h b/src/k_items.h index 85f9d099a..9098661a6 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -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); diff --git a/src/k_kart.c b/src/k_kart.c index bc4fd4393..29a943a26 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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); diff --git a/src/k_kart.h b/src/k_kart.h index a461788d2..63433543d 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -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); diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 6296addc4..5d3b23003 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -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}, diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 790a0ca03..dcfc4bd32 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -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; diff --git a/src/p_inter.c b/src/p_inter.c index bf6a7f658..9d75df2b7 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -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; diff --git a/src/p_mobj.c b/src/p_mobj.c index 7a29d3ed4..3f0b3a765 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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))))<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: diff --git a/src/p_saveg.c b/src/p_saveg.c index 98ae7d6f0..9213ccfdd 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -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); diff --git a/src/p_user.c b/src/p_user.c index a0a834e8b..58791c369 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -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); } diff --git a/src/r_patch.h b/src/r_patch.h index 96e9e8139..50e41b43c 100644 --- a/src/r_patch.h +++ b/src/r_patch.h @@ -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); diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index de1d2e51a..406c8082e 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -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)); } diff --git a/src/r_things.cpp b/src/r_things.cpp index 8465e86ba..47a56fb0a 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -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(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);