From 4b448de85ee821abe1c357bf616865010b5d0acc Mon Sep 17 00:00:00 2001 From: Anonimus Date: Sat, 25 Oct 2025 10:46:14 -0400 Subject: [PATCH] Bubble Shield adjustments Primarily to improve its defensive capabilities * Trade penetrating attacks (like a Thunder Shield's AoE attack) * Remove bump-chip * Make how items damage the Bubble Shield more modular overall --- src/k_collide.c | 63 ++++++++++++++++++++++++++++++++++++++++++++----- src/k_kart.c | 38 +++++++++++------------------ src/k_kart.h | 13 +++++----- src/p_inter.c | 31 +++++++++++++++++++++++- 4 files changed, 108 insertions(+), 37 deletions(-) diff --git a/src/k_collide.c b/src/k_collide.c index db3ba4a02..e2b349beb 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -519,7 +519,7 @@ void K_ThunderShieldAttack(mobj_t *actor, fixed_t size) P_BlockThingsIterator(bx, by, PIT_ThunderShieldAttack); } -static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble) +static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble, INT16 dmg) { if (!player) { @@ -533,10 +533,20 @@ static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble) return; } - // Pop the bubble immediately after an item hit. - K_RemoveBubbleHealth(player, BUBBLEITMDAMAGE); + // Damage the shield after an item hit. + K_RemoveBubbleHealth(player, dmg); - if (player->bubblehealth > 0) + if (K_GetShieldFromPlayer(player) == KSHIELD_BUBBLE) + { + if ((dmg > 0) && (bubble) && (!P_MobjWasRemoved(bubble))) + { + // Play a distinct "crack!" noise to clue the player in. + S_StartSound(bubble, sfx_s3k6e); + } + } + + // Apply a cooldown if the Bubble Shield took damage without shattering. + if ((player->bubblehealth > 0) && (dmg > 0)) { player->bubblecool = 15*4; player->itemflags &= ~IF_HOLDREADY; @@ -603,6 +613,47 @@ static boolean K_PlayerCanBeBubbleBumped(player_t *player) || (player->growshrinktimer > 0)); } +static INT16 K_GetBubbleDamage(mobj_t* itm) +{ + if ((!itm) || (P_MobjWasRemoved(itm))) + { + return 0; + } + + INT16 dmg = BUBBLEITMDAMAGE; + + switch (itm->type) + { + case MT_EGGMANITEM: + case MT_BALLHOG: + // Experiment: Let Bubble Shields hard-counter Ballhogs and Eggboxes. + dmg = 0; + break; + case MT_SINK: + case MT_JAWZ: + case MT_PLAYER: + // Sinks, Jawz, and powered-up players smash Bubble Shields to bits. + dmg = MAXBUBBLEHEALTH; + break; + case MT_ORBINAUT: + fixed_t momentum = K_Momentum3D(itm); + fixed_t orbi_momentum_epsilon = FixedMul(FixedMul(itm->radius, itm->scale), + K_GetKartGameSpeedScalar(gamespeed)); + + // Reward players on the offensive by letting fast Orbinauts shatter Bubble Shields. + if (momentum >= orbi_momentum_epsilon) + { + dmg = MAXBUBBLEHEALTH; + } + + break; + default: + break; + } + + return dmg; +} + boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) { if (t2->type == MT_PLAYER) @@ -624,7 +675,7 @@ boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) if (reflected) { // Drain my stuff please. - K_BubbleShieldCollideDrain(t1->target->player, t1); + K_BubbleShieldCollideDrain(t1->target->player, t1, K_GetBubbleDamage(t2)); } return reflected; @@ -637,7 +688,7 @@ boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) if (shootable) { // Drain my stuff please. - K_BubbleShieldCollideDrain(t1->target->player, t1); + K_BubbleShieldCollideDrain(t1->target->player, t1, K_GetBubbleDamage(t2)); } return shootable; diff --git a/src/k_kart.c b/src/k_kart.c index 55ed68b21..00c7643c0 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -760,18 +760,7 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol p1shield = K_GetShieldFromPlayer(mobj1->player); // Moved here so it only fires once on bump. - if (p1shield == KSHIELD_BUBBLE) - { - // Each bump does chip damage to the shield. - K_RemoveBubbleHealth(mobj1->player, BUBBLEBUMPCHIP); - - if (mobj1->player->bubblehealth > 0) - { - // Play a distinct "crack!" noise to clue the player in. - S_StartSound(mobj1, sfx_s3k6e); - } - } - else if (p1shield == KSHIELD_NONE) + if (p1shield == KSHIELD_NONE) { if (mobj2->player && (mobj1->player->rings > 0)) { @@ -796,18 +785,7 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol p2shield = K_GetShieldFromPlayer(mobj2->player); // Moved here so it only fires once on bump. - if (p2shield == KSHIELD_BUBBLE) - { - // Each bump does chip damage to the shield. - K_RemoveBubbleHealth(mobj2->player, BUBBLEBUMPCHIP); - - if (mobj2->player->bubblehealth > 0) - { - // Play a distinct "crack!" noise to clue the player in. - S_StartSound(mobj2, sfx_s3k6e); - } - } - else if (p2shield == KSHIELD_NONE) + if (p2shield == KSHIELD_NONE) { if (mobj1->player && (mobj2->player->rings > 0)) { @@ -3027,6 +3005,18 @@ angle_t K_MomentumAngle(mobj_t *mo) } } +fixed_t K_GetMomentum(mobj_t *mo, boolean twodee) +{ + fixed_t xydist = R_PointToDist2(0, 0, mo->momx, mo->momy); + + if (twodee) + { + return xydist; + } + + return R_PointToDist2(0, 0, xydist, mo->momz); +} + void K_AwardPlayerRings(player_t *player, UINT16 rings, boolean overload) { UINT16 superring; diff --git a/src/k_kart.h b/src/k_kart.h index 41ab9f933..b2d755056 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -71,15 +71,11 @@ extern vector3_t clusterpoint, clusterdtf; // Bubble Shield's maximum health #define MAXBUBBLEHEALTH 6 -// Chip damage done to a Bubble Shield upon bumping a player -#define BUBBLEBUMPCHIP 1 - // HP cost for a Bubble Shield to inflate #define BUBBLEUSECOST (MAXBUBBLEHEALTH / 3) -// Damage done to an inflated Bubble Shield by items. By default, this is the maximum health, -// so that the shield immediately breaks -#define BUBBLEITMDAMAGE (MAXBUBBLEHEALTH) +// Damage done to an inflated Bubble Shield by items. +#define BUBBLEITMDAMAGE (1) // Invincibility-related constants #define INVINDIST CV_Get(&cv_kartinvindist) @@ -211,6 +207,11 @@ UINT8 K_RaceLapCount(INT16 mapNum); void K_KartPlayerThink(player_t *player, ticcmd_t *cmd); void K_KartPlayerAfterThink(player_t *player); angle_t K_MomentumAngle(mobj_t *mo); + +fixed_t K_GetMomentum(mobj_t *mo, boolean twodee); +#define K_Momentum2D(mo) (K_GetMomentum(mo, true)) +#define K_Momentum3D(mo) (K_GetMomentum(mo, false)) + void K_AwardPlayerRings(player_t *player, UINT16 rings, boolean overload); void K_HandleRaceSplits(player_t *player, tic_t time, UINT8 checkpoint); void K_DoInstashield(player_t *player); diff --git a/src/p_inter.c b/src/p_inter.c index 0b2724dcc..a6d57c670 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2133,6 +2133,8 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da boolean force = false; boolean spbpop = false; boolean painsound = false; + boolean bubbleinvuln = false; + INT32 myShield = KSHIELD_NONE; if (objectplacing) return false; @@ -2231,6 +2233,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { const UINT8 type = (damagetype & DMG_TYPEMASK); const boolean explosioncombo = (type == DMG_EXPLODE); // This damage type can do evil stuff like ALWAYS combo + myShield = K_GetShieldFromPlayer(player); // Check if the player is allowed to be damaged! // If not, then spawn the instashield effect instead. @@ -2262,6 +2265,32 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da return false; } + if ((myShield == KSHIELD_BUBBLE) && (player->bubbleblowup)) + { + // This player is on the defensive; protect them from certain attacks. + switch (type) + { + case DMG_WIPEOUT: + bubbleinvuln = true; + break; + case DMG_VOLTAGE: + case DMG_NORMAL: + bubbleinvuln = true; + break; + default: + break; + } + + if (bubbleinvuln) + { + // This attack likely penetrated. Defense! + player->flashing = K_GetKartFlashing(player); + player->bubblecool = player->bubbleblowup*2; + K_DoInstashield(player); + return false; + } + } + // Check if we should allow explosion combos. if ((explosioncombo == false) && (player->flashing > 0 || player->squishedtimer > 0)) { @@ -2425,7 +2454,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da } // Have a shield? You get hit, but don't lose your rings! - if (K_GetShieldFromPlayer(player) != KSHIELD_NONE) + if (myShield != KSHIELD_NONE) { ringburst = 0; K_PopPlayerShield(player);