Hardcode SA2 Crates

This commit is contained in:
James R 2023-11-27 04:59:03 -08:00
parent b773fb9b33
commit aec21cd114
9 changed files with 547 additions and 2 deletions

View file

@ -4831,6 +4831,13 @@ 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",
};
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@ -6057,6 +6064,10 @@ 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",
};
const char *const MOBJFLAG_LIST[] = {

View file

@ -986,6 +986,8 @@ char sprnames[NUMSPRITES + 1][5] =
"FBTN",
"SFTR",
"SABX",
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
"VIEW",
};
@ -5677,6 +5679,13 @@ 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
};
mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@ -32200,6 +32209,84 @@ 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
},
};

View file

@ -1540,6 +1540,8 @@ typedef enum sprite
SPR_FBTN,
SPR_SFTR,
SPR_SABX,
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
SPR_VIEW,
@ -6102,6 +6104,13 @@ 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_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
NUMSTATES
@ -7348,6 +7357,10 @@ typedef enum mobj_type
MT_PATROLIVOBALL,
MT_AIRIVOBALL,
MT_BOX_SIDE,
MT_BOX_DEBRIS,
MT_SA2_CRATE,
MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES

View file

@ -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 */
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

View file

@ -44,6 +44,7 @@ target_sources(SRB2SDL2 PRIVATE
mega-barrier.cpp
frost-thrower.cpp
ivoball.cpp
crate.cpp
)
add_subdirectory(versus)

376
src/objects/crate.cpp Normal file
View file

@ -0,0 +1,376 @@
// 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 <algorithm>
#include <array>
#include <tuple>
#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<int, 6>; // -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 Graphic : Mobj
{
void hnext() = delete;
Graphic* next() const { return Mobj::hnext<Graphic>(); }
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 <typename F>
bool visit(F&& visitor);
void update()
{
visit([](auto box) { box->mobj_t::z++; });
}
};
template <class Config>
struct Box : AnyBox
{
static constexpr Fixed kIntendedSize = 128*FRACUNIT;
static constexpr Vec2<Fixed> kScrunch = {4*FRACUNIT/5, 6*FRACUNIT/5};
void extravalue1() = delete;
statenum_t debris_state() const { return static_cast<statenum_t>(mobj_t::extravalue1); }
void debris_state(statenum_t n) { mobj_t::extravalue1 = n; }
auto gfx() { return MobjListView(static_cast<Graphic*>(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<Side>({}, 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<Mobj>({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<Fixed> search = aabb();
Vec2<Fixed> org{bmaporgx, bmaporgy};
search.a -= org + MAXRADIUS;
search.b -= org - MAXRADIUS;
search.a.x = static_cast<UINT32>(search.a.x) >> MAPBLOCKSHIFT;
search.b.x = static_cast<UINT32>(search.b.x) >> MAPBLOCKSHIFT;
search.a.y = static_cast<UINT32>(search.a.y) >> MAPBLOCKSHIFT;
search.b.y = static_cast<UINT32>(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<AnyBox*>(thing)->update();
return BMIT_CONTINUE;
}
);
}
}
}
};
struct Crate : Box<SA2CrateConfig>
{
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);
}
};
template <typename F>
bool AnyBox::visit(F&& visitor)
{
switch (type)
{
case MT_SA2_CRATE:
visitor(static_cast<Crate*>(this));
break;
default:
return false;
}
return true;
}
}; // namespace
void Obj_BoxSideThink(mobj_t* mobj)
{
static_cast<Side*>(mobj)->think();
}
void Obj_TryCrateInit(mobj_t* mobj)
{
static_cast<AnyBox*>(mobj)->visit([&](auto box) { box->init(); });
}
boolean Obj_TryCrateThink(mobj_t* mobj)
{
bool c = false;
static_cast<AnyBox*>(mobj)->visit([&](auto box) { c = box->think(); });
return c;
}
void Obj_TryCrateTouch(mobj_t* special, mobj_t* toucher)
{
static_cast<AnyBox*>(special)->visit([&](auto box) { box->touch(static_cast<Toucher*>(toucher)); });
}
void Obj_TryCrateDamage(mobj_t* target, mobj_t* inflictor)
{
static_cast<AnyBox*>(target)->visit([&](auto box) { box->damage(static_cast<Toucher*>(inflictor)); });
}

View file

@ -988,6 +988,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return;
}
case MT_SA2_CRATE:
{
Obj_TryCrateTouch(special, toucher);
return;
}
default: // SOC or script pickup
P_SetTarget(&special->target, toucher);
break;
@ -2848,6 +2854,10 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
Obj_BallSwitchDamaged(target, inflictor, source);
return false;
case MT_SA2_CRATE:
Obj_TryCrateDamage(target, inflictor);
return true;
default:
break;
}

View file

@ -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,30 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
}
}
switch (tm.thing->type)
{
case MT_SA2_CRATE:
// 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

View file

@ -6836,6 +6836,19 @@ static void P_MobjSceneryThink(mobj_t *mobj)
Obj_PatrolIvoBallThink(mobj);
return;
}
case MT_SA2_CRATE:
{
if (!Obj_TryCrateThink(mobj))
{
return;
}
break;
}
case MT_BOX_SIDE:
{
Obj_BoxSideThink(mobj);
return;
}
case MT_VWREF:
case MT_VWREB:
{
@ -14476,6 +14489,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj)
Obj_PatrolIvoBallInit(mobj);
break;
}
case MT_SA2_CRATE:
{
Obj_TryCrateInit(mobj);
break;
}
default:
break;
}