diff --git a/src/deh_tables.c b/src/deh_tables.c index 78d6a6422..4e3e668ed 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4860,6 +4860,10 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi // MT_BSZSLAMP "S_BSWL", "S_BSWC", + + "S_BETA_PARTICLE_WHEEL", + "S_BETA_PARTICLE_ICON", + "S_BETA_PARTICLE_EXPLOSION", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -6099,6 +6103,11 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BSZLAMP_L", "MT_BSZSLAMP", "MT_BSZSLCHA", + + "MT_BETA_EMITTER", + "MT_BETA_PARTICLE_PHYSICAL", + "MT_BETA_PARTICLE_VISUAL", + "MT_BETA_PARTICLE_EXPLOSION", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index 506a7c25b..7dfefaade 100644 --- a/src/info.c +++ b/src/info.c @@ -999,6 +999,8 @@ char sprnames[NUMSPRITES + 1][5] = "BSWL", "BSWC", + "LCLA", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5719,6 +5721,10 @@ state_t states[NUMSTATES] = // MT_BSZSLAMP {SPR_BSWL, 0|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BSWL {SPR_BSWC, 0|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BSWC + + {SPR_LCLA, 0|FF_FULLBRIGHT|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BETA_PARTICLE_WHEEL + {SPR_LCLA, 1|FF_FULLBRIGHT|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BETA_PARTICLE_ICON + {SPR_LCLA, 2|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BETA_PARTICLE_EXPLOSION }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -30674,8 +30680,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_None, // deathsound 0, // speed - 0, // radius - 0, // height + 40*FRACUNIT, // radius + 80*FRACUNIT, // height 0, // display offset 100, // mass 1, // damage @@ -32529,6 +32535,110 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 0, // flags S_NULL // raisestate }, + { // MT_BETA_EMITTER + 2699, // 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 + 0, // radius + 0, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOSECTOR|MF_SCENERY, // flags + S_NULL // raisestate + }, + { // MT_BETA_PARTICLE_PHYSICAL + -1, // 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 + 24*FRACUNIT, // radius + 128*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_BETA_PARTICLE_VISUAL + -1, // doomednum + S_NULL, // 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 + 0, // radius + 0, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_BETA_PARTICLE_EXPLOSION + -1, // doomednum + S_BETA_PARTICLE_EXPLOSION, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // 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, // display offset + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_NOHITLAGFORME|MF_SPECIAL|MF_DONTPUNT, // flags + S_NULL // raisestate + }, }; diff --git a/src/info.h b/src/info.h index 52c1b5942..339b261e0 100644 --- a/src/info.h +++ b/src/info.h @@ -1553,6 +1553,8 @@ typedef enum sprite SPR_BSWL, SPR_BSWC, + SPR_LCLA, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -6144,6 +6146,10 @@ typedef enum state S_BSWL, S_BSWC, + S_BETA_PARTICLE_WHEEL, + S_BETA_PARTICLE_ICON, + S_BETA_PARTICLE_EXPLOSION, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -7403,6 +7409,11 @@ typedef enum mobj_type MT_BSZSLAMP, MT_BSZSLCHA, + MT_BETA_EMITTER, + MT_BETA_PARTICLE_PHYSICAL, + MT_BETA_PARTICLE_VISUAL, + MT_BETA_PARTICLE_EXPLOSION, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_objects.h b/src/k_objects.h index 2d1b01c7c..c564cc2a9 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -330,6 +330,15 @@ void Obj_TryCrateDamage(mobj_t *target, mobj_t *inflictor); void Obj_SpearInit(mobj_t *mo); void Obj_SpearThink(mobj_t *mo); +/* Lost Colony Fuel Canister */ +void Obj_FuelCanisterEmitterInit(mobj_t *mo); +boolean Obj_FuelCanisterVisualThink(mobj_t *mo); +boolean Obj_FuelCanisterEmitterThink(mobj_t *mo); +boolean Obj_FuelCanisterThink(mobj_t *mo); +void Obj_FuelCanisterTouch(mobj_t *special, mobj_t *toucher); +void Obj_FuelCanisterExplosionTouch(mobj_t *special, mobj_t *toucher); +boolean Obj_FuelCanisterExplosionThink(mobj_t *mo); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index b7dda6caf..3048568ea 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -8,7 +8,7 @@ target_sources(SRB2SDL2 PRIVATE orbinaut.c jawz.c duel-bomb.c - broly.c + broly.cpp ufo.c monitor.c item-spot.c @@ -46,6 +46,7 @@ target_sources(SRB2SDL2 PRIVATE ivoball.cpp crate.cpp spear.cpp + fuel.cpp ) add_subdirectory(versus) diff --git a/src/objects/broly.c b/src/objects/broly.c deleted file mode 100644 index 2419fac10..000000000 --- a/src/objects/broly.c +++ /dev/null @@ -1,77 +0,0 @@ -#include "../doomdef.h" -#include "../info.h" -#include "../k_kart.h" -#include "../k_objects.h" -#include "../m_easing.h" -#include "../p_local.h" -#include "../s_sound.h" - -/* An object may not be visible on the same tic: - 1) that it spawned - 2) that it cycles to the next state */ -#define BUFFER_TICS (2) - -#define broly_duration(o) ((o)->extravalue1) -#define broly_maxscale(o) ((o)->extravalue2) - -static inline fixed_t -get_unit_linear (const mobj_t *x) -{ - const tic_t t = (x->tics - BUFFER_TICS); - - return t * FRACUNIT / broly_duration(x); -} - -mobj_t * -Obj_SpawnBrolyKi -( mobj_t * source, - tic_t duration) -{ - mobj_t *x; - - if (duration <= 0) - { - return NULL; - } - - x = P_SpawnMobjFromMobj( - source, 0, 0, 0, MT_BROLY); - - P_SetTarget(&x->target, source); - - // Shrink into center of source object. - x->z = (source->z + source->height / 2); - - x->colorized = true; - x->color = source->color; - x->hitlag = 0; // do not copy source hitlag - - broly_maxscale(x) = 64 * mapobjectscale; - broly_duration(x) = duration; - - x->tics = (duration + BUFFER_TICS); - - K_ReduceVFXForEveryone(x); - - S_StartSound(x, sfx_cdfm74); - - return x; -} - -boolean -Obj_BrolyKiThink (mobj_t *x) -{ - if (broly_duration(x) <= 0) - { - P_RemoveMobj(x); - return false; - } - - const fixed_t - t = get_unit_linear(x), - n = Easing_OutSine(t, 0, broly_maxscale(x)); - - P_InstaScale(x, n); - - return true; -} diff --git a/src/objects/broly.cpp b/src/objects/broly.cpp new file mode 100644 index 000000000..fe2f43726 --- /dev/null +++ b/src/objects/broly.cpp @@ -0,0 +1,38 @@ +// 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 "broly.hpp" + +#include "../doomstat.h" +#include "../k_kart.h" +#include "../sounds.h" + +using namespace srb2::objects; + +mobj_t * +Obj_SpawnBrolyKi +( mobj_t * source, + tic_t duration) +{ + Broly* x = Broly::spawn(static_cast(source), duration, {64 * mapobjectscale, 0}); + + x->colorized = true; + x->color = source->color; + + K_ReduceVFXForEveryone(x); + x->voice(sfx_cdfm74); + + return x; +} + +boolean +Obj_BrolyKiThink (mobj_t *x) +{ + return static_cast(x)->think(); +} diff --git a/src/objects/broly.hpp b/src/objects/broly.hpp new file mode 100644 index 000000000..d75a7fc4b --- /dev/null +++ b/src/objects/broly.hpp @@ -0,0 +1,96 @@ +// 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 objects_broly_hpp +#define objects_broly_hpp + +#include + +#include "objects.hpp" + +#include "../info.h" +#include "../m_easing.h" + +namespace srb2::objects +{ + +struct Broly : Mobj +{ + static constexpr mobjtype_t kMobjType = MT_BROLY; + + /* An object may not be visible on the same tic: + 1) that it spawned + 2) that it cycles to the next state */ + static constexpr int kBufferTics = 2; + + void extravalue1() = delete; + tic_t duration() const { return mobj_t::extravalue1; } + void duration(tic_t n) { mobj_t::extravalue1 = n; } + + void threshold() = delete; + void extravalue2() = delete; + Vec2 size() const { return {mobj_t::threshold, mobj_t::extravalue2}; } + void size(const Vec2& n) + { + mobj_t::threshold = n.x; + mobj_t::extravalue2 = n.y; + } + + bool valid() const { return duration(); } + + tic_t remaining() const { return tics - kBufferTics; } + + Fixed linear() const { return (remaining() * FRACUNIT) / duration(); } + + template + static T* spawn(Mobj* source, tic_t duration, const Vec2& size) + { + static_assert(std::is_base_of_v); + + if (duration == 0) + { + return nullptr; + } + + T* x = Mobj::spawn(source->center(), T::kMobjType); + + x->target(source); + + // Shrink into center of source object. + x->z -= x->height / 2; + + x->size(size); + x->duration(duration); + + x->tics = (duration + kBufferTics); + + return x; + } + + bool think() + { + if (!valid()) + { + remove(); + return false; + } + + const Fixed center = z + (height / 2); + const Vec2 v = size(); + + scale(Easing_OutSine(linear(), v.y, v.x)); + z = center - (height / 2); + + return true; + } +}; + +}; // namespace srb2::objects + +#endif/*objects_broly_hpp*/ diff --git a/src/objects/fuel.cpp b/src/objects/fuel.cpp new file mode 100644 index 000000000..53e4ccfcd --- /dev/null +++ b/src/objects/fuel.cpp @@ -0,0 +1,236 @@ +// 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 "broly.hpp" +#include "objects.hpp" + +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_objects.h" +#include "../sounds.h" +#include "../tables.h" + +using namespace srb2::objects; + +namespace +{ + +struct FuelCanister : Mobj +{ + struct Emitter : Mobj + { + void thing_args() = delete; + tic_t frequency() const { return mobj_t::thing_args[0]; } + tic_t initial_timer() const { return mobj_t::thing_args[1]; } + + void extravalue1() = delete; + tic_t timer() const { return mobj_t::extravalue1; } + void timer(tic_t n) { mobj_t::extravalue1 = n; } + + void init() + { + timer(initial_timer()); + } + + bool think() + { + if (timer() > 0) + { + timer(timer() - 1); + return true; + } + + timer(frequency()); + + FuelCanister::spawn(this); + + return true; + } + }; + + struct Vis : Mobj + { + void extravalue1() = delete; + angle_t phys_angle_ofs() const { return mobj_t::extravalue1; } + void phys_angle_ofs(angle_t n) { mobj_t::extravalue1 = n; } + + void extravalue2() = delete; + angle_t vis_angle_ofs() const { return mobj_t::extravalue2; } + void vis_angle_ofs(angle_t n) { mobj_t::extravalue2 = n; } + + bool valid() const { return Mobj::valid() && Mobj::valid(target()); } + + bool think() + { + if (!valid()) + { + remove(); + return false; + } + + const angle_t angleOutward = target()->angle + phys_angle_ofs(); + + move_origin({target()->pos2d() + (vector(angleOutward) * Fixed {radius}), target()->z}); + angle = angleOutward + vis_angle_ofs(); + + return true; + } + }; + + struct Explosion : Broly + { + static constexpr mobjtype_t kMobjType = MT_BETA_PARTICLE_EXPLOSION; + + static Explosion* spawn(Mobj* source) + { + Explosion* x = Broly::spawn(source, 3*TICRATE, {1, 8 * mapobjectscale}); + x->voice(sfx_lcfuel); + return x; + } + + void touch(Mobj* toucher) + { + if (!P_DamageMobj(toucher, this, this, 1, DMG_NORMAL)) + { + auto& hitlag = toucher->mobj_t::hitlag; + + // Hitlag = remaining duration of explosion + if (hitlag >= 0 && hitlag + 0u < remaining()) + { + hitlag = remaining(); + } + } + } + + bool think() { return Broly::think(); } + }; + + bool valid() const { return Mobj::valid() && momz; } + + static FuelCanister* spawn(Mobj* source) + { + FuelCanister* caps = source->spawn_from({}, MT_BETA_PARTICLE_PHYSICAL); + caps->init(); + return caps; + } + + void init() + { + momz = 8 * scale(); + z -= momz; + + pieces(); + pieces(); + } + + bool think() + { + if (!valid()) + { + remove(); + return false; + } + + angle += 8 * ANG1; + + return true; + } + + void touch(Mobj* toucher) + { + Explosion::spawn(toucher); + } + +private: + struct Wheel + { + static constexpr int kSides = 6; + static constexpr statenum_t kState = S_BETA_PARTICLE_WHEEL; + static constexpr int kRadius = 8; + static constexpr Fixed kScale = FRACUNIT; + static constexpr angle_t kAngleOffset = 0; + static constexpr int kZOffset = 0; + }; + + struct Icon + { + static constexpr int kSides = 2; + static constexpr statenum_t kState = S_BETA_PARTICLE_ICON; + static constexpr int kRadius = 8; + static constexpr Fixed kScale = 3*FRACUNIT/4; + static constexpr angle_t kAngleOffset = ANGLE_90; + static constexpr int kZOffset = 64; + }; + + static Vec2 vector(angle_t angle) { return {FCOS(angle), FSIN(angle)}; } + + template + void pieces() + { + constexpr angle_t kAngleBetween = ANGLE_MAX / Config::kSides; + + const Fixed zOfs = Config::kZOffset * (Fixed {FRACUNIT} / Config::kScale); + const Fixed radius = Config::kRadius * scale(); + const Fixed scale = Config::kScale * this->scale(); + + for (int i = 1; i <= Config::kSides; ++i) + { + angle_t angleOutward = i * kAngleBetween; + + Vis* vis = spawn_from({vector(angle + angleOutward) * radius, 0}, MT_BETA_PARTICLE_VISUAL); + + vis->state(Config::kState); + vis->target(this); + vis->scale(scale); + vis->radius = radius; + vis->spriteyoffset(zOfs); + + vis->phys_angle_ofs(angleOutward); + vis->vis_angle_ofs(Config::kAngleOffset); + } + } +}; + +}; // namespace + +void Obj_FuelCanisterEmitterInit(mobj_t *mo) +{ + static_cast(mo)->init(); +} + +boolean Obj_FuelCanisterVisualThink(mobj_t *mo) +{ + return static_cast(mo)->think(); +} + +boolean Obj_FuelCanisterEmitterThink(mobj_t *mo) +{ + return static_cast(mo)->think(); +} + +boolean Obj_FuelCanisterThink(mobj_t *mo) +{ + return static_cast(mo)->think(); +} + +void Obj_FuelCanisterTouch(mobj_t *special, mobj_t *toucher) +{ + static_cast(special)->touch(static_cast(toucher)); +} + +void Obj_FuelCanisterExplosionTouch(mobj_t *special, mobj_t *toucher) +{ + static_cast(special)->touch(static_cast(toucher)); +} + +boolean Obj_FuelCanisterExplosionThink(mobj_t *mo) +{ + return static_cast(mo)->think(); +} diff --git a/src/objects/objects.hpp b/src/objects/objects.hpp new file mode 100644 index 000000000..dd8869ccc --- /dev/null +++ b/src/objects/objects.hpp @@ -0,0 +1,19 @@ +#ifndef objects_objects_hpp +#define objects_objects_hpp + +#include "../math/fixed.hpp" +#include "../math/vec.hpp" +#include "../mobj.hpp" + +#include "../k_objects.h" + +namespace srb2::objects +{ + +using srb2::Mobj; +using srb2::math::Fixed; +using srb2::math::Vec2; + +}; // namespace srb2::objects + +#endif/*objects_objects_hpp*/ diff --git a/src/p_inter.c b/src/p_inter.c index 05fb78b3d..4c600ef0b 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -995,6 +995,18 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; } + case MT_BETA_PARTICLE_PHYSICAL: + { + Obj_FuelCanisterTouch(special, toucher); + break; + } + + case MT_BETA_PARTICLE_EXPLOSION: + { + Obj_FuelCanisterExplosionTouch(special, toucher); + return; + } + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; diff --git a/src/p_mobj.c b/src/p_mobj.c index f5b14929c..76d2daa6d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6862,6 +6862,24 @@ static void P_MobjSceneryThink(mobj_t *mobj) { return; } + case MT_BETA_PARTICLE_VISUAL: + { + Obj_FuelCanisterVisualThink(mobj); + return; + } + case MT_BETA_EMITTER: + { + Obj_FuelCanisterEmitterThink(mobj); + return; + } + case MT_BETA_PARTICLE_EXPLOSION: + { + if (Obj_FuelCanisterExplosionThink(mobj) == false) + { + return; + } + break; + } case MT_VWREF: case MT_VWREB: { @@ -10259,6 +10277,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) Obj_SidewaysFreezeThrusterThink(mobj); break; } + case MT_BETA_PARTICLE_PHYSICAL: + { + if (!Obj_FuelCanisterThink(mobj)) + { + return false; + } + break; + } default: // check mobj against possible water content, before movement code @@ -11034,6 +11060,8 @@ fixed_t P_GetMobjDefaultScale(mobj_t *mobj) return 2*FRACUNIT; case MT_SPEAR: return 2*FRACUNIT; + case MT_BETA_EMITTER: + return 4*FRACUNIT; default: break; } @@ -14515,6 +14543,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) Obj_SpearInit(mobj); break; } + case MT_BETA_EMITTER: + { + Obj_FuelCanisterEmitterInit(mobj); + break; + } default: break; } diff --git a/src/sounds.c b/src/sounds.c index 1c9a5b841..9d0722e2a 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1251,6 +1251,8 @@ sfxinfo_t S_sfx[NUMSFX] = {"ivobal", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Ivo Ball + {"lcfuel", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Fuel Capsule explodes"}, + // Damage sounds {"dmga1", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, {"dmga2", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, diff --git a/src/sounds.h b/src/sounds.h index 88ba458e4..9b31bb293 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1323,6 +1323,9 @@ typedef enum // Ivo Ball sfx_ivobal, + // Fuel Capsule + sfx_lcfuel, + // Damage sounds sfx_dmga1, sfx_dmga2,