diff --git a/src/d_player.h b/src/d_player.h index 9e5d8cb45..d93bb97f8 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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 diff --git a/src/k_collide.c b/src/k_collide.c index 8dfc2ff76..ea763e9d1 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -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); } diff --git a/src/k_collide.h b/src/k_collide.h index 958f3f410..655781c2a 100644 --- a/src/k_collide.h +++ b/src/k_collide.h @@ -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); diff --git a/src/k_kart.c b/src/k_kart.c index 3b946cb1d..f9b4ce5cf 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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--; diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 4a32f4d18..5ce64548e 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -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; diff --git a/src/p_map.c b/src/p_map.c index ddc5c3a53..c0614dfcd 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -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 diff --git a/src/p_saveg.c b/src/p_saveg.c index f67d299a3..0c79754c6 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -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);