diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 4fcb45dcd..2ef89d216 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2564,6 +2564,7 @@ void CL_ClearPlayer(INT32 playernum) // TODO: Any better handling in store? P_SetTarget(&players[playernum].flickyAttacker, NULL); P_SetTarget(&players[playernum].powerup.flickyController, NULL); + P_SetTarget(&players[playernum].powerup.barrier, NULL); // These are camera items and possibly belong to multiple players. P_SetTarget(&players[playernum].skybox.viewpoint, NULL); diff --git a/src/d_player.h b/src/d_player.h index d9d46979d..ed64b9fc0 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -212,6 +212,8 @@ typedef enum NUMPOWERUPS = ENDOFPOWERUPS - FIRSTPOWERUP, } kartitems_t; +#define POWERUP_BIT(x) (1 << ((x) - FIRSTPOWERUP)) + typedef enum { KSHIELD_NONE = 0, @@ -545,6 +547,7 @@ struct powerupvars_t { UINT16 barrierTimer; UINT16 rhythmBadgeTimer; mobj_t *flickyController; + mobj_t *barrier; }; // player_t struct for all alternative viewpoint variables diff --git a/src/deh_tables.c b/src/deh_tables.c index 7175b7a35..4fd9a3449 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4787,6 +4787,10 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BLENDEYE_PUYO_SHOCK", "S_BLENDEYE_PUYO_DIE", "S_BLENDEYE_PUYO_DUST", + + "S_MEGABARRIER1", + "S_MEGABARRIER2", + "S_MEGABARRIER3", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -5994,6 +5998,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BLENDEYE_PUYO", "MT_BLENDEYE_PUYO_DUST", "MT_BLENDEYE_PUYO_DUST_COFFEE", + "MT_MEGABARRIER", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/g_game.c b/src/g_game.c index 9aa814730..72b7294c2 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2256,6 +2256,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) P_SetTarget(&players[player].awayview.mobj, NULL); P_SetTarget(&players[player].flickyAttacker, NULL); P_SetTarget(&players[player].powerup.flickyController, NULL); + P_SetTarget(&players[player].powerup.barrier, NULL); // The following pointers are safe to set directly, because the end goal should be refcount consistency before and after remanifestation. ringShooter = players[player].ringShooter; diff --git a/src/info.c b/src/info.c index 445de3dc9..0fdde16c6 100644 --- a/src/info.c +++ b/src/info.c @@ -966,6 +966,8 @@ char sprnames[NUMSPRITES + 1][5] = "PUYD", "PUYE", + "MGSH", // Mega Barrier + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5613,6 +5615,10 @@ state_t states[NUMSTATES] = {SPR_PUYA, 3, -1, {A_BlendEyePuyoHack}, 0, 0, S_NULL}, // S_BLENDEYE_PUYO_SHOCK, {SPR_PUYA, 4|FF_ANIMATE, 5, {A_BlendEyePuyoHack}, 2, 2, S_NULL}, // S_BLENDEYE_PUYO_DIE, {SPR_PUYA, 5, 2, {A_BlendEyePuyoHack}, 0, 0, S_BLENDEYE_PUYO_DIE}, // S_BLENDEYE_PUYO_DUST, + + {SPR_MGSH, 2|FF_PAPERSPRITE|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MEGABARRIER1, + {SPR_MGSH, 1|FF_PAPERSPRITE|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MEGABARRIER2, + {SPR_MGSH, 0|FF_PAPERSPRITE|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MEGABARRIER3, }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -31738,6 +31744,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_SCENERY|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags S_NULL // raisestate }, + + { // MT_MEGABARRIER + -1, // doomednum + S_MEGABARRIER1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // 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 + 0, // speed + 0, // radius + 0, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOSQUISH, // flags + S_NULL // raisestate + } }; diff --git a/src/info.h b/src/info.h index 3d83aebed..90f80efcf 100644 --- a/src/info.h +++ b/src/info.h @@ -1520,6 +1520,8 @@ typedef enum sprite SPR_PUYD, SPR_PUYE, + SPR_MGSH, // Mega Barrier + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -6038,6 +6040,10 @@ typedef enum state S_BLENDEYE_PUYO_DIE, S_BLENDEYE_PUYO_DUST, + S_MEGABARRIER1, + S_MEGABARRIER2, + S_MEGABARRIER3, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -7264,6 +7270,8 @@ typedef enum mobj_type MT_BLENDEYE_PUYO_DUST, MT_BLENDEYE_PUYO_DUST_COFFEE, + MT_MEGABARRIER, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_objects.h b/src/k_objects.h index 619b8f81c..d50e17125 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -293,6 +293,9 @@ void Obj_BallSwitchThink(mobj_t *mobj); void Obj_BallSwitchTouched(mobj_t *mobj, mobj_t *toucher); void Obj_BallSwitchDamaged(mobj_t *mobj, mobj_t *inflictor, mobj_t *source); +/* Barrier Power-Up */ +void Obj_SpawnMegaBarrier(player_t *player); +boolean Obj_MegaBarrierThink(mobj_t *mobj); #ifdef __cplusplus } // extern "C" diff --git a/src/k_powerup.cpp b/src/k_powerup.cpp index 3d1fcb312..51b9f96c7 100644 --- a/src/k_powerup.cpp +++ b/src/k_powerup.cpp @@ -27,17 +27,19 @@ tic_t K_PowerUpRemaining(const player_t* player, kartitems_t powerup) } } -boolean K_AnyPowerUpRemaining(const player_t* player) +UINT32 K_AnyPowerUpRemaining(const player_t* player) { + UINT32 mask = 0; + for (int k = FIRSTPOWERUP; k < ENDOFPOWERUPS; ++k) { if (K_PowerUpRemaining(player, static_cast(k))) { - return true; + mask |= POWERUP_BIT(k); } } - return false; + return mask; } void K_GivePowerUp(player_t* player, kartitems_t powerup, tic_t time) @@ -56,6 +58,7 @@ void K_GivePowerUp(player_t* player, kartitems_t powerup, tic_t time) case POWERUP_BARRIER: player->powerup.barrierTimer += time; + Obj_SpawnMegaBarrier(player); break; case POWERUP_BUMPER: diff --git a/src/k_powerup.h b/src/k_powerup.h index 0598e45ce..0a94edf39 100644 --- a/src/k_powerup.h +++ b/src/k_powerup.h @@ -9,7 +9,7 @@ extern "C" { #endif tic_t K_PowerUpRemaining(const player_t *player, kartitems_t powerup); -boolean K_AnyPowerUpRemaining(const player_t *player); +UINT32 K_AnyPowerUpRemaining(const player_t *player); // returns POWERUP_BIT mask void K_GivePowerUp(player_t *player, kartitems_t powerup, tic_t timer); void K_DropPowerUps(player_t *player); diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 8abbd34ee..2bd4a38b3 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -40,6 +40,7 @@ target_sources(SRB2SDL2 PRIVATE shadow.cpp ball-switch.cpp charge.c + mega-barrier.cpp ) add_subdirectory(versus) diff --git a/src/objects/block.c b/src/objects/block.c index d9f1dbd82..d5682d8a5 100644 --- a/src/objects/block.c +++ b/src/objects/block.c @@ -5,11 +5,6 @@ #include "../k_kart.h" #include "../k_powerup.h" -static INT16 guard_upscale (player_t *player) -{ - return K_PowerUpRemaining(player, POWERUP_BARRIER) ? 40 : player->spheres; -} - void Obj_BlockRingThink (mobj_t *ring) { if (P_MobjWasRemoved(ring->target) || !ring->target->player) @@ -28,7 +23,7 @@ void Obj_BlockRingThink (mobj_t *ring) ring->color = mo->color; fixed_t baseScale = mo->scale / 2; - baseScale += (mo->scale / 30) * guard_upscale(player); + baseScale += (mo->scale / 30) * player->spheres; P_SetScale(ring, baseScale); // Twirl @@ -41,7 +36,7 @@ void Obj_BlockRingThink (mobj_t *ring) else ring->renderflags |= RF_DONTDRAW; - if (!K_PlayerGuard(player)) + if (K_PowerUpRemaining(player, POWERUP_BARRIER) || !K_PlayerGuard(player)) ring->renderflags |= RF_DONTDRAW; } } @@ -61,7 +56,7 @@ void Obj_BlockBodyThink (mobj_t *body) body->flags &= ~(MF_NOCLIPTHING); fixed_t baseScale = mo->scale / 2; - baseScale += (mo->scale / 30) * guard_upscale(player); + baseScale += (mo->scale / 30) * player->spheres; P_SetScale(body, baseScale); P_MoveOrigin(body, mo->x, mo->y, mo->z + mo->height/2); @@ -76,7 +71,7 @@ void Obj_BlockBodyThink (mobj_t *body) else body->renderflags |= RF_DONTDRAW; - if (!K_PlayerGuard(player)) + if (K_PowerUpRemaining(player, POWERUP_BARRIER) || !K_PlayerGuard(player)) body->renderflags |= RF_DONTDRAW; } } diff --git a/src/objects/mega-barrier.cpp b/src/objects/mega-barrier.cpp new file mode 100644 index 000000000..f7ed04719 --- /dev/null +++ b/src/objects/mega-barrier.cpp @@ -0,0 +1,161 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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 "../doomdef.h" +#include "../d_player.h" +#include "../g_game.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../k_powerup.h" +#include "../m_fixed.h" +#include "../p_local.h" +#include "../p_mobj.h" +#include "../r_defs.h" + +#define barrier_player(o) ((o)->extravalue1) + +namespace +{ + +struct Barrier; + +// TODO: header +struct Mobj : mobj_t +{ + struct PosArg + { + fixed_t x, y, z; + + PosArg(fixed_t x_, fixed_t y_, fixed_t z_) : x(x_), y(y_), z(z_) {} + PosArg(const mobj_t* mobj) : x(mobj->x), y(mobj->y), z(mobj->z) {} + }; + + bool valid() const { return !P_MobjWasRemoved(this); } + + PosArg center() const { return {x, y, z + (height / 2)}; } + + template + T* spawn_offset(mobjtype_t type) { return static_cast(P_SpawnMobjFromMobj(this, 0, 0, 0, type)); } + + void state(statenum_t state) { P_SetMobjState(this, state); } + statenum_t statenum() const { return static_cast(mobj_t::state - states); } + + fixed_t scale() const { return mobj_t::scale; } + + void scale(fixed_t n) + { + mobj_t::scale = n; + mobj_t::destscale = n; + } + + void move_origin(const PosArg& p) { P_MoveOrigin(this, p.x, p.y, p.z); } + + void remove() { P_RemoveMobj(this); } +}; + +struct Player : player_t +{ + struct Powerups : powerupvars_t + { + Barrier* barrier() const { return reinterpret_cast(powerupvars_t::barrier); } + void barrier(Barrier* n) { P_SetTarget(&this->powerupvars_t::barrier, reinterpret_cast(n)); } + }; + + static Player* at(std::size_t i) { return static_cast(&players[i]); } + + bool valid() const { return this >= players && this < &players[MAXPLAYERS] && playeringame[num()]; } + + std::size_t num() const { return this - Player::at(0); } + Mobj* mobj() const { return static_cast(mo); } + + Powerups& powerups() { return static_cast(player_t::powerup); } + const Powerups& powerups() const { return static_cast(player_t::powerup); } +}; + +struct Barrier : Mobj +{ + static constexpr angle_t kSpinSpeed = ANGLE_22h; + static constexpr angle_t kSpinGap = 20*ANG1; + + static Barrier* spawn(Player* player, statenum_t state, int idx) + { + Barrier* child = player->mobj()->spawn_offset(MT_MEGABARRIER); + + child->angle = player->mobj()->angle + (idx * kSpinGap); + child->player(player); + child->renderflags |= RF_DONTDRAW; + child->state(state); + + return child; + } + + static void spawn_chain(Player* player) + { + player->powerups().barrier(spawn(player, S_MEGABARRIER1, 0)); + spawn(player, S_MEGABARRIER2, 1); + spawn(player, S_MEGABARRIER2, 2); + spawn(player, S_MEGABARRIER2, 3); + spawn(player, S_MEGABARRIER2, 4); + spawn(player, S_MEGABARRIER3, 5); + } + + int playernum() const { return barrier_player(this); } + Player* player() const { return Player::at(playernum()); } + void player(player_t* n) { barrier_player(this) = n - players; } + + bool valid() const { return Mobj::valid() && player()->valid() && player()->mobj()->valid(); } + + bool think() + { + if (!valid() || !K_PowerUpRemaining(player(), POWERUP_BARRIER)) + { + remove(); + return false; + } + + Mobj* source = player()->mobj(); + color = source->color; + scale(8 * source->scale() / 9); + move_origin(source->center()); + angle += kSpinSpeed; + eflags = (eflags & ~MFE_VERTICALFLIP) | (source->eflags & MFE_VERTICALFLIP); + + if (K_PlayerGuard(player())) + { + renderflags &= ~RF_DONTDRAW; + renderflags ^= RF_ADD | RF_TRANS90; + } + else + { + renderflags |= RF_DONTDRAW; + } + + return true; + } +}; + +}; // namespace + +void Obj_SpawnMegaBarrier(player_t* p) +{ + Player* player = static_cast(p); + + if (!static_cast(player->powerups().barrier())->valid()) + { + Barrier::spawn_chain(player); + } +} + +boolean Obj_MegaBarrierThink(mobj_t* mobj) +{ + return static_cast(mobj)->think(); +} diff --git a/src/objects/powerup-aura.cpp b/src/objects/powerup-aura.cpp index 80620010d..6bfa47096 100644 --- a/src/objects/powerup-aura.cpp +++ b/src/objects/powerup-aura.cpp @@ -1,3 +1,4 @@ +#include "../doomtype.h" #include "../info.h" #include "../g_game.h" #include "../m_fixed.h" @@ -85,6 +86,15 @@ struct Aura : mobj_t P_InstaScale(this, 11 * origin()->scale / 10); translate(); + + if (K_AnyPowerUpRemaining(&players[seek()]) & ~POWERUP_BIT(POWERUP_BARRIER)) + { + renderflags &= ~RF_DONTDRAW; + } + else + { + renderflags |= RF_DONTDRAW; + } } void translate() diff --git a/src/p_mobj.c b/src/p_mobj.c index d6eedf380..4dad2d393 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6807,6 +6807,14 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->angle += ANG2; break; } + case MT_MEGABARRIER: + { + if (!Obj_MegaBarrierThink(mobj)) + { + return; + } + break; + } case MT_VWREF: case MT_VWREB: { diff --git a/src/p_saveg.c b/src/p_saveg.c index 6fa7a9363..6f1450773 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -82,6 +82,7 @@ typedef enum FLICKYATTACKER = 0x0800, FLICKYCONTROLLER = 0x1000, TRICKINDICATOR = 0x2000, + BARRIER = 0x4000, } player_saveflags; static inline void P_ArchivePlayer(savebuffer_t *save) @@ -332,6 +333,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (players[i].powerup.flickyController) flags |= FLICKYCONTROLLER; + if (players[i].powerup.barrier) + flags |= BARRIER; + WRITEUINT16(save->p, flags); if (flags & SKYBOXVIEW) @@ -373,6 +377,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (flags & FLICKYCONTROLLER) WRITEUINT32(save->p, players[i].powerup.flickyController->mobjnum); + if (flags & BARRIER) + WRITEUINT32(save->p, players[i].powerup.barrier->mobjnum); + WRITEUINT32(save->p, (UINT32)players[i].followitem); WRITEUINT32(save->p, players[i].charflags); @@ -911,6 +918,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) if (flags & FLICKYCONTROLLER) players[i].powerup.flickyController = (mobj_t *)(size_t)READUINT32(save->p); + if (flags & BARRIER) + players[i].powerup.barrier = (mobj_t *)(size_t)READUINT32(save->p); + players[i].followitem = (mobjtype_t)READUINT32(save->p); //SetPlayerSkinByNum(i, players[i].skin); @@ -5743,6 +5753,13 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&players[i].powerup.flickyController, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "powerup.flickyController not found on player %d\n", i); } + if (players[i].powerup.barrier) + { + temp = (UINT32)(size_t)players[i].powerup.barrier; + players[i].powerup.barrier = NULL; + if (!P_SetTarget(&players[i].powerup.barrier, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "powerup.barrier not found on player %d\n", i); + } } } diff --git a/src/p_user.c b/src/p_user.c index fbb03d497..920d4f365 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4069,6 +4069,7 @@ void P_PlayerThink(player_t *player) PlayerPointerErase(player->hoverhyudoro); PlayerPointerErase(player->flickyAttacker); PlayerPointerErase(player->powerup.flickyController); + PlayerPointerErase(player->powerup.barrier); #undef PlayerPointerErase }