// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2023 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. //----------------------------------------------------------------------------- // Original Lua script by Sal // Hardcoded by jartha #include #include #include "../math/fixed.hpp" #include "../math/vec.hpp" #include "../mobj.hpp" #include "../mobj_list.hpp" #include "../d_player.h" #include "../doomstat.h" #include "../info.h" #include "../k_objects.h" #include "../m_fixed.h" #include "../p_pspr.h" #include "../sounds.h" #include "../tables.h" using srb2::Mobj; using srb2::MobjList; using srb2::math::Fixed; using srb2::math::Vec2; extern mobj_t* svg_rocks; namespace { struct AngelIsland { static constexpr statenum_t kRespawnState = S_AZROCKS_RESPAWN; static constexpr mobjtype_t kParticleType = MT_AZROCKS_PARTICLE; static constexpr std::array kParticleStates = {S_AZROCKS_PARTICLE1, S_AZROCKS_PARTICLE1}; }; struct EndlessMine { static constexpr statenum_t kRespawnState = S_EMROCKS_RESPAWN; static constexpr mobjtype_t kParticleType = MT_EMROCKS_PARTICLE; static constexpr std::array kParticleStates = {S_EMROCKS_PARTICLE1, S_EMROCKS_PARTICLE2}; }; struct AnyRocks : Mobj { void hnext() = delete; AnyRocks* next() const { return Mobj::hnext(); } void next(AnyRocks* n) { Mobj::hnext(n); } template bool visit(F&& visitor); }; template struct Rocks : AnyRocks { bool busted() const { return state()->num() == Config::kRespawnState; } void respawn() { if (busted()) { tics = 2; // respawn soon } } void touch(Mobj* toucher) { if (busted()) { if (tics > 0) { tics = 2; // postpone respawn } } else { slow(toucher); bust(toucher); } } private: static void slow(Mobj* toucher) { const player_t* p = toucher->player; if (p->sneakertimer || p->invincibilitytimer || p->growshrinktimer > 0 || p->hyudorotimer) { return; } toucher->momx /= 2; toucher->momy /= 2; toucher->momz = toucher->flip(std::abs(2 * toucher->momz / 3)); } void bust(Mobj* toucher) { vfx(toucher, 1); vfx(toucher, 2); vfx(toucher, 3); voice(info->deathsound); state(Config::kRespawnState); } void vfx(Mobj* toucher, int i) { fixed_t zvar = flip((i + 1) * 4 * mapobjectscale); angle_t avar = ANGLE_45 * (i - 2); auto part = [&](angle_t angle, statenum_t state) { Mobj* h = spawn_from({Vec2 {}, zvar}, Config::kParticleType); h->state(state); h->angle = angle; h->instathrust(angle, 4 * mapobjectscale); h->momz = zvar; return h; }; static_assert(Config::kParticleStates.size() == 2); part(toucher->angle + ANGLE_90 - avar, Config::kParticleStates[0]); part(toucher->angle - ANGLE_90 + avar, Config::kParticleStates[1]); } }; struct AngelIslandRocks : Rocks { }; struct EndlessMineRocks : Rocks { }; template bool AnyRocks::visit(F&& visitor) { switch (type) { case MT_AZROCKS: visitor(static_cast(this)); break; case MT_EMROCKS: visitor(static_cast(this)); break; default: return false; } return true; } MobjList rocks_list; }; // namespace void Obj_LinkRocks(mobj_t* mo) { rocks_list.push_front(static_cast(mo)); } void Obj_UnlinkRocks(mobj_t* mo) { rocks_list.erase(static_cast(mo)); } void Obj_TouchRocks(mobj_t* special, mobj_t* toucher) { static_cast(special)->visit([&](auto rocks) { rocks->touch(static_cast(toucher)); }); } void Obj_UpdateRocks(void) { for (AnyRocks* h : rocks_list) { h->visit([](auto rocks) { rocks->respawn(); }); } } void Obj_AnimateEndlessMineRocks(mobj_t *mo) { // sync colors with sky animation constexpr int kFrames = 8; constexpr int kDiff = kFrames - 2; constexpr int kTotal = kFrames + kDiff; UINT8 f = ((leveltime / 6) % kTotal); if (f >= kFrames) { f = kTotal - f; } mo->frame = (mo->frame & ~FF_FRAMEMASK) | f; }