diff --git a/src/deh_tables.c b/src/deh_tables.c index 8d767ddf1..45bbd6955 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4831,6 +4831,19 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi // MT_IVOBALL "S_IVOBALL", + + "S_SA2_CRATE_DEBRIS", + "S_SA2_CRATE_DEBRIS_E", + "S_SA2_CRATE_DEBRIS_F", + "S_SA2_CRATE_DEBRIS_G", + "S_SA2_CRATE_DEBRIS_H", + "S_SA2_CRATE_DEBRIS_METAL", + + "S_ICECAPBLOCK_DEBRIS", + "S_ICECAPBLOCK_DEBRIS_C", + "S_ICECAPBLOCK_DEBRIS_D", + "S_ICECAPBLOCK_DEBRIS_E", + "S_ICECAPBLOCK_DEBRIS_F", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -6057,6 +6070,11 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_IVOBALL", "MT_PATROLIVOBALL", "MT_AIRIVOBALL", + + "MT_BOX_SIDE", + "MT_BOX_DEBRIS", + "MT_SA2_CRATE", + "MT_ICECAPBLOCK", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index e66a1e06b..26561f4eb 100644 --- a/src/info.c +++ b/src/info.c @@ -986,6 +986,9 @@ char sprnames[NUMSPRITES + 1][5] = "FBTN", "SFTR", + "SABX", + "ICBL", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5677,6 +5680,19 @@ state_t states[NUMSTATES] = // MT_IVOBALL {SPR_BSPH, 2|FF_SEMIBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_IVOBALL + + {SPR_UNKN, FF_FULLBRIGHT, -1, {A_RandomStateRange}, S_SA2_CRATE_DEBRIS_E, S_SA2_CRATE_DEBRIS_H, S_NULL}, // S_SA2_CRATE_DEBRIS + {SPR_SABX, 4, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_E + {SPR_SABX, 5, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_F + {SPR_SABX, 6, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_G + {SPR_SABX, 7, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_H + {SPR_SABX, 12, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_METAL + + {SPR_UNKN, FF_FULLBRIGHT, -1, {A_RandomStateRange}, S_ICECAPBLOCK_DEBRIS_C, S_ICECAPBLOCK_DEBRIS_F, S_NULL}, // S_ICECAPBLOCK_DEBRIS + {SPR_ICBL, 2, 70, {NULL}, 0, 0, S_NULL}, // S_ICECAPBLOCK_DEBRIS_C + {SPR_ICBL, 3, 70, {NULL}, 0, 0, S_NULL}, // S_ICECAPBLOCK_DEBRIS_D + {SPR_ICBL, 4, 70, {NULL}, 0, 0, S_NULL}, // S_ICECAPBLOCK_DEBRIS_E + {SPR_ICBL, 5, 70, {NULL}, 0, 0, S_NULL}, // S_ICECAPBLOCK_DEBRIS_F }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -32200,6 +32216,110 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags S_NULL // raisestate }, + { // MT_BOX_SIDE + -1, // doomednum + S_INVISIBLE, // 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 + 40*FRACUNIT, // radius + 80*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOCLIPTHING, // flags + S_NULL // raisestate + }, + { // MT_BOX_DEBRIS + -1, // doomednum + S_INVISIBLE, // 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*FRACUNIT, // radius + 0*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOCLIPTHING, // flags + S_NULL // raisestate + }, + { // MT_SA2_CRATE + 2529, // doomednum + S_INVISIBLE, // spawnstate + 1, // 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 + 40*FRACUNIT, // radius + 80*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_SOLID|MF_SHOOTABLE|MF_SCENERY|MF_DONTPUNT, // flags + S_NULL // raisestate + }, + { // MT_ICECAPBLOCK + 3750, // doomednum + S_INVISIBLE, // spawnstate + 1, // 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 + 40*FRACUNIT, // radius + 80*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_SOLID|MF_SHOOTABLE|MF_SCENERY|MF_DONTPUNT, // flags + S_NULL // raisestate + }, }; diff --git a/src/info.h b/src/info.h index 9e6d02d8d..c623dcbca 100644 --- a/src/info.h +++ b/src/info.h @@ -1540,6 +1540,9 @@ typedef enum sprite SPR_FBTN, SPR_SFTR, + SPR_SABX, + SPR_ICBL, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -6102,6 +6105,19 @@ typedef enum state // MT_IVOBALL S_IVOBALL, + S_SA2_CRATE_DEBRIS, + S_SA2_CRATE_DEBRIS_E, + S_SA2_CRATE_DEBRIS_F, + S_SA2_CRATE_DEBRIS_G, + S_SA2_CRATE_DEBRIS_H, + S_SA2_CRATE_DEBRIS_METAL, + + S_ICECAPBLOCK_DEBRIS, + S_ICECAPBLOCK_DEBRIS_C, + S_ICECAPBLOCK_DEBRIS_D, + S_ICECAPBLOCK_DEBRIS_E, + S_ICECAPBLOCK_DEBRIS_F, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -7348,6 +7364,11 @@ typedef enum mobj_type MT_PATROLIVOBALL, MT_AIRIVOBALL, + MT_BOX_SIDE, + MT_BOX_DEBRIS, + MT_SA2_CRATE, + MT_ICECAPBLOCK, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_objects.h b/src/k_objects.h index 51e7eae96..803a0caa9 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -321,6 +321,13 @@ void Obj_PatrolIvoBallInit(mobj_t *mo); void Obj_PatrolIvoBallThink(mobj_t *mo); void Obj_PatrolIvoBallTouch(mobj_t *special, mobj_t *toucher); +/* SA2 Crates / Ice Cap Blocks */ +void Obj_BoxSideThink(mobj_t *mo); +void Obj_TryCrateInit(mobj_t *mo); +boolean Obj_TryCrateThink(mobj_t *mo); +void Obj_TryCrateTouch(mobj_t *special, mobj_t *toucher); +void Obj_TryCrateDamage(mobj_t *target, mobj_t *inflictor); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/math/vec.hpp b/src/math/vec.hpp index 670a3677c..935725b3c 100644 --- a/src/math/vec.hpp +++ b/src/math/vec.hpp @@ -22,9 +22,9 @@ struct Vec2 { T x, y; - Vec2() : x{}, y{} {} - Vec2(T x_, T y_) : x(x_), y(y_) {} - Vec2(T z) : x(z), y(z) {} + constexpr Vec2() : x{}, y{} {} + constexpr Vec2(T x_, T y_) : x(x_), y(y_) {} + constexpr Vec2(T z) : x(z), y(z) {} template Vec2(const Vec2& b) : Vec2(b.x, b.y) {} diff --git a/src/mobj.hpp b/src/mobj.hpp index fc78f480a..da4973606 100644 --- a/src/mobj.hpp +++ b/src/mobj.hpp @@ -13,8 +13,12 @@ #include #include "math/fixed.hpp" +#include "math/line_segment.hpp" #include "math/vec.hpp" +#include "doomtype.h" +#include "k_hitlag.h" +#include "k_kart.h" #include "info.h" #include "p_local.h" #include "p_mobj.h" @@ -27,16 +31,20 @@ namespace srb2 struct Mobj : mobj_t { + using fixed = math::Fixed; + using line_segment = math::LineSegment; + using vec2 = math::Vec2; + // TODO: Vec3 would be nice struct PosArg { - math::Fixed x, y, z; + fixed x, y, z; PosArg() : PosArg(0, 0, 0) {} - PosArg(fixed_t x_, fixed_t y_, fixed_t z_) : x(x_), y(y_), z(z_) {} + PosArg(fixed x_, fixed y_, fixed z_) : x(x_), y(y_), z(z_) {} template - PosArg(math::Vec2 p, fixed_t z) : PosArg(p.x, p.y, z) {} + PosArg(math::Vec2 p, fixed z) : PosArg(p.x, p.y, z) {} PosArg(const mobj_t* mobj) : PosArg(mobj->x, mobj->y, mobj->z) {} }; @@ -97,19 +105,26 @@ struct Mobj : mobj_t PosArg center() const { return {x, y, z + (height / 2)}; } PosArg pos() const { return {x, y, z}; } - math::Vec2 pos2d() const { return {x, y}; } - math::Fixed top() const { return z + height; } + vec2 pos2d() const { return {x, y}; } + fixed top() const { return z + height; } bool is_flipped() const { return eflags & MFE_VERTICALFLIP; } - math::Fixed flip(fixed_t x) const { return x * P_MobjFlip(this); } + fixed flip(fixed x) const { return x * P_MobjFlip(this); } // Collision helper bool z_overlaps(const Mobj* b) const { return z < b->top() && b->z < top(); } void move_origin(const PosArg& p) { P_MoveOrigin(this, p.x, p.y, p.z); } void set_origin(const PosArg& p) { P_SetOrigin(this, p.x, p.y, p.z); } - void instathrust(angle_t angle, fixed_t speed) { P_InstaThrust(this, angle, speed); } - void thrust(angle_t angle, fixed_t speed) { P_Thrust(this, angle, speed); } + void instathrust(angle_t angle, fixed speed) { P_InstaThrust(this, angle, speed); } + void thrust(angle_t angle, fixed speed) { P_Thrust(this, angle, speed); } + + static void bounce(Mobj* t1, Mobj* t2) { K_KartBouncing(t1, t2); } + void solid_bounce(Mobj* solid) { K_KartSolidBounce(this, solid); } + + // A = bottom left corner + // this->aabb; the standard bounding box. This is inapproporiate for paper collision! + line_segment aabb() const { return {{x - radius, y - radius}, {x + radius, y + radius}}; } // @@ -150,15 +165,15 @@ struct Mobj : mobj_t // Scale // - math::Fixed scale() const { return mobj_t::scale; } + fixed scale() const { return mobj_t::scale; } - void scale(fixed_t n) + void scale(fixed n) { - mobj_t::scale = n; + P_SetScale(this, n); mobj_t::destscale = n; } - void scale_to(fixed_t stop, std::optional speed = {}) + void scale_to(fixed stop, std::optional speed = {}) { mobj_t::destscale = stop; @@ -168,13 +183,53 @@ struct Mobj : mobj_t } } - void scale_between(fixed_t start, fixed_t stop, std::optional speed = {}) + void scale_between(fixed start, fixed stop, std::optional speed = {}) { - mobj_t::scale = start; + P_SetScale(this, start); scale_to(stop, speed); } + // + // Sprite offsets + // + +#define FIXED_METHOD(member) \ + fixed member() const { return mobj_t::member; } \ + void member(fixed n) { mobj_t::member = n; } + + FIXED_METHOD(spritexscale) + FIXED_METHOD(spriteyscale) + FIXED_METHOD(spritexoffset) + FIXED_METHOD(spriteyoffset) + FIXED_METHOD(sprxoff) + FIXED_METHOD(spryoff) + FIXED_METHOD(sprzoff) + + vec2 spritescale() const { return {spritexscale(), spriteyscale()}; } + void spritescale(const vec2& v) + { + spritexscale(v.x); + spriteyscale(v.y); + } + + vec2 spriteoffset() const { return {spritexoffset(), spriteyoffset()}; } + void spriteoffset(const vec2& v) + { + spritexoffset(v.x); + spriteyoffset(v.y); + } + + vec2 sproff2d() const { return {sprxoff(), spryoff()}; } + void sproff2d(const vec2& v) + { + sprxoff(v.x); + spryoff(v.y); + } + + // TODO: Vec3 + + // // Sound // @@ -194,6 +249,18 @@ struct Mobj : mobj_t voice(sfx, volume); } } + + + // + // Hitlag + // + + INT32 hitlag() const { return mobj_t::hitlag; } + void hitlag(INT32 tics, bool damage = false) { K_AddHitLag(this, tics, damage); } + void hitlag(Mobj* inflictor, Mobj* source, INT32 tics, bool damage) + { + K_SetHitLagForObjects(this, inflictor, source, tics, damage); + } }; }; // namespace srb2 diff --git a/src/mobj_list_view.hpp b/src/mobj_list_view.hpp new file mode 100644 index 000000000..638e7d952 --- /dev/null +++ b/src/mobj_list_view.hpp @@ -0,0 +1,74 @@ +// 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. +//----------------------------------------------------------------------------- + +#ifndef mobj_list_view_hpp +#define mobj_list_view_hpp + +#include +#include +#include + +#include "mobj.hpp" + +namespace srb2 +{ + +// for (T* ptr : MobjList(hnext(), [](T* ptr) { return ptr->hnext(); })) +template , bool> = true> +struct MobjListView +{ + struct Iterator + { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = T*; + using pointer = value_type; + using reference = value_type; + + Iterator(pointer ptr, F adv) : ptr_(deref(ptr)), adv_(adv) {} + + bool operator==(const Iterator& b) const { return ptr_ == b.ptr_; }; + bool operator!=(const Iterator& b) const { return ptr_ != b.ptr_; }; + + reference operator*() const { return ptr_; } + pointer operator->() { return &ptr_; } + + Iterator& operator++() + { + ptr_ = deref(adv_(ptr_)); + return *this; + } + + Iterator operator++(int) + { + Iterator prev = *this; + ++(*this); + return prev; + } + + private: + pointer ptr_; + F adv_; + + static T* deref(T* ptr) { return ptr && ptr->valid() ? ptr : nullptr; } + }; + + MobjListView(T* ptr, F adv) : ptr_(ptr), adv_(adv) {} + + Iterator begin() const { return {ptr_, adv_}; } + Iterator end() const { return {nullptr, adv_}; } + +private: + T* ptr_; + F adv_; +}; + +}; // namespace srb2 + +#endif/*mobj_list_view_hpp*/ diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 6a420e1cf..19cb75116 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -44,6 +44,7 @@ target_sources(SRB2SDL2 PRIVATE mega-barrier.cpp frost-thrower.cpp ivoball.cpp + crate.cpp ) add_subdirectory(versus) diff --git a/src/objects/crate.cpp b/src/objects/crate.cpp new file mode 100644 index 000000000..dafa06b72 --- /dev/null +++ b/src/objects/crate.cpp @@ -0,0 +1,391 @@ +// 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 +#include + +#include "../math/fixed.hpp" +#include "../math/line_segment.hpp" +#include "../math/vec.hpp" +#include "../mobj.hpp" +#include "../mobj_list_view.hpp" + +#include "../d_player.h" +#include "../doomtype.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../p_maputl.h" +#include "../p_pspr.h" +#include "../r_defs.h" + +using srb2::math::Fixed; +using srb2::math::LineSegment; +using srb2::math::Vec2; +using srb2::Mobj; +using srb2::MobjListView; + +namespace +{ + +using frame_layout = std::array; // -z, +z, -x, -y, +x, +y + +struct SA2CrateConfig +{ + static constexpr spritenum_t kSprite = SPR_SABX; + static constexpr frame_layout kFrames = {3, 2, 0, 0, 0, 0}; + static constexpr statenum_t kDefaultDebris = S_SA2_CRATE_DEBRIS; +}; + +struct IceCapBlockConfig +{ + static constexpr spritenum_t kSprite = SPR_ICBL; + static constexpr frame_layout kFrames = {6, 6, 0, 0, 0, 0}; + static constexpr statenum_t kDefaultDebris = S_ICECAPBLOCK_DEBRIS; +}; + +struct Graphic : Mobj +{ + void hnext() = delete; + Graphic* next() const { return Mobj::hnext(); } + void next(Graphic* n) { Mobj::hnext(n); } + + Graphic* dress(spritenum_t sprite, UINT32 frame) + { + this->sprite = sprite; + this->frame = frame; + this->renderflags |= RF_NOSPLATBILLBOARD; + return this; + } + + Graphic* xy(fixed_t x, fixed_t y) + { + this->sproff2d({x, y}); + return this; + } + + Graphic* z(fixed_t z) + { + this->sprzoff(z); + return this; + } + + Graphic* turn(angle_t angle) + { + this->angle = angle; + return this; + } +}; + +struct Side : Graphic +{ + bool valid() const { return Mobj::valid() && Mobj::valid(owner()); } + + void think() + { + if (!valid()) + { + remove(); + return; + } + + move_origin(owner()); + renderflags = owner()->renderflags; + } +}; + +struct Toucher : Mobj +{ + bool boosting() const { return player && (player->sneakertimer || K_PlayerCanPunt(player)); } +}; + +struct AnyBox : Graphic +{ + template + bool visit(F&& visitor); + + void update() + { + visit([](auto box) { box->mobj_t::eflags &= ~MFE_ONGROUND; }); + } +}; + +template +struct Box : AnyBox +{ + static constexpr Fixed kIntendedSize = 128*FRACUNIT; + static constexpr Vec2 kScrunch = {4*FRACUNIT/5, 6*FRACUNIT/5}; + + void extravalue1() = delete; + statenum_t debris_state() const { return static_cast(mobj_t::extravalue1); } + void debris_state(statenum_t n) { mobj_t::extravalue1 = n; } + + auto gfx() { return MobjListView(static_cast(this), [](Graphic* g) { return g->next(); }); } + + void init() + { + scale(scale() * (kIntendedSize / Fixed {info->height})); + + Graphic* node = this; + int i = 0; + auto dress = [&](Graphic* g, UINT32 ff) { return g->dress(Config::kSprite, Config::kFrames[i++] | ff); }; + auto side = [&](UINT32 ff) + { + Side* side = spawn_from({}, MT_BOX_SIDE); + side->owner(this); + node->next(side); // link + node = side; + return dress(side, ff); + }; + + dress(this, FF_FLOORSPRITE); // bottom (me) + side(FF_FLOORSPRITE)->z(height); // top + + // sides + side(FF_PAPERSPRITE)->xy(-radius, 0)->turn(ANGLE_270); + side(FF_PAPERSPRITE)->xy(0, -radius); + side(FF_PAPERSPRITE)->xy(+radius, 0)->turn(ANGLE_90); + side(FF_PAPERSPRITE)->xy(0, +radius)->turn(ANGLE_180); + + debris_state(Config::kDefaultDebris); + } + + bool think() + { + if (fuse) + { + fuse--; + renderflags ^= RF_DONTDRAW; + + if (!fuse) + { + update_nearby(); + remove(); + return false; + } + } + + return true; + } + + void touch(Toucher* toucher) + { + if (fuse) + { + return; + } + + P_DamageMobj(this, toucher, nullptr, 1, DMG_NORMAL); + + if (!toucher->boosting()) + { + toucher->solid_bounce(this); + } + } + + bool damage_valid(const Mobj* inflictor) const { return !fuse && Mobj::valid(inflictor); } + + void damage(Mobj* inflictor) + { + if (!damage_valid(inflictor)) + { + return; + } + + inflictor->hitlag(3); + fuse = 10; + + // scrunch crate sides + for (Graphic* g : gfx()) + { + if (g->frame & FF_PAPERSPRITE) + { + g->frame++; + g->spritescale(kScrunch); + } + else + { + g->spritescale(kScrunch.x); + } + + // reset interp + g->mobj_t::old_spritexscale = g->spritexscale(); + g->mobj_t::old_spriteyscale = g->spriteyscale(); + + g->sproff2d(g->sproff2d() * kScrunch.x); + g->sprzoff(g->sprzoff() * kScrunch.y); + } + + debris(inflictor); + update_nearby(); + } + +private: + void debris(Mobj* inflictor) + { + if (debris_state() >= NUMSTATES) + { + return; + } + + auto rng = [&](int x, int y) { return P_RandomRange(PR_DECORATION, x, y) * scale(); }; + auto rng_xyz = [&](int x) { return std::tuple(rng(-x, x), rng(-x, x), rng(0, x)); }; + + auto spawn = [&] + { + auto [x, y, z] = rng_xyz(info->height / FRACUNIT); + Mobj* p = spawn_from({x, y, z}, MT_BOX_DEBRIS); + + p->scale_between(scale() / 2, scale()); + p->state(debris_state()); + + std::tie(x, y, z) = rng_xyz(4); + + p->momx = (inflictor->momx / 8) + x; + p->momy = (inflictor->momy / 8) + y; + p->momz = (Fixed::hypot(inflictor->momx, inflictor->momy) / 4) + z; + }; + + spawn(); + spawn(); + spawn(); + spawn(); + spawn(); + spawn(); + } + + void update_nearby() const + { + LineSegment search = aabb(); + Vec2 org{bmaporgx, bmaporgy}; + + search.a -= org + MAXRADIUS; + search.b -= org - MAXRADIUS; + + search.a.x = static_cast(search.a.x) >> MAPBLOCKSHIFT; + search.b.x = static_cast(search.b.x) >> MAPBLOCKSHIFT; + search.a.y = static_cast(search.a.y) >> MAPBLOCKSHIFT; + search.b.y = static_cast(search.b.y) >> MAPBLOCKSHIFT; + + BMBOUNDFIX(search.a.x, search.b.x, search.b.x, search.b.y); + + for (INT32 bx = search.a.x; bx <= search.b.x; ++bx) + { + for (INT32 by = search.a.y; by <= search.b.y; ++by) + { + P_BlockThingsIterator( + bx, + by, + [](mobj_t* thing) + { + static_cast(thing)->update(); + return BMIT_CONTINUE; + } + ); + } + } + } +}; + +struct Crate : Box +{ + static constexpr int kMetalFrameStart = 8; + + void thing_args() = delete; + bool metal() const { return mobj_t::thing_args[0]; } + + void init() + { + Box::init(); + + if (metal()) + { + for (Graphic* g : gfx()) + { + g->frame += kMetalFrameStart; + } + + debris_state(S_SA2_CRATE_DEBRIS_METAL); + } + } + + void damage(Toucher* inflictor) + { + if (!Box::damage_valid(inflictor)) + { + return; + } + + if (metal() && !inflictor->boosting()) + { + return; + } + + Box::damage(inflictor); + } +}; + +struct Ice : Box +{ +}; + +template +bool AnyBox::visit(F&& visitor) +{ + switch (type) + { + case MT_SA2_CRATE: + visitor(static_cast(this)); + break; + + case MT_ICECAPBLOCK: + visitor(static_cast(this)); + break; + + default: + return false; + } + + return true; +} + +}; // namespace + +void Obj_BoxSideThink(mobj_t* mobj) +{ + static_cast(mobj)->think(); +} + +void Obj_TryCrateInit(mobj_t* mobj) +{ + static_cast(mobj)->visit([&](auto box) { box->init(); }); +} + +boolean Obj_TryCrateThink(mobj_t* mobj) +{ + bool c = false; + static_cast(mobj)->visit([&](auto box) { c = box->think(); }); + return c; +} + +void Obj_TryCrateTouch(mobj_t* special, mobj_t* toucher) +{ + static_cast(special)->visit([&](auto box) { box->touch(static_cast(toucher)); }); +} + +void Obj_TryCrateDamage(mobj_t* target, mobj_t* inflictor) +{ + static_cast(target)->visit([&](auto box) { box->damage(static_cast(inflictor)); }); +} diff --git a/src/objects/ivoball.cpp b/src/objects/ivoball.cpp index fbcecede9..d3fd3e8ce 100644 --- a/src/objects/ivoball.cpp +++ b/src/objects/ivoball.cpp @@ -64,7 +64,7 @@ struct IvoBall : Mobj Fixed wave{(x / mapobjectscale) + (y / mapobjectscale)}; offset(wave / kRippleFactor); color = SKINCOLOR_TANGERINE; - sprzoff = kFloat * mapobjectscale; + sprzoff(kFloat * mapobjectscale); } void think() @@ -81,7 +81,7 @@ struct IvoBall : Mobj fixed_t ballTimer = leveltime + offset(); Fixed bob = kBobHeight * Fixed {FSIN((M_TAU_FIXED * kBobTime) * ballTimer)}; - spriteyoffset = bob; + spriteyoffset(bob); colorized = !((ballTimer / kFlashTime) & 1); } diff --git a/src/p_inter.c b/src/p_inter.c index 35e158178..05fb78b3d 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -988,6 +988,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; } + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + { + Obj_TryCrateTouch(special, toucher); + return; + } + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; @@ -2842,10 +2849,19 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da return false; } - if (target->type == MT_BALLSWITCH_BALL) + switch (target->type) { - Obj_BallSwitchDamaged(target, inflictor, source); - return false; + case MT_BALLSWITCH_BALL: + Obj_BallSwitchDamaged(target, inflictor, source); + return false; + + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + Obj_TryCrateDamage(target, inflictor); + return true; + + default: + break; } if (!force) diff --git a/src/p_map.c b/src/p_map.c index 216d7c169..affb16159 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1200,8 +1200,6 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } - //} - if ((thing->type == MT_SPRINGSHELL || thing->type == MT_YELLOWSHELL) && thing->health > 0 && (tm.thing->player || (tm.thing->flags & MF_PUSHABLE)) && tm.thing->health > 0) { @@ -1634,6 +1632,31 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) } } + switch (tm.thing->type) + { + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + // Let crates stack on top of solid objects (this + // includes other crates). + if (thing->flags & MF_SOLID) + { + fixed_t thingtop = thing->z + thing->height; + if (tm.thing->z > thing->z && thingtop > tm.floorz) + { + tm.floorz = thingtop; + tm.floorrover = NULL; + tm.floorslope = NULL; + tm.floorpic = -1; + tm.floorstep = 0; + return BMIT_CONTINUE; + } + } + break; + + default: + break; + } + // This code is causing conflicts for Ring Racers, // as solid objects cause bumping. If you need to // bring back this code for a moving platform-style diff --git a/src/p_mobj.c b/src/p_mobj.c index 2578f13af..50bb15ac8 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6836,6 +6836,20 @@ static void P_MobjSceneryThink(mobj_t *mobj) Obj_PatrolIvoBallThink(mobj); return; } + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + { + if (!Obj_TryCrateThink(mobj)) + { + return; + } + break; + } + case MT_BOX_SIDE: + { + Obj_BoxSideThink(mobj); + return; + } case MT_VWREF: case MT_VWREB: { @@ -14476,6 +14490,12 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) Obj_PatrolIvoBallInit(mobj); break; } + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + { + Obj_TryCrateInit(mobj); + break; + } default: break; }