Further tweaks to Bubble Shield

* Guest star: fixed-point item bar
* Bubble scaling and shards have been refined
* No more passive protection against explosives and thunder
* The bubble boost timer now protects you from mine explosion particles, not
  just flashtics, making it easier to pass through proxmines
This commit is contained in:
GenericHeroGuy 2025-11-01 01:27:41 +01:00
parent 1a3630c640
commit b9022d9640
5 changed files with 84 additions and 143 deletions

View file

@ -343,7 +343,8 @@ boolean K_MineExplosionCollide(mobj_t *t1, mobj_t *t2)
{
if (t2->player)
{
if (t2->player->flashing > 0)
// don't sideswipe anyone boosting with a bubble shield!
if (t2->player->flashing > 0 || t2->player->bubbleboost > 0)
return true;
if (t1->state == &states[S_MINEEXPLOSION1])

View file

@ -1306,6 +1306,22 @@ void K_getMinimapDrawinfo(drawinfo_t *out)
out->flags = fflags;
}
static void K_DrawItemBar(INT32 fx, INT32 fy, INT32 fflags, fixed_t itembar, UINT8 colors[static 3])
{
const boolean fourp = r_splitscreen > 1;
const INT32 barlength = (fourp ? 12 : 26)*FRACUNIT;
const INT32 length = min(barlength, FixedMul(itembar, barlength));
const INT32 height = (fourp ? 1 : 2)*FRACUNIT;
const INT32 x = (fx + (fourp ? 17 : 11)) * FRACUNIT, y = (fy + (fourp ? 27 : 35)) * FRACUNIT;
V_DrawSciencePatch(x, y, V_HUDTRANS|fflags, kp_itemtimer[fourp ? 1 : 0], FRACUNIT);
V_DrawFixedFill(x+FRACUNIT, y+FRACUNIT, min(FRACUNIT, length), height, colors[2]|fflags); // the left edge
V_DrawFixedFill(x+max(FRACUNIT, length), y+FRACUNIT, min(FRACUNIT, length), height, colors[2]|fflags); // the right edge
if (!fourp)
V_DrawFixedFill(x+2*FRACUNIT, y+2*FRACUNIT, max(0, length - 2*FRACUNIT), FRACUNIT, colors[1]|fflags); // the dulled underside
V_DrawFixedFill(x+2*FRACUNIT, y+FRACUNIT, max(0, length - 2*FRACUNIT), FRACUNIT, colors[0]|fflags); // the shine
}
// see also MT_PLAYERARROW mobjthinker in p_mobj.c
static void K_drawKartItem(void)
{
@ -1321,11 +1337,7 @@ static void K_drawKartItem(void)
boolean dark = false;
INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags...
INT32 numberdisplaymin = 2;
INT32 itembar = 0;
INT32 maxl = 0; // itembar's normal highest value
INT32 flamebar = 0;
INT32 flamemaxl = 0; // flamebar's normal highest value
const INT32 barlength = (r_splitscreen > 1 ? 12 : 26);
fixed_t itembar = -1, flamebar = -1;
UINT16 localcolor = SKINCOLOR_NONE;
SINT8 colormode = TC_RAINBOW;
UINT8 *colmap = NULL;
@ -1386,8 +1398,7 @@ static void K_drawKartItem(void)
}
else if (stplyr->rocketsneakertimer > 1)
{
itembar = stplyr->rocketsneakertimer;
maxl = (itemtime*3) - barlength;
itembar = FixedDiv(stplyr->rocketsneakertimer, itemtime*3);
if (leveltime & 1)
localpatch = kp_rocketsneaker[offset];
@ -1396,10 +1407,8 @@ static void K_drawKartItem(void)
}
else if (stplyr->flametimer > 1)
{
itembar = stplyr->flametimer;
maxl = (itemtime*3) - barlength;
flamebar = stplyr->flamestore;
flamemaxl = FLAMESTOREMAX;
itembar = FixedDiv(stplyr->flametimer, itemtime*3);
flamebar = FixedDiv(stplyr->flamestore, FLAMESTOREMAX);
localbg = kp_itembg[offset+1];
dark = true;
@ -1417,10 +1426,7 @@ static void K_drawKartItem(void)
else if (stplyr->growshrinktimer > 0)
{
if (stplyr->growcancel > 0)
{
itembar = stplyr->growcancel;
maxl = 26;
}
itembar = FixedDiv(stplyr->growcancel, 26);
if (leveltime & 1)
localpatch = kp_grow[offset];
@ -1429,14 +1435,10 @@ static void K_drawKartItem(void)
}
else if ((stplyr->invincibilitytimer) && (K_GetKartInvinType() == KARTINVIN_ALTERN))
{
itembar = stplyr->invincibilitytimer;
maxl = max(1, stplyr->maxinvincibilitytime);
itembar = FixedDiv(stplyr->invincibilitytimer, max(1, stplyr->maxinvincibilitytime));
if (stplyr->invincibilitycancel > 0)
{
flamebar = stplyr->invincibilitycancel;
flamemaxl = 26;
}
flamebar = FixedDiv(stplyr->invincibilitycancel, 26);
if (leveltime & 1)
localpatch = localinv;
@ -1454,8 +1456,7 @@ static void K_drawKartItem(void)
localcolor = SKINCOLOR_WHITE;
}
itembar = stplyr->bubblehealth;
maxl = MAXBUBBLEHEALTH;
itembar = FixedDiv(stplyr->bubblehealth, MAXBUBBLEHEALTH);
}
else if (stplyr->sadtimer > 0)
{
@ -1588,45 +1589,12 @@ static void K_drawKartItem(void)
//V_ClearClipRect();
// Extensible meter, currently used by Invincibilty, Grow, Rocket Sneakers and Flame Shield
if (itembar || K_GetShieldFromPlayer(stplyr) == KSHIELD_BUBBLE)
{
const INT32 fill = ((itembar*barlength)/maxl);
const INT32 length = min(barlength, fill);
const INT32 height = (offset ? 1 : 2);
const INT32 x = (offset ? 17 : 11), y = (offset ? 27 : 35);
// ...aren't you forgetting something?
if (itembar != -1)
K_DrawItemBar(fx, fy, fflags|V_HUDTRANS, itembar, (UINT8 []){0, 8, 12});
V_DrawScaledPatch(fx+x, fy+y, V_HUDTRANS|fflags, kp_itemtimer[offset]);
// The left dark "AA" edge
V_DrawFill(fx+x+1, fy+y+1, (length == 2 ? 2 : 1), height, 12|fflags|V_HUDTRANS);
// The bar itself
if (length > 2)
{
V_DrawFill(fx+x+length, fy+y+1, 1, height, 12|fflags|V_HUDTRANS); // the right one
if (height == 2)
V_DrawFill(fx+x+2, fy+y+2, length-2, 1, 8|fflags|V_HUDTRANS); // the dulled underside
V_DrawFill(fx+x+2, fy+y+1, length-2, 1, 0|fflags|V_HUDTRANS); // the shine
}
}
if (flamebar)
{
const INT32 fill = ((flamebar*barlength)/flamemaxl);
const INT32 length = min(barlength, fill);
const INT32 height = (offset ? 1 : 2);
const INT32 x = (offset ? 17 : 11), y = (offset ? 27 : 35);
V_DrawScaledPatch(fx+x, fy+y-8, V_HUDTRANS|fflags, kp_itemtimer[offset]);
// The left dark "AA" edge
V_DrawFill(fx+x+1, fy+y+1-8, (length == 2 ? 2 : 1), height, 55|fflags|V_HUDTRANS);
// The bar itself
if (length > 2)
{
V_DrawFill(fx+x+length, fy+y+1-8, 1, height, 55|fflags|V_HUDTRANS); // the right one
if (height == 2)
V_DrawFill(fx+x+2, fy+y+2-8, length-2, 1, 36|fflags|V_HUDTRANS); // the dulled underside
V_DrawFill(fx+x+2, fy+y+1-8, length-2, 1, 51|fflags|V_HUDTRANS); // the shine
}
}
if (flamebar != -1)
K_DrawItemBar(fx, fy - 8, fflags|V_HUDTRANS, flamebar, (UINT8 []){51, 36, 55});
// Quick Eggman numbers
if (stplyr->eggmanexplode > 1)

View file

@ -5306,59 +5306,49 @@ static void K_BreakBubbleShield(player_t* player)
if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE)
return;
// TODO: Some more dramatic SFX and VFX (DSS3K59, bubble shield shards fly all over)
mobj_t* shard;
const INT32 flip = P_MobjFlip(player->mo);
const fixed_t scalediff = player->shieldtracer->scale - mapobjectscale;
const fixed_t shieldrad = player->shieldtracer->radius;
fixed_t move_magnitude, shieldrad, shieldscale, scalediff;
vector2_t mul_vec;
vector3_t mom;
fixed_t randang, randzang;
INT32 flip = P_MobjFlip(player->mo);
shieldscale = player->shieldtracer->scale;
scalediff = shieldscale - mapobjectscale;
shieldrad = FixedMul(player->shieldtracer->radius, shieldscale);
mul_vec.x = FRACUNIT;
mul_vec.y = 0;
INT32 i;
for (i = 0; i < MAXSHARDCOUNT; i++)
for (INT32 i = 0; i < MAXSHARDCOUNT; i++)
{
mul_vec.x = FRACUNIT;
mul_vec.y = 0;
vector2_t mul_vec = { FRACUNIT, 0 };
randang = (SHARDROT * (i + 1));
randzang = (P_RandomRange(23, 157) * FRACUNIT);
move_magnitude =
fixed_t randang = SHARDROT * (i + 1);
fixed_t randzang = P_RandomRange(10, 120) * FRACUNIT;
fixed_t move_magnitude =
FixedMul((P_RandomRange(4, 16) * FRACUNIT), mapobjectscale + (scalediff / 4));
FV2_Rotate(&mul_vec, randzang);
// Do shitty initial 3D rotations around the shield's radius.
shard = P_SpawnMobj(
mobj_t *shard = P_SpawnMobj(
player->shieldtracer->x +
FixedMul(FixedMul(mul_vec.x, shieldrad), FCOS(FixedAngle(randang))),
FixedMul(FixedMul(mul_vec.x, shieldrad), FCOS(FixedAngle(randang))),
player->shieldtracer->y +
FixedMul(FixedMul(mul_vec.x, shieldrad), FSIN(FixedAngle(randang))),
player->shieldtracer->z + FixedMul(mul_vec.y * flip, shieldrad),
FixedMul(FixedMul(mul_vec.x, shieldrad), FSIN(FixedAngle(randang))),
player->shieldtracer->z + ((player->shieldtracer->height + scalediff) / 4)
+ FixedMul(mul_vec.y * flip, shieldrad),
MT_BUBBLESHLD_DEBRIS);
if (shard)
if (!P_MobjWasRemoved(shard))
{
//CONS_Printf(M_GetText("randzang: %d, randang: %d\n"), randzang / FRACUNIT, randang / FRACUNIT);
mul_vec.x = FixedMul(move_magnitude, mul_vec.x);
mul_vec.y = FixedMul(move_magnitude, mul_vec.y * flip);
mom.x = FixedMul(mul_vec.x, FCOS(FixedAngle(randang)));
mom.y = FixedMul(mul_vec.x, FSIN(FixedAngle(randang)));
mom.z = mul_vec.y;
vector3_t mom = {
.x = FixedMul(mul_vec.x, FCOS(FixedAngle(randang))),
.y = FixedMul(mul_vec.x, FSIN(FixedAngle(randang))),
.z = mul_vec.y,
};
shard->momx = mom.x + player->mo->momx;
shard->momy = mom.y + player->mo->momy;
shard->momz = mom.z + player->mo->momz;
shard->extravalue1 = i % 5; // 20% chance to play the shard sound
}
}
@ -5386,9 +5376,7 @@ void K_PopPlayerShield(player_t *player)
player->itemamount = 0;
break;
case KSHIELD_BUBBLE:
if (player->bubblehealth > 0)
K_BreakBubbleShield(player); // Nice whiff; see ya!
K_BreakBubbleShield(player);
player->bubbleblowup = 0;
player->bubblecool = 0;
player->bubblehealth = 0;
@ -5415,10 +5403,7 @@ void K_RemoveBubbleHealth(player_t *player, INT16 sub)
player->bubblehealth = (UINT8)(max(0, (INT16)(player->bubblehealth) - sub));
if (player->bubblehealth <= 0)
{
K_BreakBubbleShield(player);
K_PopPlayerShield(player);
}
}
mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount)
@ -10797,7 +10782,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
// If you overcharge the Bubble Shield at
// any point, it pops and gives you a boost.
K_BreakBubbleShield(player);
K_PopPlayerShield(player);
if (onground)
@ -10832,10 +10816,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->bubblecool--;
if (player->bubblecool == 0 && player->bubblehealth <= 0)
{
K_BreakBubbleShield(player);
K_PopPlayerShield(player);
}
}
if (buttons & BT_ATTACK || player->bubblecool > 0)

