diff --git a/src/k_objects.h b/src/k_objects.h index 803a0caa9..215b4b84f 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -328,6 +328,10 @@ boolean Obj_TryCrateThink(mobj_t *mo); void Obj_TryCrateTouch(mobj_t *special, mobj_t *toucher); void Obj_TryCrateDamage(mobj_t *target, mobj_t *inflictor); +/* Lavender Shrine Spears */ +void Obj_SpearInit(mobj_t *mo); +void Obj_SpearThink(mobj_t *mo); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 19cb75116..b7dda6caf 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -45,6 +45,7 @@ target_sources(SRB2SDL2 PRIVATE frost-thrower.cpp ivoball.cpp crate.cpp + spear.cpp ) add_subdirectory(versus) diff --git a/src/objects/spear.cpp b/src/objects/spear.cpp new file mode 100644 index 000000000..4998c1cdc --- /dev/null +++ b/src/objects/spear.cpp @@ -0,0 +1,179 @@ +// 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 Lat +// Hardcoded by jartha + +#include + +#include "../math/fixed.hpp" +#include "../math/vec.hpp" +#include "../mobj.hpp" +#include "../mobj_list_view.hpp" + +#include "../doomstat.h" +#include "../doomtype.h" +#include "../info.h" +#include "../k_objects.h" +#include "../tables.h" + +using srb2::math::Fixed; +using srb2::math::Vec2; +using srb2::Mobj; +using srb2::MobjListView; + +namespace +{ + +Vec2 angle_vector(angle_t x) +{ + return Vec2 {FCOS(x), FSIN(x)}; +} + +struct Spear : Mobj +{ + enum Mode + { + kWait, + kShake, + kPush, + kPull, + kNumModes, + }; + + static constexpr tic_t kWaitTimes[kNumModes] = { + TICRATE, + TICRATE/2, + TICRATE/2, + TICRATE, + }; + + void extravalue1() = delete; + int mode() const { return mobj_t::extravalue1; } + void mode(int n) + { + mobj_t::extravalue1 = n; + timer(kWaitTimes[n]); + } + + void extravalue2() = delete; + tic_t timer() const { return mobj_t::extravalue2; } + void timer(tic_t n) { mobj_t::extravalue2 = n; } + + void threshold() = delete; + Fixed dist() const { return mobj_t::threshold; } + void dist(Fixed n) { mobj_t::threshold = n; } + + void thing_args() = delete; + bool delayed_start() const { return mobj_t::thing_args[0]; } + + void init() + { + mode(kWait); + + if (delayed_start()) + { + timer(timer() + TICRATE*3/2); + } + + auto piece = [&](statenum_t state) + { + Mobj* vis = spawn_from({}, MT_SPEARVISUAL); + vis->state(state); + return vis; + }; + + Vec2 v = angle_vector(angle) * scale(); + auto divider = [&](statenum_t state, int offset) + { + Mobj* vis = piece(state); + vis->angle = angle - ANGLE_90; + vis->sproff2d(v * offset); + return vis; + }; + + Mobj* head = this; + auto link = [&](Mobj* vis) + { + vis->punt_ref(this); + head->hnext(vis); + head = vis; + return vis; + }; + + Mobj* wall = divider(S_SPEAR_WALL, 0); // never moves + + set_origin({pos2d() + (angle_vector(angle) * Fixed {radius}), z}); + + link(divider(S_SPEAR_HILT_BACK, 26)); + Mobj* front = link(divider(S_SPEAR_HILT_FRONT, 34)); + + Mobj* tip = piece(S_SPEAR_TIP); + tip->angle = angle; + link(tip); + + // Whether you use a positive or negative offset + // depends on how the sprite would originally be + // sorted... + this->linkdraw(wall, -5); // this sorts the rod behind the wall plate + tip->linkdraw(front, -5); // this sorts the tip IN FRONT of the rod + } + + void think() + { + Vec2 p = pos2d() - vector(); + dist(new_dist()); + Mobj::PosArg mpos{p + vector(), z}; + + move_origin(mpos); + for (Mobj* vis : MobjListView(hnext(), [](Mobj* vis) { return vis->hnext(); })) + { + vis->move_origin(mpos); + } + + timer(timer() - 1); + if (!timer()) + { + mode((mode() + 1) % kNumModes); + } + } + +private: + Fixed new_dist() const + { + static constexpr int kMinDist = -96; + static constexpr int kMaxDist = 0; + + switch (mode()) + { + default: + return kMinDist * scale(); + case kShake: + return (leveltime & 1 ? kMinDist : kMinDist + 4) * scale(); + case kPush: + return std::min(scale() * kMaxDist, dist() + (16 * scale())); + case kPull: + return std::max(scale() * kMinDist, dist() - (4 * scale())); + } + } + + Vec2 vector() const { return angle_vector(angle) * dist(); } +}; + +}; // namespace + +void Obj_SpearInit(mobj_t* mobj) +{ + static_cast(mobj)->init(); +} + +void Obj_SpearThink(mobj_t* mobj) +{ + static_cast(mobj)->think(); +} diff --git a/src/p_mobj.c b/src/p_mobj.c index 50bb15ac8..a21d676cb 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6850,6 +6850,15 @@ static void P_MobjSceneryThink(mobj_t *mobj) Obj_BoxSideThink(mobj); return; } + case MT_SPEAR: + { + Obj_SpearThink(mobj); + return; + } + case MT_SPEARVISUAL: + { + return; + } case MT_VWREF: case MT_VWREB: { @@ -11020,6 +11029,8 @@ fixed_t P_GetMobjDefaultScale(mobj_t *mobj) case MT_HANAGUMIHALL_STEAM: case MT_HANAGUMIHALL_NPC: return 2*FRACUNIT; + case MT_SPEAR: + return 2*FRACUNIT; default: break; } @@ -14496,6 +14507,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) Obj_TryCrateInit(mobj); break; } + case MT_SPEAR: + { + Obj_SpearInit(mobj); + break; + } default: break; }