Redapple the Bubble Shield, part 1/?

* The bubble remains inflated after holding the button, and slowly deflates
* Popping the shield now gives you flashtics, in addition to the speedboost
* Popping it also makes you invulnerable to proxmines!?
* Getting hit by an item while the shield is deflated protects you from
  JUST the spinout exactly ONCE, geez, think of the poor D5 players
* Players can be bumped by the inflated shield again, but it's very weak
* Bumping players with a deflated shield depletes half its HP, inflated
  bumps cost the same as other bumps
This commit is contained in:
GenericHeroGuy 2025-10-31 20:12:54 +01:00
parent ff3823afef
commit 1a3630c640
9 changed files with 128 additions and 206 deletions

View file

@ -255,7 +255,7 @@ INT32 stealtime = TICRATE/2;
INT32 sneakertime = TICRATE + (TICRATE/3);
INT32 waterpaneltime = TICRATE*2;
INT32 itemtime = 8*TICRATE;
INT32 bubbletime = TICRATE/4;
INT32 bubbletime = TICRATE/2;
INT32 comebacktime = 10*TICRATE;
INT32 bumptime = 6;
INT32 greasetics = 3*TICRATE;

View file

@ -922,7 +922,7 @@ static void K_BotItemBubble(botdata_t *bd, const player_t *player)
boolean hold = false;
// We are holding this thing too long, lets get rid of it.
if (bd->itemconfirm > 20*TICRATE)
if (bd->itemconfirm > 15*TICRATE)
{
bd->itemconfirm++;
@ -934,17 +934,13 @@ static void K_BotItemBubble(botdata_t *bd, const player_t *player)
return;
}
if (player->bubbleblowup <= 0)
if (player->bubblecool <= 0)
{
if (player->bubblecool <= 0)
{
fixed_t radius = 192 * player->mo->scale;
radius = Easing_Linear(FRACUNIT * player->botvars.difficulty / MAXBOTDIFFICULTY, 2*radius, radius);
hold = K_GetBlockedBubbleItem(player, radius);
}
fixed_t radius = 192 * player->mo->scale;
radius = Easing_Linear(FRACUNIT * player->botvars.difficulty / MAXBOTDIFFICULTY, 2*radius, radius);
hold = K_GetBlockedBubbleItem(player, radius);
}
else if (player->bubbleblowup < bubbletime)
else if (player->bubblecool < bubbletime)
{
hold = true;
}

View file

