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
This commit is contained in:
Anonimus 2025-10-25 10:46:14 -04:00
parent 289db04dec
commit 4b448de85e
4 changed files with 108 additions and 37 deletions

View file

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

View file

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

View file

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

View file

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