View file

@ -2273,7 +2273,8 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
}
// Bubble Shield protects you from spinout, but not knockback
if (source != NULL && K_GetShieldFromPlayer(player) == KSHIELD_BUBBLE)
if (K_GetShieldFromPlayer(player) == KSHIELD_BUBBLE
&& explosioncombo == false && source != NULL && type != DMG_VOLTAGE)
{
K_PopPlayerShield(player);
K_DoInstashield(player);

View file

@ -69,18 +69,6 @@ mobj_t *boss3cap = NULL;
mobj_t *mobjcache = NULL;
// Bubble Shield overlay spritescales.
static fixed_t bubbleoverlayscales[4][2] = {
// All scales are raw 16.16 fixed-point numbers based on FRACUNIT.
{65536, 65536}, // 1.0, 1.0
{65536, 59667}, // 1.0, 0.9104477
{90593, 61623}, // 1.382352, 0.940298
{100231, 53798} // 1.529411, 0.820895
};
static statenum_t bubbledamagestates[5] = { S_INVISIBLE, S_BUBLSHLD_DMG_1, S_BUBLSHLD_DMG_2, S_BUBLSHLD_DMG_3, S_BUBLSHLD_DMG_4 };
void P_InitCachedActions(void)
{
actioncachehead.prev = actioncachehead.next = &actioncachehead;
@ -9176,8 +9164,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
case MT_BUBBLESHIELD:
{
fixed_t scale;
statenum_t curstate, overlaystate;
const player_t *player = P_MobjWasRemoved(mobj->target) ? NULL : mobj->target->player;
if (player == NULL || player->mo->health == 0 || K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE)
@ -9186,22 +9172,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
return false;
}
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 scale_idx = player->bubblecool > bubbletime ? player->bubblecool % 4 : 0;
if (player->bubbleblowup > 0 && player->bubblecool < 8)
scale_idx = player->bubblecool/2;
fixed_t scale = 5*player->mo->scale/4;
if (player->bubblecool)
{
INT32 blow = player->bubbleblowup + min(player->bubblecool, bubbletime)*10;
scale += (blow * scale) / (bubbletime * 10);
if (curstate != S_BUBBLESHIELDBLOWUP)
if (mobj->state - states != S_BUBBLESHIELDBLOWUP)
P_SetMobjState(mobj, S_BUBBLESHIELDBLOWUP);
mobj->angle += ANGLE_22h;
@ -9210,13 +9188,24 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
else
mobj->renderflags &= ~RF_GHOSTLYMASK;
mobj->frame = states[S_BUBBLESHIELDBLOWUP].frame + scale_idx;
fixed_t stretch = 0;
if (player->bubblecool > bubbletime)
stretch = (player->bubblecool % 4) * FRACUNIT/5;
else if (player->bubbleblowup > 0 && player->bubblecool < 4)
stretch = player->bubblecool*FRACUNIT/6;
else if (player->bubbleblowup >= 4 && player->bubblecool < 8)
stretch = (8 - player->bubblecool)*FRACUNIT/6;
else if (player->bubblehealth == 0 && player->bubbleblowup == 0)
stretch = (player->bubblecool - bubbletime)*FRACUNIT/bubbletime;
mobj->spritexscale = FRACUNIT + FTAN(FixedAngle(stretch*40) + ANGLE_90);
mobj->spriteyscale = FRACUNIT - stretch/3 + abs(FSIN(FixedAngle(stretch*80)))/8;
mobj->colorized = true;
if (player->bubbleblowup > 0 && leveltime & 1)
{
mobj->color = SKINCOLOR_WHITE;
mobj->colorized = true;
}
else
mobj->color = SKINCOLOR_BLUE;
if (P_IsObjectOnGround(player->mo) && player->bubbleblowup > 0)
{
@ -9250,8 +9239,10 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
{
mobj->angle = player->mo->angle;
mobj->renderflags &= ~RF_GHOSTLYMASK;
mobj->colorized = false;
mobj->color = SKINCOLOR_BLUE;
if (curstate == S_BUBBLESHIELDBLOWUP)
if (mobj->state - states == S_BUBBLESHIELDBLOWUP)
P_SetMobjState(mobj, S_BUBBLESHIELD1);
}
@ -9262,24 +9253,22 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
if (mobj->tracer->type == MT_OVERLAY)
{
fixed_t health_scalar;
P_SetScale(mobj->tracer, mobj->destscale);
mobj->tracer->threshold |= OV_DONTXYSCALE;
mobj->tracer->spritexscale = FixedMul(mobj->spritexscale, bubbleoverlayscales[scale_idx][0]);
mobj->tracer->spriteyscale = FixedMul(mobj->spriteyscale, bubbleoverlayscales[scale_idx][1]);
mobj->tracer->spritexscale = mobj->spritexscale;
mobj->tracer->spriteyscale = mobj->spriteyscale;
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)));
statenum_t damagestate = health_scalar == 0 ? S_INVISIBLE : S_BUBLSHLD_DMG_1 - 1 + health_scalar;
// Depending on the level of damage done to the shield, show some cracks.
if (overlaystate != bubbledamagestates[health_scalar])
{
P_SetMobjState(mobj->tracer, bubbledamagestates[health_scalar]);
}
if (mobj->tracer->state - states != damagestate)
P_SetMobjState(mobj->tracer, damagestate);
}
else
{
@ -9328,7 +9317,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
if (curstate != S_INVISIBLE)
{
// "Despawn" and play the glass landing sound.
S_StartSoundAtVolume(mobj, mobj->info->activesound, 40);
if (mobj->extravalue1 == 0)
S_StartSoundAtVolume(mobj, mobj->info->activesound, 128);
P_SetMobjState(mobj, S_INVISIBLE);
}
}