@ -547,12 +547,7 @@ static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble, INT16 d
// 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;
bubble->extravalue1 = 4;
bubble->cvmem = 1;
}
player->bubbleblowup = 0;
}
static boolean K_BubbleReflectingTrapItem(const mobj_t *t)
@ -561,6 +556,12 @@ static boolean K_BubbleReflectingTrapItem(const mobj_t *t)
t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE;
}
static boolean K_StrongPlayerBump(const player_t *player)
{
return (((K_GetKartInvinType() == KARTINVIN_LEGACY) && (player->invincibilitytimer))
|| (player->growshrinktimer > 0));
}
boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2)
{
if (t2->threshold && !t2->player)
@ -572,9 +573,16 @@ boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2)
fixed_t momentum = max(FixedHypot(owner->momx, owner->momy), FixedHypot(t2->momx, t2->momy));
momentum = max(3*momentum/4, 16*mapobjectscale); // do SOMETHING!
P_InstaThrust(t2, angle, momentum);
if (!t2->player)
t2->angle = angle;
if (t2->player && !K_StrongPlayerBump(t2->player))
{
P_Thrust(t2, angle, momentum/2);
}
else
{
P_InstaThrust(t2, angle, momentum);
if (!t2->player)
t2->angle = angle;
}
if (K_BubbleReflectingTrapItem(t2))
{
@ -598,19 +606,13 @@ boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2)
return true;
}
boolean K_BubbleShieldCanReflect(mobj_t *t1, mobj_t *t2)
boolean K_BubbleShieldCanReflect(mobj_t *t)
{
return (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ || t2->type == MT_JAWZ_DUD
|| t2->type == MT_BANANA || t2->type == MT_EGGMANITEM || t2->type == MT_BALLHOG
|| t2->type == MT_SSMINE || t2->type == MT_LANDMINE || t2->type == MT_SINK
|| t2->type == MT_KART_LEFTOVER
|| (t2->type == MT_PLAYER && t1->target != t2));
}
static boolean K_PlayerCanBeBubbleBumped(player_t *player)
{
return (((K_GetKartInvinType() == KARTINVIN_LEGACY) && (player->invincibilitytimer))
|| (player->growshrinktimer > 0));
return (t->type == MT_ORBINAUT || t->type == MT_JAWZ || t->type == MT_JAWZ_DUD
|| t->type == MT_BANANA || t->type == MT_EGGMANITEM || t->type == MT_BALLHOG
|| t->type == MT_SSMINE || t->type == MT_LANDMINE || t->type == MT_SINK
|| t->type == MT_KART_LEFTOVER
|| t->type == MT_PLAYER);
}
static INT16 K_GetBubbleDamage(mobj_t* itm)
@ -665,19 +667,11 @@ static INT16 K_GetBubbleDamage(mobj_t* itm)
boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2)
{
if (t2->type == MT_PLAYER)
{
// don't bump yourself
if (t1->target == t2)
return true;
boolean bumpme = ((t2->player) && (K_PlayerCanBeBubbleBumped(t2->player)));
if (!bumpme)
{
// Ignore non-bumpable players.
return true;
}
}
if (K_BubbleShieldCanReflect(t1, t2))
if (K_BubbleShieldCanReflect(t2))
{
boolean reflected = K_BubbleShieldReflect(t1, t2);

View file

@ -18,7 +18,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2);
void K_ThunderShieldAttack(mobj_t *actor, fixed_t size);
boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2);
boolean K_BubbleShieldCanReflect(mobj_t *t1, mobj_t *t2);
boolean K_BubbleShieldCanReflect(mobj_t *t);
boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2);
boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2);

View file

