From 1a3630c64035132a7b00e14bf2271d277cbee4dc Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Fri, 31 Oct 2025 20:12:54 +0100 Subject: [PATCH 1/8] 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 --- src/g_game.c | 2 +- src/k_botitem.cpp | 16 +++---- src/k_collide.c | 60 ++++++++++++-------------- src/k_collide.h | 2 +- src/k_hud.c | 4 +- src/k_kart.c | 87 +++++++++++++++++++------------------- src/k_kart.h | 15 +++---- src/p_inter.c | 43 ++++++------------- src/p_mobj.c | 105 ++++++++++++++-------------------------------- 9 files changed, 128 insertions(+), 206 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 9530fc28f..d9a4eb195 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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; diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index bcaccaea9..4ff9197cd 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -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; } diff --git a/src/k_collide.c b/src/k_collide.c index 72badbcce..0ff5b3ea1 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -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); diff --git a/src/k_collide.h b/src/k_collide.h index 2677f409b..367855e14 100644 --- a/src/k_collide.h +++ b/src/k_collide.h @@ -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); diff --git a/src/k_hud.c b/src/k_hud.c index 22c73b666..18a3dd662 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -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); diff --git a/src/k_kart.c b/src/k_kart.c index 226949569..fe4afadc8 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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; } } } diff --git a/src/k_kart.h b/src/k_kart.h index 22ad14eba..a6d89e115 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -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) diff --git a/src/p_inter.c b/src/p_inter.c index 0cb66ce6a..dd4df34bb 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -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); diff --git a/src/p_mobj.c b/src/p_mobj.c index 81d423a5c..c0ea81917 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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; } From b9022d9640b0571ada06e5ccb2efe8191aee315c Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 1 Nov 2025 01:27:41 +0100 Subject: [PATCH 2/8] 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 --- src/k_collide.c | 3 +- src/k_hud.c | 90 ++++++++++++++++--------------------------------- src/k_kart.c | 65 +++++++++++++---------------------- src/p_inter.c | 3 +- src/p_mobj.c | 66 +++++++++++++++--------------------- 5 files changed, 84 insertions(+), 143 deletions(-) diff --git a/src/k_collide.c b/src/k_collide.c index 0ff5b3ea1..b6c1b6da0 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -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]) diff --git a/src/k_hud.c b/src/k_hud.c index 18a3dd662..4f46dcb4f 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -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) diff --git a/src/k_kart.c b/src/k_kart.c index fe4afadc8..bed9d3960 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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) diff --git a/src/p_inter.c b/src/p_inter.c index dd4df34bb..b6c202b70 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -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); diff --git a/src/p_mobj.c b/src/p_mobj.c index c0ea81917..082da153a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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); } } From ea6bab146dd9acdfa92df03255ab20d121e7a6bf Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 1 Nov 2025 02:40:05 +0100 Subject: [PATCH 3/8] More tweaks to player/item bumps, minimum charge, overcharge bonus --- src/k_collide.c | 2 +- src/k_kart.c | 14 +++++++++----- src/k_kart.h | 2 +- src/p_mobj.c | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/k_collide.c b/src/k_collide.c index b6c1b6da0..9bbf3955d 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -548,7 +548,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->bubbleblowup = 0; + player->bubbleblowup /= 2; } static boolean K_BubbleReflectingTrapItem(const mobj_t *t) diff --git a/src/k_kart.c b/src/k_kart.c index bed9d3960..e9266797e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -485,7 +485,8 @@ 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 - || (K_GetShieldFromPlayer(against->player) == KSHIELD_BUBBLE && K_GetShieldFromPlayer(mobj->player) != KSHIELD_BUBBLE))) // They have a Bubble Shield + || (K_GetShieldFromPlayer(against->player) == KSHIELD_BUBBLE // They have a Bubble Shield + && K_GetShieldFromPlayer(mobj->player) != KSHIELD_BUBBLE))) // and you don't { weight = 0; // This player does not cause any bump action } @@ -497,7 +498,7 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) weight = (mobj->player->kartweight) * FRACUNIT; - if (K_GetShieldFromPlayer(mobj->player) == KSHIELD_BUBBLE) + if (K_GetShieldFromPlayer(mobj->player) == KSHIELD_BUBBLE && mobj->player->bubblecool == 0) { weight = max(BUBBLEMINWEIGHT, weight); weight = FixedMul(weight, FRACUNIT/16); @@ -766,7 +767,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) + if (p1shield == KSHIELD_BUBBLE && mobj1->player->bubblecool == 0) { // Each bump does chip damage to the shield. K_RemoveBubbleHealth(mobj1->player, BUBBLEBUMPCHIP); @@ -802,7 +803,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) + if (p2shield == KSHIELD_BUBBLE && mobj2->player->bubblecool == 0) { // Each bump does chip damage to the shield. K_RemoveBubbleHealth(mobj2->player, BUBBLEBUMPCHIP); @@ -10767,7 +10768,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (!HOLDING_ITEM && NO_HYUDORO) { - if ((buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY)) + if ((buttons & BT_ATTACK && player->itemflags & IF_HOLDREADY) + || (player->bubbleblowup > 0 && player->bubblecool <= bubbletime/2)) // auto { if (player->bubblecool == 0) S_StartSound(player->mo, sfx_s3k75); @@ -10777,6 +10779,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->bubbleblowup += 3; player->bubblehealth--; } + else if (player->bubblecool >= bubbletime) + player->bubbleblowup++; // overcharge bonus if (++player->bubblecool >= bubbletime + TICRATE/2) { diff --git a/src/k_kart.h b/src/k_kart.h index a6d89e115..ffd764659 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -75,7 +75,7 @@ extern vector3_t clusterpoint, clusterdtf; #define BUBBLEBUMPCHIP ((MAXBUBBLEHEALTH + 1) / 2) // Damage done to an inflated Bubble Shield (rounded up) -#define BUBBLEITMDAMAGE ((MAXBUBBLEHEALTH + 5) / 6) +#define BUBBLEITMDAMAGE ((MAXBUBBLEHEALTH + 8) / 9) // Bump weight for a Bubble Shield #define BUBBLEMINWEIGHT (5 * FRACUNIT) diff --git a/src/p_mobj.c b/src/p_mobj.c index 082da153a..26f1d8ed0 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9189,17 +9189,17 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->renderflags &= ~RF_GHOSTLYMASK; 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) + if (player->bubblecool >= bubbletime) // overcharge + stretch = ((player->bubblecool - bubbletime) % 4) * FRACUNIT/4; + else if (player->bubbleblowup > 0 && player->bubblecool <= 4) // inflate 1 + stretch = player->bubblecool*FRACUNIT/5; + else if (player->bubbleblowup >= 5 && player->bubblecool < 8) // inflate 2 + stretch = (8 - player->bubblecool)*FRACUNIT/5; + else if (player->bubblehealth == 0 && player->bubbleblowup == 0) // decaying 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->spritexscale = FRACUNIT + FTAN(FixedAngle(stretch*30) + ANGLE_90); + mobj->spriteyscale = FRACUNIT - stretch/5; mobj->colorized = true; if (player->bubbleblowup > 0 && leveltime & 1) @@ -9214,7 +9214,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) for (i = 0; i < 2; i++) { angle_t a = mobj->angle + ((i & 1) ? ANGLE_180 : 0); - fixed_t ws = player->mo->scale/2 + scale/5; + fixed_t ws = player->mo->scale/4 + scale/4; mobj_t *wave; wave = P_SpawnMobj( From 6be6e19fba349d2c76ab2af2e19969de29cf0994 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 1 Nov 2025 03:13:28 +0100 Subject: [PATCH 4/8] Save shield health on respawn --- src/g_game.c | 11 +++++++++++ src/k_kart.c | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index d9a4eb195..e9249f005 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2744,6 +2744,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 previtemroulette; INT32 roulettetype; INT32 growshrinktimer; + UINT8 bubblehealth; INT32 bumper; INT32 karmapoints; INT32 wanted; @@ -2812,6 +2813,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) itemtype = 0; itemamount = 0; growshrinktimer = 0; + bubblehealth = 0; bumper = ((gametyperules & GTR_BUMPERS) ? K_StartingBumperCount() : 0); karmapoints = 0; wanted = 0; @@ -2867,6 +2869,14 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) else growshrinktimer = 0; + // deplete your item stack if you died with zero bubble health + bubblehealth = players[player].bubblehealth; + if (bubblehealth == 0 && itemtype == KITEM_BUBBLESHIELD && itemamount > 0) + { + if (--itemamount == 0) + itemtype = 0; + } + bumper = players[player].bumper; karmapoints = players[player].karmapoints; wanted = players[player].wanted; @@ -3001,6 +3011,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->itemtype = itemtype; p->itemamount = itemamount; p->growshrinktimer = growshrinktimer; + p->bubblehealth = bubblehealth; p->bumper = bumper; p->karmadelay = comebacktime; p->karmapoints = karmapoints; diff --git a/src/k_kart.c b/src/k_kart.c index e9266797e..a525f0ed0 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10763,7 +10763,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&shield->target, player->mo); P_SetTarget(&player->shieldtracer, shield); S_StartSound(player->mo, sfx_s3k3f); - player->bubblehealth = MAXBUBBLEHEALTH; + if (player->bubblehealth <= 0 || player->bubblehealth > MAXBUBBLEHEALTH) + player->bubblehealth = MAXBUBBLEHEALTH; } if (!HOLDING_ITEM && NO_HYUDORO) From 0572cff4373719219ac0dff820a124d0dda63d6f Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sat, 1 Nov 2025 00:55:31 -0400 Subject: [PATCH 5/8] cvars for more opinionated things we can play around with in tests --- src/k_kart.c | 29 ++++++++++++++++++++++++++++- src/k_kart.h | 8 +++++++- src/p_inter.c | 4 +++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index a525f0ed0..4266b4d65 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -82,6 +82,12 @@ consvar_t cv_driftsparkpulse = CVAR_INIT ("driftsparkpulse", "1.4", CV_SAVE|CV_F consvar_t cv_saltyhop = CVAR_INIT ("hardcodehop", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_karthitemdialog = CVAR_INIT ("karthitemdialog", "On", CV_SAVE, CV_OnOff, NULL); +// opinionated stuff for testing +consvar_t cv_kartbubble_defense_canidle = CVAR_INIT ("kartbubble_defense_canidle", "On", CV_NETVAR, CV_OnOff, NULL); +static CV_PossibleValue_t bubble_defense_damagerate_cons_t[] = {{0, "MIN"}, {FRACUNIT, "MAX"}, {0, NULL}}; +consvar_t cv_kartbubble_defense_damagerate = CVAR_INIT ("kartbubble_defense_damagerate", "1.0", CV_NETVAR|CV_FLOAT, bubble_defense_damagerate_cons_t, NULL); +consvar_t cv_kartbubble_boost_allow = CVAR_INIT ("kartbubble_boost_allow", "On", CV_NETVAR, CV_OnOff, NULL); + // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) // franticitems is Frantic Mode items, bool @@ -303,6 +309,11 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartdebugcolorize); CV_RegisterVar(&cv_kartdebugdirector); + // experimental stuff + CV_RegisterVar(&cv_kartbubble_defense_canidle); + CV_RegisterVar(&cv_kartbubble_defense_damagerate); + CV_RegisterVar(&cv_kartbubble_boost_allow); + // HUD cvars K_RegisterKartHUDStuff(); @@ -5398,9 +5409,21 @@ void K_PopPlayerShield(player_t *player) K_UnsetItemOut(player); } +// Returns true is the bubble is actively in defense mode (inflating or inflated) +boolean K_IsBubbleDefending(player_t *player) +{ + if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) return false; + return (player->bubbleblowup > 0 && player->bubblecool > 0); +} + // Depletes a bubble shield's health, and pops the shield at 0 health. void K_RemoveBubbleHealth(player_t *player, INT16 sub) { + // experiment: inflated bubble shield applies a direct reduction to all incoming damage + if (K_IsBubbleDefending(player)) + { + sub = FixedMul(sub<>FRACBITS; + } player->bubblehealth = (UINT8)(max(0, (INT16)(player->bubblehealth) - sub)); if (player->bubblehealth <= 0) @@ -10798,7 +10821,11 @@ void K_MoveKartPlayer(player_t *player, boolean onground) K_PlayBoostTaunt(player->mo); } - player->bubbleboost = 7 * sneakertime / 10; + // experiment: don't boost, just give invulnerability + if (cv_kartbubble_boost_allow.value) + { + player->bubbleboost = 7 * sneakertime / 10; + } player->flashing = 4*TICRATE/7; } } diff --git a/src/k_kart.h b/src/k_kart.h index ffd764659..057cdffcb 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -20,6 +20,11 @@ extern consvar_t cv_kartchainingsound; extern consvar_t cv_kartdriftsounds; extern consvar_t cv_kartdriftefx; +// opinionated stuff for testing +extern consvar_t cv_kartbubble_defense_canidle; +extern consvar_t cv_kartbubble_defense_damagerate; +extern consvar_t cv_kartbubble_boost_allow; + #define KART_FULLTURN 800 /* @@ -68,7 +73,7 @@ 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 (determines how many times it can be inflated) +// Bubble Shield's maximum health (determines how many times it can be fully inflated) #define MAXBUBBLEHEALTH (bubbletime * 3) // Chip damage done to a Bubble Shield upon bumping a player when not inflated (rounded up) @@ -254,6 +259,7 @@ void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source); void K_UpdateHnextList(player_t *player, boolean clean); void K_DropHnextList(player_t *player); void K_PopPlayerShield(player_t *player); +boolean K_IsBubbleDefending(player_t *player); void K_RemoveBubbleHealth(player_t *player, INT16 sub); void K_RepairOrbitChain(mobj_t *orbit); void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player); diff --git a/src/p_inter.c b/src/p_inter.c index b6c202b70..951bd2ffe 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2274,7 +2274,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da // Bubble Shield protects you from spinout, but not knockback if (K_GetShieldFromPlayer(player) == KSHIELD_BUBBLE - && explosioncombo == false && source != NULL && type != DMG_VOLTAGE) + && explosioncombo == false && source != NULL && type != DMG_VOLTAGE + // experiment: disable this since defense can be prepared in advance instead of requiring reaction + && cv_kartbubble_defense_canidle.value) { K_PopPlayerShield(player); K_DoInstashield(player); From a5805a7d21fca408194f568d20630662ea063e70 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sat, 1 Nov 2025 01:28:58 -0400 Subject: [PATCH 6/8] tweak visuals when idle defense is off todo: fix interaction with landmine not being correct --- src/k_kart.c | 2 +- src/p_mobj.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 4266b4d65..6807287f5 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5413,7 +5413,7 @@ void K_PopPlayerShield(player_t *player) boolean K_IsBubbleDefending(player_t *player) { if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) return false; - return (player->bubbleblowup > 0 && player->bubblecool > 0); + return (player->bubbleblowup); } // Depletes a bubble shield's health, and pops the shield at 0 health. diff --git a/src/p_mobj.c b/src/p_mobj.c index 26f1d8ed0..b465f587c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9183,7 +9183,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj) P_SetMobjState(mobj, S_BUBBLESHIELDBLOWUP); mobj->angle += ANGLE_22h; - if (player->bubbleblowup == 0 && player->bubblecool > 0) + if (cv_kartbubble_defense_canidle.value && player->bubbleblowup == 0 && player->bubblecool > 0) + mobj->renderflags |= RF_GHOSTLY; + else if ((!cv_kartbubble_defense_canidle.value) && K_IsBubbleDefending(player)) mobj->renderflags |= RF_GHOSTLY; else mobj->renderflags &= ~RF_GHOSTLYMASK; From 8616d2683345880d701e3be299148026a6fccef2 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sat, 1 Nov 2025 01:39:10 -0400 Subject: [PATCH 7/8] oops --- src/p_mobj.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index b465f587c..8afc7a4c1 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9185,7 +9185,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->angle += ANGLE_22h; if (cv_kartbubble_defense_canidle.value && player->bubbleblowup == 0 && player->bubblecool > 0) mobj->renderflags |= RF_GHOSTLY; - else if ((!cv_kartbubble_defense_canidle.value) && K_IsBubbleDefending(player)) + else if ((!cv_kartbubble_defense_canidle.value) && !K_IsBubbleDefending(player)) mobj->renderflags |= RF_GHOSTLY; else mobj->renderflags &= ~RF_GHOSTLYMASK; From 6d18483ffdaa44189b1f05c0e8cf2a615b523995 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sat, 1 Nov 2025 02:18:41 -0400 Subject: [PATCH 8/8] bring bubble renderflag stuff out of bubblecool conditional --- src/p_mobj.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 8afc7a4c1..e3847f74b 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9173,6 +9173,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } fixed_t scale = 5*player->mo->scale/4; + if (cv_kartbubble_defense_canidle.value && player->bubbleblowup == 0 && player->bubblecool > 0) + mobj->renderflags |= RF_GHOSTLY; + else if ((!cv_kartbubble_defense_canidle.value) && !K_IsBubbleDefending(player)) + mobj->renderflags |= (RF_TRANS40 | RF_FULLBRIGHT); + else + mobj->renderflags &= ~RF_GHOSTLYMASK; if (player->bubblecool) { @@ -9183,12 +9189,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) P_SetMobjState(mobj, S_BUBBLESHIELDBLOWUP); mobj->angle += ANGLE_22h; - if (cv_kartbubble_defense_canidle.value && player->bubbleblowup == 0 && player->bubblecool > 0) - mobj->renderflags |= RF_GHOSTLY; - else if ((!cv_kartbubble_defense_canidle.value) && !K_IsBubbleDefending(player)) - mobj->renderflags |= RF_GHOSTLY; - else - mobj->renderflags &= ~RF_GHOSTLYMASK; fixed_t stretch = 0; if (player->bubblecool >= bubbletime) // overcharge @@ -9240,7 +9240,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) else { mobj->angle = player->mo->angle; - mobj->renderflags &= ~RF_GHOSTLYMASK; + // mobj->renderflags &= ~RF_GHOSTLYMASK; mobj->colorized = false; mobj->color = SKINCOLOR_BLUE;