Bubble Shield adjustments

- Players with inflated Bubble Shields now ignore collisions with anything the shield itself is supposed to reflect
- If a player happens to still collide with a reflectable in this state, they noclip through and a ``bubblegraze`` value increments
  - ``bubblegraze``, like ``justbumped``, prevents K_KartBouncing collisions for some time
- Falling rocks can now be reflected by Bubble Shields
- 1.5 seconds of flashtics applied to players whose (inflated) Bubble Shields are shattered
This commit is contained in:
yamamama 2026-03-15 15:10:22 -04:00
parent 9a39b24d28
commit b46a51607f
7 changed files with 175 additions and 5 deletions

View file

@ -465,6 +465,17 @@ typedef enum
// Delay-drift: Time the game is given to predict a "wanted" drift
#define DRIFT_DELAY_TIME (7)
#define BUBBLEGRAZE_INCREMENT (2)
#if ((BUBBLEGRAZE_INCREMENT / 2) < 1)
#define BUBBLEGRAZE_SOFTINCREMENT (1)
#else
#define BUBBLEGRAZE_SOFTINCREMENT (BUBBLEGRAZE_INCREMENT / 2)
#endif
#define BUBBLEGRAZE_DECREMENT (1)
#define BUBBLEGRAZE_MAX (0x3FFFFFFF)
// enum for saved lap times
typedef enum
{
@ -702,6 +713,11 @@ struct player_t
UINT8 bubbleblowup; // Bubble Shield usage blowup
UINT8 bubblehealth; // Bubble Shield health
UINT16 bubbleboost; // Bubble shield boost timer
// Specific variant of justbumped where skimming a player with an inflated
// Bubble Shield increments this.
UINT32 bubblegraze;
tic_t bubblebuffer; // Prevents Bubble Shield from taking damage
UINT16 flamedash; // Flame Shield dash power

View file

@ -713,6 +713,14 @@ static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble, INT16 d
player->bubbleblowup /= 2;
}
boolean K_BubbleShieldReflectSpecialCase(mobj_t *t)
{
if ((!t) || (P_MobjWasRemoved(t)))
return false; // Object doesn't even exist
return t->type == MT_FALLINGROCK; // TODO: Maybe an object flag (MF_BUBBLEREFLECT)?
}
static boolean K_BubbleReflectingTrapItem(const mobj_t *t)
{
// slow orbis are traps
@ -721,7 +729,7 @@ static boolean K_BubbleReflectingTrapItem(const mobj_t *t)
return t->type == MT_BANANA || (t->type == MT_ORBINAUT && t->flags2 & MF2_AMBUSH) || t->type == MT_JAWZ_DUD ||
t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE ||
t->type == MT_EGGMINE;
t->type == MT_EGGMINE || K_BubbleShieldReflectSpecialCase(t);
}
static boolean K_StrongPlayerBump(const player_t *player)
@ -782,7 +790,7 @@ boolean K_BubbleShieldCanReflect(mobj_t *t)
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_EGGMINE
|| t->type == MT_KART_LEFTOVER
|| t->type == MT_KART_LEFTOVER || K_BubbleShieldReflectSpecialCase(t)
|| t->type == MT_PLAYER);
}

View file

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

View file

@ -846,6 +846,23 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol
return false;
}
// Don't bump if you've bubble-grazed
if (mobj1->player && mobj1->player->bubblegraze)
{
mobj1->player->bubblegraze = ApproachOneWayUINT32(
mobj1->player->bubblegraze, BUBBLEGRAZE_MAX, BUBBLEGRAZE_SOFTINCREMENT);
mobj1->player->justbumped = bumptime;
return false;
}
if (mobj2->player && mobj2->player->bubblegraze)
{
mobj2->player->bubblegraze = ApproachOneWayUINT32(
mobj2->player->bubblegraze, BUBBLEGRAZE_MAX, BUBBLEGRAZE_SOFTINCREMENT);
mobj2->player->justbumped = bumptime;
return false;
}
// Don't bump if you're flipping over
if (mobj1->player && mobj1->player->flipovertimer)
{
@ -5478,6 +5495,12 @@ void K_RemoveBubbleHealth(player_t *player, INT16 sub)
if (player->bubblehealth <= 0)
{
if (player->bubbleblowup)
{
// 1.5 seconds of i-frames for better gamefeel on shatters
player->flashing = (UINT32)((float)(TICRATE) * 1.5f);
}
K_PopPlayerShield(player);
}
else
@ -7673,6 +7696,11 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->justbumped > 0)
player->justbumped--;
if (player->bubblegraze > 0)
{
player->bubblegraze = ApproachOneWayUINT32(player->bubblegraze, 0, BUBBLEGRAZE_DECREMENT);
}
if (player->outruntime > 0)
player->outruntime--;

