diff --git a/src/deh_tables.c b/src/deh_tables.c index 35c7b649d..0fe01a222 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -2028,6 +2028,15 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BUBBLESHIELDWAVE5", "S_BUBBLESHIELDWAVE6", + // Bubble Shield Visuals + "S_BUBA1", + "S_BUBB1", + "S_BUBB2", + "S_BUBC1", + "S_BUBC2", + "S_BUBD1", + "S_BUBE1", + // Flame Shield "S_FLAMESHIELD1", "S_FLAMESHIELD2", @@ -3616,6 +3625,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_LIGHTNINGSHIELD", // Shields "MT_BUBBLESHIELD", + "MT_BUBBLESHIELD_VISUAL", "MT_FLAMESHIELD", "MT_FLAMESHIELDUNDERLAY", "MT_FLAMESHIELDPAPER", diff --git a/src/info.c b/src/info.c index faed52546..1973faecc 100644 --- a/src/info.c +++ b/src/info.c @@ -376,6 +376,11 @@ char sprnames[NUMSPRITES + 1][5] = "TRNQ", // SPB Manta Ring loop "THNS", // Lightning Shield "BUBS", // Bubble Shield (not Bubs) + "BUBA", // Bubble Shield Outline + "BUBB", // Bubble Shield Top Wave + "BUBC", // Bubble Shield Bottom Wave + "BUBD", // Bubble Shield Reflection + "BUBE", // Bubble Shield Underline "BWVE", // Bubble Shield waves "FLMS", // Flame Shield "FLMD", // Flame Shield dash @@ -2564,6 +2569,15 @@ state_t states[NUMSTATES] = {SPR_BWVE, FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_BUBBLESHIELDWAVE6}, // S_BUBBLESHIELDWAVE5 {SPR_BWVE, FF_FULLBRIGHT|5, 1, {NULL}, 0, 0, S_NULL}, // S_BUBBLESHIELDWAVE6 + // Bubble Shield Visuals + {SPR_BUBA, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_BUBA1}, // S_BUBA1 + {SPR_BUBB, FF_FULLBRIGHT|FF_ANIMATE, 36, {NULL}, 8, 4, S_BUBB1}, // S_BUBB1 + {SPR_NULL, 0, 5, {NULL}, 0, 0, S_BUBB1}, // S_BUBB2 + {SPR_BUBC, FF_FULLBRIGHT|FF_ANIMATE, 36, {NULL}, 8, 4, S_BUBC1}, // S_BUBC1 + {SPR_NULL, 0, 5, {NULL}, 0, 0, S_BUBC1}, // S_BUBC2 + {SPR_BUBD, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_BUBD1}, // S_BUBD1 + {SPR_BUBE, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_BUBE1}, // S_BUBE1 + {SPR_FLMS, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_FLAMESHIELD2}, // S_FLAMESHIELD1 {SPR_FLMS, FF_FULLBRIGHT|9, 2, {NULL}, 0, 0, S_FLAMESHIELD3}, // S_FLAMESHIELD2 {SPR_FLMS, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_FLAMESHIELD4}, // S_FLAMESHIELD3 @@ -15395,6 +15409,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_BUBBLESHIELD_VISUAL + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 8, // speed + 28*FRACUNIT, // radius + 56*FRACUNIT, // height + 1, // display offset + 16, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + { // MT_FLAMESHIELD -1, // doomednum S_FLAMESHIELD1, // spawnstate diff --git a/src/info.h b/src/info.h index a525bfd50..9b26cad1c 100644 --- a/src/info.h +++ b/src/info.h @@ -915,6 +915,11 @@ typedef enum sprite SPR_TRNQ, // SPB Manta Ring loop SPR_THNS, // Thunder Shield SPR_BUBS, // Bubble Shield (not Bubs) + SPR_BUBA, // Bubble Shield Outline + SPR_BUBB, // Bubble Shield Top Wave + SPR_BUBC, // Bubble Shield Bottom Wave + SPR_BUBD, // Bubble Shield Reflection + SPR_BUBE, // Bubble Shield Underline SPR_BWVE, // Bubble Shield waves SPR_FLMS, // Flame Shield SPR_FLMD, // Flame Shield dash @@ -3059,6 +3064,15 @@ typedef enum state S_BUBBLESHIELDWAVE5, S_BUBBLESHIELDWAVE6, + // Bubble Shield Visuals + S_BUBA1, + S_BUBB1, + S_BUBB2, + S_BUBC1, + S_BUBC2, + S_BUBD1, + S_BUBE1, + // Flame Shield S_FLAMESHIELD1, S_FLAMESHIELD2, @@ -4674,6 +4688,7 @@ typedef enum mobj_type MT_LIGHTNINGSHIELD, // Shields MT_BUBBLESHIELD, + MT_BUBBLESHIELD_VISUAL, MT_FLAMESHIELD, MT_FLAMESHIELDUNDERLAY, MT_FLAMESHIELDPAPER, diff --git a/src/k_kart.c b/src/k_kart.c index cf1fb5a20..208fe1eff 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -14092,6 +14092,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&shield->target, player->mo); S_StartSound(player->mo, sfx_s3k3f); player->curshield = KSHIELD_BUBBLE; + + Obj_SpawnBubbleShieldVisuals(shield); } if (!HOLDING_ITEM && NO_HYUDORO) diff --git a/src/k_objects.h b/src/k_objects.h index 0d75c222a..ed0d62022 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -449,6 +449,10 @@ UINT8 K_HogChargeToHogCount(INT32 charge, UINT8 cap); void K_UpdateBallhogReticules(player_t *player, UINT8 num_hogs, boolean on_release); void K_DoBallhogAttack(player_t *player, UINT8 num_hogs); +/* Bubble Shield */ +void Obj_SpawnBubbleShieldVisuals(mobj_t *source); +boolean Obj_TickBubbleShieldVisual(mobj_t *mobj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 35ee3ad6c..01c773b7d 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -60,6 +60,7 @@ target_sources(SRB2SDL2 PRIVATE pulley.cpp amps.c ballhog.cpp + bubble-shield.cpp flybot767.c ) diff --git a/src/objects/bubble-shield.cpp b/src/objects/bubble-shield.cpp new file mode 100644 index 000000000..89d60e129 --- /dev/null +++ b/src/objects/bubble-shield.cpp @@ -0,0 +1,136 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include + +#include "objects.hpp" + +#include "../m_easing.h" +#include "../m_fixed.h" +#include "../tables.h" + +using namespace srb2::objects; + +namespace +{ + +struct Bubble : Mobj +{ + static constexpr fixed_t kBaseScale = 5*FRACUNIT/4; + static constexpr fixed_t kMaxScale = 5*FRACUNIT; + static constexpr fixed_t kScaleRange = kMaxScale - kBaseScale; + + void target() = delete; + Mobj* follow() const { return Mobj::target(); } + void follow(Mobj* n) { Mobj::target(n); } + + player_t* player() const { return follow()->player; } + + bool valid() const { return Mobj::valid(follow()) && player(); } +}; + +struct Visual : Mobj +{ + void target() = delete; + Bubble* bubble() const { return Mobj::target(); } + void bubble(Bubble* n) { Mobj::target(n); } + + void extravalue1() = delete; + Fixed prev_scale() const { return Mobj::extravalue1; } + void prev_scale(Fixed n) { Mobj::extravalue1 = n; } + + bool valid() const { return Mobj::valid(bubble()) && bubble()->valid(); } + + static void spawn + ( Bubble* bubble, + statenum_t state, + int flicker, + int offset) + { + if (!bubble->valid()) + return; + + Visual* x = Mobj::spawn(bubble->pos(), MT_BUBBLESHIELD_VISUAL); + //x->scale(5 * x->scale() / 4); + x->state(state); + x->spriteyoffset(22*FRACUNIT); + x->bubble(bubble); + x->linkdraw(bubble->follow(), offset); + + if (flicker) + x->renderflags |= RF_DONTDRAW; + } + + bool tick() + { + if (!valid()) + { + remove(); + return false; + } + + move_origin(bubble()->pos()); + + renderflags = ((renderflags ^ RF_DONTDRAW) & RF_DONTDRAW); + + // ATTENTION: this object relies on the original MT_BUBBLESHIELD object for scale + fixed_t f = Fixed {bubble()->scale() / bubble()->follow()->scale() - Bubble::kBaseScale} / Fixed {Bubble::kScaleRange}; + + scale(Easing_Linear(f, + bubble()->follow()->scale() * Fixed {Bubble::kBaseScale}, + bubble()->follow()->scale() * 4)); + + if (sprite != SPR_BUBB && + sprite != SPR_BUBC && + bubble()->player()->bubblecool && + f == 0) // base size + renderflags |= RF_DONTDRAW; + + if (scale() > prev_scale()) + spritescale({ 3*FRACUNIT/2, 3*FRACUNIT/4 }); + else if (scale() < prev_scale()) + spritescale({ 3*FRACUNIT/4, 3*FRACUNIT/2 }); + else + { + if (f == FRACUNIT) // max size + { + if (leveltime & 1) + spritescale({ 3*FRACUNIT/4, 3*FRACUNIT/2 }); + else + { + spritescale({ FRACUNIT, FRACUNIT }); + renderflags |= RF_ADD; + } + } + else + spritescale({ FRACUNIT, FRACUNIT }); + } + + prev_scale(scale()); + + return true; + } +}; + +}; // namespace + +void Obj_SpawnBubbleShieldVisuals(mobj_t *bubble) +{ + Visual::spawn(static_cast(bubble), S_BUBA1, 1, 2); + Visual::spawn(static_cast(bubble), S_BUBB1, 0, 1); + Visual::spawn(static_cast(bubble), S_BUBC1, 1, -1); + Visual::spawn(static_cast(bubble), S_BUBD1, 0, -2); + Visual::spawn(static_cast(bubble), S_BUBE1, 1, -3); +} + +boolean Obj_TickBubbleShieldVisual(mobj_t *mobj) +{ + return static_cast(mobj)->tick(); +} diff --git a/src/p_mobj.c b/src/p_mobj.c index 683163ee8..7e370eb1e 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6658,6 +6658,14 @@ static void P_MobjSceneryThink(mobj_t *mobj) Obj_PulleyThink(mobj); return; } + case MT_BUBBLESHIELD_VISUAL: + { + if (!Obj_TickBubbleShieldVisual(mobj)) + { + return; + } + break; + } default: if (mobj->fuse) { // Scenery object fuse! Very basic! @@ -8534,6 +8542,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) return false; } + // FIXME: Old Bubble Shield code is still running. + // Some of it is visual, some gameplay. + // I left it alone and just tell it to go invisible~ + // See objects/bubble-shield.cpp + mobj->renderflags |= RF_DONTDRAW; + scale = (5*mobj->target->scale)>>2; curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states)));