@ -1448,7 +1448,7 @@ static void K_drawKartItem(void)
localpatch = kp_bubbleshield[offset];
dark = true;
if ((stplyr->bubbleblowup > bubbletime) && (leveltime & 1))
if (stplyr->bubbleblowup > 0 && leveltime & 1)
{
colormode = TC_BLINK;
localcolor = SKINCOLOR_WHITE;
@ -1588,7 +1588,7 @@ static void K_drawKartItem(void)
//V_ClearClipRect();
// Extensible meter, currently used by Invincibilty, Grow, Rocket Sneakers and Flame Shield
if (itembar)
if (itembar || K_GetShieldFromPlayer(stplyr) == KSHIELD_BUBBLE)
{
const INT32 fill = ((itembar*barlength)/maxl);
const INT32 length = min(barlength, fill);

View file

@ -485,7 +485,7 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against)
if (against && !P_MobjWasRemoved(against) && against->player
&& ((!P_PlayerInPain(against->player) && P_PlayerInPain(mobj->player)) // You're hurt
|| (against->player->itemtype == KITEM_BUBBLESHIELD && mobj->player->itemtype != KITEM_BUBBLESHIELD))) // They have a Bubble Shield
|| (K_GetShieldFromPlayer(against->player) == KSHIELD_BUBBLE && K_GetShieldFromPlayer(mobj->player) != KSHIELD_BUBBLE))) // They have a Bubble Shield
{
weight = 0; // This player does not cause any bump action
}
@ -497,7 +497,7 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against)
weight = (mobj->player->kartweight) * FRACUNIT;
if (mobj->player->itemtype == KITEM_BUBBLESHIELD)
if (K_GetShieldFromPlayer(mobj->player) == KSHIELD_BUBBLE)
{
weight = max(BUBBLEMINWEIGHT, weight);
weight = FixedMul(weight, FRACUNIT/16);
@ -7461,12 +7461,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
}
}
if (player->itemtype == KITEM_BUBBLESHIELD)
{
if (player->bubblecool)
player->bubblecool--;
}
else
if (player->itemtype != KITEM_BUBBLESHIELD)
{
player->bubbleblowup = 0;
player->bubblecool = 0;
@ -10218,8 +10213,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->itemflags &= ~IF_USERINGS;
}
if (player->exiting && player->bubbleblowup > 0)
player->bubbleblowup--;
if (player->exiting)
{
player->bubbleblowup = 0;
if (player->bubblecool > 0)
player->bubblecool--;
}
if (player && player->mo && player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && player->respawn == 0 && !(player->exiting || mapreset))
{
@ -10785,16 +10784,16 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
if ((buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY))
{
if (player->bubbleblowup == 0)
if (player->bubblecool == 0)
S_StartSound(player->mo, sfx_s3k75);
player->bubbleblowup++;
if (player->bubblecool < bubbletime && player->bubblehealth > 0)
{
player->bubbleblowup += 3;
player->bubblehealth--;
}
// Reduce cooldown for easier itemset chaining.
// (was blowup x4 before)
player->bubblecool = player->bubbleblowup*2;
if (player->bubbleblowup > bubbletime*4)
if (++player->bubblecool >= bubbletime + TICRATE/2)
{
// If you overcharge the Bubble Shield at
// any point, it pops and gives you a boost.
@ -10811,43 +10810,41 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
player->bubbleboost = 7 * sneakertime / 10;
player->flashing = 4*TICRATE/7;
}
}
else
{
if (player->bubblecool > bubbletime)
player->bubblecool = bubbletime;
if (player->bubbleblowup > 0)
{
boolean popped = false;
if (player->bubbleblowup > bubbletime)
player->bubbleblowup = bubbletime;
player->bubbleblowup--;
if (player->bubbleblowup == 0)
K_BotResetItemConfirm(player, false);
if (player->bubblecool < bubbletime)
player->bubblecool++;
}
if (player->bubbleblowup)
if (player->bubbleblowup == 0 && player->bubblecool > 0)
{
player->bubblecool--;
if (player->bubblecool == 0 && player->bubblehealth <= 0)
{
player->bubbleblowup--;
if (!player->bubbleblowup)
{
// Each use costs some points of health.
K_RemoveBubbleHealth(player, BUBBLEUSECOST);
if (player->bubblehealth <= 0)
{
// Bubble popped!
popped = true;
}
}
K_BreakBubbleShield(player);
K_PopPlayerShield(player);
}
}
if (!popped)
{
if (buttons & BT_ATTACK || player->bubblecool)
{
player->itemflags &= ~IF_HOLDREADY;
}
else
{
player->itemflags |= IF_HOLDREADY;
}
}
if (buttons & BT_ATTACK || player->bubblecool > 0)
{
player->itemflags &= ~IF_HOLDREADY;
}
else
{
player->itemflags |= IF_HOLDREADY;
}
}
}

View file

@ -68,17 +68,14 @@ extern boolean clusterplayer[MAXPLAYERS];
extern UINT32 clusterid; // ID of the "cluster player", the one closest to the cluster point.
extern vector3_t clusterpoint, clusterdtf;
// Bubble Shield's maximum health
#define MAXBUBBLEHEALTH 12
// Bubble Shield's maximum health (determines how many times it can be inflated)
#define MAXBUBBLEHEALTH (bubbletime * 3)
// Chip damage done to a Bubble Shield upon bumping a player
#define BUBBLEBUMPCHIP (MAXBUBBLEHEALTH / 4)
// Chip damage done to a Bubble Shield upon bumping a player when not inflated (rounded up)
#define BUBBLEBUMPCHIP ((MAXBUBBLEHEALTH + 1) / 2)
// HP cost for a Bubble Shield to inflate
#define BUBBLEUSECOST (MAXBUBBLEHEALTH / 3)
// Damage done to an inflated Bubble Shield by items.
#define BUBBLEITMDAMAGE (MAXBUBBLEHEALTH / 6)
// Damage done to an inflated Bubble Shield (rounded up)
#define BUBBLEITMDAMAGE ((MAXBUBBLEHEALTH + 5) / 6)
// Bump weight for a Bubble Shield
#define BUBBLEMINWEIGHT (5 * FRACUNIT)

View file