View file

@ -399,6 +399,7 @@ static int lib_lenLocalplayers(lua_State *L)
X(bubbleblowup) \
X(bubblehealth) \
X(bubbleboost) \
X(bubblegraze) \
X(flamedash) \
X(flametimer) \
X(flamestore) \
@ -861,6 +862,9 @@ static int player_get(lua_State *L)
case player_bubbleboost:
lua_pushinteger(L, plr->bubbleboost);
break;
case player_bubblegraze:
lua_pushinteger(L, plr->bubblegraze);
break;
case player_flamedash:
lua_pushinteger(L, plr->flamedash);
break;
@ -1656,6 +1660,9 @@ static int player_set(lua_State *L)
case player_bubbleboost:
plr->bubbleboost = luaL_checkinteger(L, 3);
break;
case player_bubblegraze:
plr->bubblegraze = luaL_checkinteger(L, 3);
break;
case player_flamedash:
plr->flamedash = luaL_checkinteger(L, 3);
break;

View file

@ -548,6 +548,11 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
{
fixed_t blockdist;
boolean shrinkleeway = false;
boolean bubbleleeway = false, movingbubble = false, movedbubble = false;
// Bubble Shield special case: unconditionally process collision for this object so the
// Bubble Shield can reflect it.
boolean bubblewantsreflect = false;
if (g_tm.thing == NULL || P_MobjWasRemoved(g_tm.thing) == true)
return BMIT_STOP; // func just popped our g_tm.thing, cannot continue.
@ -565,8 +570,54 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|| (thing->player && thing->player->spectator))
return BMIT_CONTINUE;
if ((thing->flags & MF_NOCLIPTHING) || !(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE/*|MF_SPRING*/))) // we still use MF_SOLID for springs
return BMIT_CONTINUE;
// Check for players with inflated Bubble Shields
if (g_tm.thing->player && K_IsBubbleDefending(g_tm.thing->player))
movingbubble = true;
if (thing->player && K_IsBubbleDefending(thing->player))
movedbubble = true;
if (movingbubble || movedbubble)
bubbleleeway = true;
/*
* "Why does this exist?" Well...
* Falling rocks don't have the needed flags to process collisions further, when the Bubble
* Shield is the one checking for collisions (and thus things to reflect). At the same time,
* when falling rocks are checking for collision, the Bubble Shield has its noclip flags
* enabled, meaning it's ALSO never checked.
* So, how do we solve the problem? This shitty special-case bandaid.
*/
boolean _playerinflate = false;
if (g_tm.thing->type == MT_BUBBLESHIELD)
{
_playerinflate =
(g_tm.thing->target && (!P_MobjWasRemoved(g_tm.thing->target)) &&
(g_tm.thing->target->player) && (g_tm.thing->target->player->bubbleblowup));
bubblewantsreflect =
((_playerinflate) && (!(g_tm.thing->flags & (MF_NOCLIP | MF_NOCLIPTHING))) &&
(K_BubbleShieldReflectSpecialCase(thing)));
}
else if (thing->type == MT_BUBBLESHIELD)
{
_playerinflate =
(g_tm.thing->target && (!P_MobjWasRemoved(g_tm.thing->target)) &&
(g_tm.thing->target->player) && (g_tm.thing->target->player->bubbleblowup));
bubblewantsreflect =
((_playerinflate) && (!(thing->flags & (MF_NOCLIP | MF_NOCLIPTHING))) &&
(K_BubbleShieldReflectSpecialCase(g_tm.thing)));
}
if ((thing->flags & MF_NOCLIPTHING) ||
!((thing->flags & (MF_SOLID | MF_SPECIAL | MF_PAIN | MF_SHOOTABLE /*|MF_SPRING*/)) ||
bubblewantsreflect)) // we still use MF_SOLID for springs
{
return BMIT_CONTINUE;
}
// Get rid of the "just flipped" flag.
// We only use that to confirm flipover hits.
@ -745,6 +796,38 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
if (thing->type == MT_BUBBLESHIELD || g_tm.thing->type == MT_BUBBLESHIELD)
return BMIT_CONTINUE;
// Triple-check: anything the Bubble Shield reflects should phase through players inflating their shield.
if (bubbleleeway)
{
// see if it went over / under
if (g_tm.thing->z > thing->z + thing->height)
return BMIT_CONTINUE; // overhead
if (g_tm.thing->z + g_tm.thing->height < thing->z)
return BMIT_CONTINUE; // underneath
if (movingbubble && K_BubbleShieldCanReflect(thing))
{
g_tm.thing->player->bubblegraze =
ApproachOneWayUINT32(g_tm.thing->player->bubblegraze,
BUBBLEGRAZE_MAX,
BUBBLEGRAZE_INCREMENT);
return BMIT_CONTINUE;
}
if (movedbubble && K_BubbleShieldCanReflect(g_tm.thing))
{
thing->player->bubblegraze =
ApproachOneWayUINT32(thing->player->bubblegraze,
BUBBLEGRAZE_MAX,
BUBBLEGRAZE_INCREMENT);
return BMIT_CONTINUE;
}
// Anything else falls through as a possible false positive.
}
if (thing->flags & MF_PAIN)
{ // Player touches painful thing sitting on the floor
// see if it went over / under
@ -1330,10 +1413,33 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return BMIT_CONTINUE;
}
// If we're inflating, don't do PvP bumps.
// I do not doubt the previous bubbleleeway check'll fall through, so, yeah.
if (bubbleleeway)
{
if (movingbubble)
{
g_tm.thing->player->bubblegraze =
ApproachOneWayUINT32(g_tm.thing->player->bubblegraze,
BUBBLEGRAZE_MAX,
BUBBLEGRAZE_INCREMENT);
}
if (movedbubble)
{
thing->player->bubblegraze =
ApproachOneWayUINT32(thing->player->bubblegraze,
BUBBLEGRAZE_MAX,
BUBBLEGRAZE_INCREMENT);
}
return BMIT_CONTINUE;
}
if (thing->player->squishedtimer || thing->player->hyudorotimer
|| thing->player->justbumped || thing->scale > g_tm.thing->scale + (mapobjectscale/8)
|| g_tm.thing->player->squishedtimer || g_tm.thing->player->hyudorotimer
|| g_tm.thing->player->justbumped || g_tm.thing->scale > thing->scale + (mapobjectscale/8))
|| g_tm.thing->player->justbumped || g_tm.thing->scale > thing->scale + (mapobjectscale/8)
|| thing->player->bubblegraze || g_tm.thing->player->bubblegraze)
{
return BMIT_CONTINUE;
}
@ -1453,6 +1559,9 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
}
else if (thing->type == MT_KART_LEFTOVER)
{
if (bubbleleeway)
return BMIT_CONTINUE; // If we're inflating, don't do PvP bumps.
// see if it went over / under
if (g_tm.thing->z > thing->z + thing->height)
return BMIT_CONTINUE; // overhead

View file

@ -725,6 +725,7 @@ static void P_NetSyncPlayers(savebuffer_t *save)
SYNC(players[i].bubbleblowup);
SYNC(players[i].bubblehealth);
SYNC(players[i].bubbleboost);
SYNC(players[i].bubblegraze);
SYNC(players[i].bubblebuffer);
SYNC(players[i].flamedash);