@ -2133,8 +2133,6 @@ 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;
@ -2232,8 +2230,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
else
{
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);
const boolean explosioncombo = type == DMG_EXPLODE // This damage type can do evil stuff like ALWAYS combo
&& player->bubbleboost == 0; // ...but popping a Bubble Shield protects you!
// Check if the player is allowed to be damaged!
// If not, then spawn the instashield effect instead.
@ -2265,32 +2264,6 @@ 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))
{
@ -2298,6 +2271,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
K_DoInstashield(player);
return false;
}
// Bubble Shield protects you from spinout, but not knockback
if (source != NULL && K_GetShieldFromPlayer(player) == KSHIELD_BUBBLE)
{
K_PopPlayerShield(player);
K_DoInstashield(player);
return false;
}
}
// We successfully damaged them! Give 'em some bumpers!
@ -2454,7 +2435,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 (myShield != KSHIELD_NONE)
if (K_GetShieldFromPlayer(player) != KSHIELD_NONE)
{
ringburst = 0;
K_PopPlayerShield(player);

View file

@ -9178,77 +9178,60 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
{
fixed_t scale;
statenum_t curstate, overlaystate;
const player_t *player = P_MobjWasRemoved(mobj->target) ? NULL : mobj->target->player;
if (!mobj->target || !mobj->target->health || !mobj->target->player
|| K_GetShieldFromPlayer(mobj->target->player) != KSHIELD_BUBBLE)
if (player == NULL || player->mo->health == 0 || K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE)
{
P_RemoveMobj(mobj);
return false;
}
scale = (5*mobj->target->scale)>>2;
curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states)));
scale = 5*player->mo->scale/4;
curstate = mobj->tics == 1 ? mobj->state->nextstate : (statenum_t)(mobj->state - states);
mobj->color = SKINCOLOR_BLUE;
mobj->colorized = false;
// Lookup for rescaling the "bubble damage" overlay
UINT8 frame, scale_idx;
UINT8 scale_idx = player->bubblecool > bubbletime ? player->bubblecool % 4 : 0;
if (player->bubbleblowup > 0 && player->bubblecool < 8)
scale_idx = player->bubblecool/2;
frame = scale_idx = 0;
if (mobj->target->player->bubbleblowup)
if (player->bubblecool)
{
INT32 blow = mobj->target->player->bubbleblowup;
if (blow > bubbletime)
blow = bubbletime;
INT32 blow = player->bubbleblowup + min(player->bubblecool, bubbletime)*10;
scale += (blow * scale) / (bubbletime * 10);
if (curstate != S_BUBBLESHIELDBLOWUP)
P_SetMobjState(mobj, S_BUBBLESHIELDBLOWUP);
mobj->angle += ANGLE_22h;
mobj->renderflags &= ~RF_GHOSTLYMASK;
scale += (blow * ((3*scale)>>1)) / bubbletime;
if (player->bubbleblowup == 0 && player->bubblecool > 0)
mobj->renderflags |= RF_GHOSTLY;
else
mobj->renderflags &= ~RF_GHOSTLYMASK;
mobj->frame = CLAMP(states[S_BUBBLESHIELDBLOWUP].frame + mobj->extravalue1, states[S_BUBBLESHIELDBLOWUP].frame, states[S_BUBBLESHIELDBLOWUP].frame + 3);
mobj->frame = states[S_BUBBLESHIELDBLOWUP].frame + scale_idx;
frame = mobj->frame & FF_FRAMEMASK;
scale_idx = CLAMP(frame - 9, 0, 3);
if ((mobj->target->player->bubbleblowup > bubbletime) && (leveltime & 1))
if (player->bubbleblowup > 0 && leveltime & 1)
{
mobj->color = SKINCOLOR_WHITE;
mobj->colorized = true;
}
else
{
mobj->color = SKINCOLOR_BLUE;
mobj->colorized = false;
}
if (mobj->extravalue1 < 4 && mobj->extravalue2 < blow && !mobj->cvmem && (leveltime & 1)) // Growing
{
mobj->extravalue1++;
if (mobj->extravalue1 >= 4)
mobj->cvmem = 1; // shrink back down
}
else if ((mobj->extravalue1 > -4 && mobj->extravalue2 > blow)
|| (mobj->cvmem && mobj->extravalue1 > 0)) // Shrinking
mobj->extravalue1--;
if (P_IsObjectOnGround(mobj->target))
if (P_IsObjectOnGround(player->mo) && player->bubbleblowup > 0)
{
UINT8 i;
for (i = 0; i < 2; i++)
{
angle_t a = mobj->angle + ((i & 1) ? ANGLE_180 : 0);
fixed_t ws = (mobj->target->scale>>1);
fixed_t ws = player->mo->scale/2 + scale/5;
mobj_t *wave;
ws += (blow * ws) / bubbletime;
wave = P_SpawnMobj(
(mobj->target->x - mobj->target->momx) + P_ReturnThrustX(NULL, a, mobj->radius - (21*ws)),
(mobj->target->y - mobj->target->momy) + P_ReturnThrustY(NULL, a, mobj->radius - (21*ws)),
(mobj->target->z - mobj->target->momz), MT_THOK);
(player->mo->x - player->mo->momx) + P_ReturnThrustX(NULL, a, mobj->radius - (21*ws)),
(player->mo->y - player->mo->momy) + P_ReturnThrustY(NULL, a, mobj->radius - (21*ws)),
(player->mo->z - player->mo->momz), MT_THOK);
wave->colorized = true;
wave->color = SKINCOLOR_BLUE;
@ -9257,47 +9240,21 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
P_SetMobjState(wave, S_SPLISH1);
wave->momx = mobj->target->momx;
wave->momy = mobj->target->momy;
wave->momz = mobj->target->momz;
wave->momx = player->mo->momx;
wave->momy = player->mo->momy;
wave->momz = player->mo->momz;
}
}
}
else
{
mobj->cvmem = 0;
mobj->angle = mobj->target->angle;
mobj->angle = player->mo->angle;
mobj->renderflags &= ~RF_GHOSTLYMASK;
if (curstate == S_BUBBLESHIELDBLOWUP)
{
if (mobj->extravalue1 != 0)
{
mobj->frame = (states[S_BUBBLESHIELDBLOWUP].frame + mobj->extravalue1);
frame = mobj->frame & FF_FRAMEMASK;
scale_idx = CLAMP(frame - 9, 0, 3);
if (mobj->extravalue1 < 0 && (leveltime & 1))
mobj->extravalue1++;
else if (mobj->extravalue1 > 0)
mobj->extravalue1--;
}
else
{
P_SetMobjState(mobj, S_BUBBLESHIELD1);
mobj->extravalue1 = 0;
}
}
else
{
if (mobj->target->player->bubblecool && ((curstate-S_BUBBLESHIELD1) & 1))
mobj->renderflags |= RF_GHOSTLY;
else
mobj->renderflags &= ~RF_GHOSTLYMASK;
}
P_SetMobjState(mobj, S_BUBBLESHIELD1);
}
mobj->extravalue2 = mobj->target->player->bubbleblowup;
P_SetScale(mobj, (mobj->destscale = scale));
if (mobj->tracer && (!P_MobjWasRemoved(mobj->tracer)))
@ -9313,7 +9270,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
mobj->tracer->spritexscale = FixedMul(mobj->spritexscale, bubbleoverlayscales[scale_idx][0]);
mobj->tracer->spriteyscale = FixedMul(mobj->spriteyscale, bubbleoverlayscales[scale_idx][1]);
health_scalar = mobj->target->player->bubblehealth * FRACUNIT / MAXBUBBLEHEALTH;
health_scalar = player->bubblehealth * FRACUNIT / MAXBUBBLEHEALTH;
health_scalar = 5 - CLAMP(health_scalar / (FRACUNIT / 5), 1, 5);
overlaystate = ((mobj->tracer->tics == 1) ? (mobj->tracer->state->nextstate) : ((statenum_t)(mobj->tracer->state-states)));
@ -9346,7 +9303,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
mobj->sloperoll = 0;
mobj->flags &= ~(MF_NOCLIP|MF_NOCLIPTHING);
P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
P_MoveOrigin(mobj, player->mo->x, player->mo->y, player->mo->z);
mobj->flags |= MF_NOCLIP|MF_NOCLIPTHING;
break;
}