From 3c1e13084b11ab8e101eb684b32e8f9d30fbd126 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 30 Mar 2024 22:33:43 -0700 Subject: [PATCH 1/4] Add Obj_SpawnCustomBrolyKi --- src/k_objects.h | 1 + src/objects/broly.cpp | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/k_objects.h b/src/k_objects.h index abc5913cf..4182dfcfe 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -83,6 +83,7 @@ void Obj_DuelBombInit(mobj_t *bomb); /* Broly Ki */ mobj_t *Obj_SpawnBrolyKi(mobj_t *source, tic_t duration); +mobj_t *Obj_SpawnCustomBrolyKi(mobj_t *source, tic_t duration, fixed_t start, fixed_t end); boolean Obj_BrolyKiThink(mobj_t *ki); /* Special Stage UFO */ diff --git a/src/objects/broly.cpp b/src/objects/broly.cpp index a7c589d6c..7b6b584fc 100644 --- a/src/objects/broly.cpp +++ b/src/objects/broly.cpp @@ -16,11 +16,13 @@ using namespace srb2::objects; mobj_t * -Obj_SpawnBrolyKi +Obj_SpawnCustomBrolyKi ( mobj_t * source, - tic_t duration) + tic_t duration, + fixed_t start, + fixed_t end) { - Broly* x = Broly::spawn(static_cast(source), duration, {64 * mapobjectscale, 0}); + Broly* x = Broly::spawn(static_cast(source), duration, {start, end}); if (!x) { @@ -36,6 +38,14 @@ Obj_SpawnBrolyKi return x; } +mobj_t * +Obj_SpawnBrolyKi +( mobj_t * source, + tic_t duration) +{ + return Obj_SpawnCustomBrolyKi(source, duration, 64 * mapobjectscale, 0); +} + boolean Obj_BrolyKiThink (mobj_t *x) { From 52449bdb5e098805df3fa9a22de5308cf27c2e1c Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 30 Mar 2024 22:35:51 -0700 Subject: [PATCH 2/4] srb2::Mobj: fix state call on player --- src/mobj.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mobj.hpp b/src/mobj.hpp index ce947b851..1678c879a 100644 --- a/src/mobj.hpp +++ b/src/mobj.hpp @@ -157,7 +157,7 @@ struct Mobj : mobj_t statenum_t num() const { return static_cast(static_cast(this) - states); } }; - void state(statenum_t state) { P_SetMobjState(this, state); } + void state(statenum_t state) { (player ? P_SetPlayerMobjState : P_SetMobjState)(this, state); } const State* state() const { return static_cast(mobj_t::state); } From 9d0650786e301e76adad43b15151ed6e94ea6c9a Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 30 Mar 2024 22:36:05 -0700 Subject: [PATCH 3/4] srb2::Mobj: add exact_hitlag method --- src/mobj.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mobj.hpp b/src/mobj.hpp index 1678c879a..fcefead04 100644 --- a/src/mobj.hpp +++ b/src/mobj.hpp @@ -276,6 +276,11 @@ struct Mobj : mobj_t { K_SetHitLagForObjects(this, inflictor, source, tics, damage); } + void exact_hitlag(INT32 tics, bool damage) + { + mobj_t::hitlag = tics; + mobj_t::eflags = (mobj_t::eflags & ~MFE_DAMAGEHITLAG) | (MFE_DAMAGEHITLAG * damage); + } }; }; // namespace srb2 From bf0041644a6c7155381ad198843986ca15a9de3a Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 30 Mar 2024 22:37:41 -0700 Subject: [PATCH 4/4] Add destroyed kart effects - Removed hitlag sparks and extra quakes from player death - Removed dontdraw flag from NO CONTESTED player - Moved most dead kart code to objects/destroyed-kart.cpp --- src/deh_tables.c | 8 + src/info.c | 60 +++- src/info.h | 24 ++ src/k_objects.h | 7 + src/mobj.hpp | 2 +- src/objects/CMakeLists.txt | 1 + src/objects/broly.cpp | 2 +- src/objects/destroyed-kart.cpp | 511 +++++++++++++++++++++++++++++++++ src/p_inter.c | 95 +----- src/p_mobj.c | 82 ++---- src/p_user.c | 1 - src/sounds.c | 6 + src/sounds.h | 6 + 13 files changed, 661 insertions(+), 144 deletions(-) create mode 100644 src/objects/destroyed-kart.cpp diff --git a/src/deh_tables.c b/src/deh_tables.c index 581f8c23d..33b836fde 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -370,6 +370,13 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_KART_TIRE1", "S_KART_TIRE2", + "S_KART_FIRE", + "S_KART_SMOKE", + + "S_KART_XPL01", + "S_KART_XPL02", + "S_KART_XPL03", + // Boss Explosion "S_BOSSEXPLODE", @@ -3061,6 +3068,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_PLAYER", "MT_KART_LEFTOVER", "MT_KART_TIRE", + "MT_KART_PARTICLE", // Generic Boss Items "MT_BOSSEXPLODE", diff --git a/src/info.c b/src/info.c index df1e76e57..db2ab0ef1 100644 --- a/src/info.c +++ b/src/info.c @@ -746,6 +746,22 @@ char sprnames[NUMSPRITES + 1][5] = // Tutorial "TLKP", // Talk Point + // Destroyed Kart + "DIEA", // tire + "DIEB", // pipeframe bar + "DIEC", // pedal tip + "DIED", // right pedal + "DIEE", // steering wheel + "DIEF", // kart + "DIEG", // left pedal + "DIEH", // strut + "DIEI", // wheel axle bar + "DIEJ", // screw + "DIEK", // electric engine + "DIEL", // fire + "DIEM", // smoke + "DIEN", // explosion + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -873,18 +889,25 @@ state_t states[NUMSTATES] = {SPR_PLAY, SPR2_DRRO, 1, {NULL}, 0, 0, S_KART_DRIFT_R_OUT}, // S_KART_DRIFT_R_OUT {SPR_PLAY, SPR2_DRRI, 1, {NULL}, 0, 0, S_KART_DRIFT_R_IN}, // S_KART_DRIFT_R_IN {SPR_PLAY, SPR2_SPIN|FF_ANIMATE, 350, {NULL}, 0, 1, S_KART_STILL}, // S_KART_SPINOUT - {SPR_PLAY, SPR2_DEAD, 3, {NULL}, 0, 0, S_KART_DEAD}, // S_KART_DEAD + {SPR_PLAY, SPR2_DEAD|FF_SEMIBRIGHT, 3, {NULL}, 0, 0, S_KART_DEAD}, // S_KART_DEAD {SPR_PLAY, SPR2_SIGN|FF_ANIMATE|FF_PAPERSPRITE, -1, {NULL}, 0, 1, 0}, // S_KART_SIGN {SPR_PLAY, SPR2_SIGL|FF_ANIMATE|FF_PAPERSPRITE, -1, {NULL}, 0, 1, 0}, // S_KART_SIGL {SPR_NULL, 0, -1, {NULL}, 0, 0, S_OBJPLACE_DUMMY}, // S_OBJPLACE_DUMMY {SPR_KART, 0, -1, {NULL}, 0, 0, S_NULL}, // S_KART_LEFTOVER - {SPR_KART, 1, -1, {NULL}, 0, 0, S_NULL}, // S_KART_LEFTOVER_NOTIRES + {SPR_DIEF, 0, -1, {NULL}, 0, 0, S_NULL}, // S_KART_LEFTOVER_NOTIRES {SPR_TIRE, 0, -1, {NULL}, 0, 0, S_NULL}, // S_KART_TIRE1 {SPR_TIRE, 1, -1, {NULL}, 0, 0, S_NULL}, // S_KART_TIRE2 + {SPR_DIEL, 0|FF_ANIMATE, 12, {NULL}, 11, 1, S_NULL}, // S_KART_FIRE + {SPR_DIEM, FF_SEMIBRIGHT|FF_ANIMATE|FF_TRANS30, 30, {NULL}, 9, 3, S_NULL}, // S_KART_SMOKE + + {SPR_DIEN, 0|FF_PAPERSPRITE|FF_ADD, 3, {NULL}, 0, 0, S_KART_XPL02}, // S_KART_XPL01 + {SPR_DIEN, 1|FF_PAPERSPRITE|FF_ADD|FF_ANIMATE, 4, {NULL}, 1, 2, S_KART_XPL03}, // S_KART_XPL02 + {SPR_DIEN, 3|FF_PAPERSPRITE|FF_ADD|FF_ANIMATE, 10, {NULL}, 4, 2, S_NULL}, // S_KART_XPL03 + // Boss Explosion {SPR_BOM2, FF_FULLBRIGHT|FF_ANIMATE, (5*7), {NULL}, 6, 5, S_NULL}, // S_BOSSEXPLODE @@ -3722,7 +3745,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_None, // painsound S_NULL, // meleestate S_NULL, // missilestate - S_KART_DEAD, // deathstate + S_KART_SPINOUT, // deathstate S_NULL, // xdeathstate sfx_None, // deathsound 1, // speed @@ -3738,13 +3761,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { // MT_KART_LEFTOVER 4095, // doomednum - S_KART_LEFTOVER, // spawnstate + S_KART_LEFTOVER_NOTIRES, // spawnstate 2, // spawnhealth S_NULL, // seestate sfx_None, // seesound 0, // reactiontime sfx_None, // attacksound - S_NULL, // painstate + S_KART_LEFTOVER_NOTIRES, // painstate 0, // painchance sfx_None, // painsound S_NULL, // meleestate @@ -3790,6 +3813,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_KART_PARTICLE + -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 + 1, // speed + 6*FRACUNIT, // radius + 12*FRACUNIT, // height + -1, // display offset + 1000, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_DONTENCOREMAP|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + { // MT_BOSSEXPLODE -1, // doomednum S_BOSSEXPLODE, // spawnstate diff --git a/src/info.h b/src/info.h index 9ce16346f..e0a224ebe 100644 --- a/src/info.h +++ b/src/info.h @@ -1281,6 +1281,22 @@ typedef enum sprite // Tutorial SPR_TLKP, // Talk Point + // Destroyed Kart + SPR_DIEA, // tire + SPR_DIEB, // pipeframe bar + SPR_DIEC, // pedal tip + SPR_DIED, // right pedal + SPR_DIEE, // steering wheel + SPR_DIEF, // kart + SPR_DIEG, // left pedal + SPR_DIEH, // strut + SPR_DIEI, // wheel axle bar + SPR_DIEJ, // screw + SPR_DIEK, // electric engine + SPR_DIEL, // fire + SPR_DIEM, // smoke + SPR_DIEN, // explosion + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -1371,6 +1387,13 @@ typedef enum state S_KART_TIRE1, S_KART_TIRE2, + S_KART_FIRE, + S_KART_SMOKE, + + S_KART_XPL01, + S_KART_XPL02, + S_KART_XPL03, + // Boss Explosion S_BOSSEXPLODE, @@ -4089,6 +4112,7 @@ typedef enum mobj_type MT_PLAYER, MT_KART_LEFTOVER, MT_KART_TIRE, + MT_KART_PARTICLE, // Generic Boss Items MT_BOSSEXPLODE, diff --git a/src/k_objects.h b/src/k_objects.h index 4182dfcfe..8fc9351bc 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -418,6 +418,13 @@ void Obj_TalkPointOrbThink(mobj_t* mo); void Obj_SpawnPowerUpSpinner(mobj_t *source, INT32 powerup, tic_t duration); void Obj_TickPowerUpSpinner(mobj_t *mobj); +/* Destroyed Kart */ +void Obj_SpawnDestroyedKart(mobj_t *player); +void Obj_DestroyedKartThink(mobj_t *kart); +boolean Obj_DestroyKart(mobj_t *kart); +void Obj_DestroyedKartParticleThink(mobj_t *part); +void Obj_DestroyedKartParticleLanding(mobj_t *part); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/mobj.hpp b/src/mobj.hpp index fcefead04..8ceb3700b 100644 --- a/src/mobj.hpp +++ b/src/mobj.hpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2023 by James Robert Roman +// Copyright (C) 2023-2024 by James Robert Roman // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 5ba9951d2..31a0f6122 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -56,6 +56,7 @@ target_sources(SRB2SDL2 PRIVATE talk-point.cpp powerup-spinner.cpp adventure-air-booster.c + destroyed-kart.cpp ) add_subdirectory(versus) diff --git a/src/objects/broly.cpp b/src/objects/broly.cpp index 7b6b584fc..7acf799f9 100644 --- a/src/objects/broly.cpp +++ b/src/objects/broly.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2023 by James Robert Roman +// Copyright (C) 2023-2024 by James Robert Roman // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/destroyed-kart.cpp b/src/objects/destroyed-kart.cpp new file mode 100644 index 000000000..fa7e6d18c --- /dev/null +++ b/src/objects/destroyed-kart.cpp @@ -0,0 +1,511 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman. +// Copyright (C) 2024 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. +//----------------------------------------------------------------------------- + +#include + +#include "../cxxutil.hpp" +#include "objects.hpp" + +#include "../m_easing.h" +#include "../m_random.h" +#include "../r_skins.h" +#include "../tables.h" + +using namespace srb2::objects; + +namespace +{ + +Vec2 angle_vector(angle_t x) +{ + return Vec2 {FCOS(x), FSIN(x)}; +} + +template +void radial_generic(int ofs, int spokes, F&& f) +{ + int ang = 360 / spokes; + for (int i = 0; i < spokes; ++i) + { + f((ofs + (ang * i)) % 360); + } +} + +angle_t degr_to_angle(int degr) +{ + return FixedAngle(degr * FRACUNIT); +} + +struct Particle : Mobj +{ + void extravalue1() = delete; + UINT8 bounces() const { return mobj_t::extravalue1; } + void bounces(UINT8 n) { mobj_t::extravalue1 = n; } + + void extravalue2() = delete; + UINT8 counter() const { return mobj_t::extravalue2; } + void counter(UINT8 n) { mobj_t::extravalue2 = n; } + + bool is_shrapnel() const { return sprite == SPR_KRBM; } + + static void spew(Mobj* source) + { + auto generic = [&](spritenum_t sprite, int degr, Fixed scale, int momx, const Vec2& momz) + { + Particle* x = source->spawn_from({}, MT_KART_PARTICLE); + if (x) + { + x->sprite = sprite; + x->color = source->color; + x->frame = FF_SEMIBRIGHT; + x->lightlevel = 112; + x->scale(scale * x->scale()); + + x->instathrust(source->angle + degr_to_angle(degr), momx * mapobjectscale); + x->momz = P_RandomRange(PR_ITEM_DEBRIS, momz.x, momz.y) * mapobjectscale * 2; + + x->angle = P_Random(PR_ITEM_DEBRIS); + x->rollangle = P_Random(PR_ITEM_DEBRIS); + + x->renderflags |= RF_DONTDRAW; + } + return x; + }; + + auto part = [&](spritenum_t sprite, int degr, Fixed scale) + { + return generic(sprite, degr, scale, 2, {8, 16}); + }; + + auto radial = [&](spritenum_t sprite, int ofs, int spokes, Fixed scale) + { + radial_generic(ofs, spokes, [&](int ang) { part(sprite, ang, scale); }); + }; + + constexpr Fixed kSmall = 3*FRACUNIT/2; + constexpr Fixed kMedium = 7*FRACUNIT/4; + constexpr Fixed kLarge = 2*FRACUNIT; + + part(SPR_DIEE, 0, kLarge); // steering wheel + part(SPR_DIEK, 180 + 45, kLarge); // engine + + part(SPR_DIEG, 90, kLarge); // left pedal base + part(SPR_DIED, -90, kLarge); // right pedal base + + radial(SPR_DIEI, 90, 2, kLarge); // wheel axle bars + radial(SPR_DIEC, 90, 2, kLarge); // pedal tips + radial(SPR_DIEA, 45, 4, kMedium); // tires + radial(SPR_DIEH, 45, 4, kMedium); // struts / springs + radial(SPR_DIEB, 360/12, 6, kSmall); // pipeframe bars + radial(SPR_DIEJ, 360/16, 8, kSmall); // screws + + radial_generic(0, 6, [&](int degr) { generic(SPR_KRBM, degr, kSmall, 8, {22, 28}); }); // shrapnel + + // explosion + radial_generic( + 45, 4, + [&](int degr) + { + if (Mobj* x = source->spawn_from({0, 0, source->height}, MT_KART_PARTICLE)) + { + x->flags |= MF_NOGRAVITY | MF_NOCLIPHEIGHT; + x->height = 0; + x->scale(2 * x->scale()); + x->angle = degr_to_angle(degr); + x->state(S_KART_XPL01); + x->renderflags |= RF_REDUCEVFX; + } + } + ); + } + + void think() + { + if (state()->num() == S_BRAKEDRIFT) + { + renderflags ^= RF_DONTDRAW; + return; + } + + // explosion + if (sprite == SPR_DIEN) + { + counter(counter() + 1); + if (counter() > 6) + { + renderflags ^= RF_DONTDRAW; + } + return; + } + + constexpr tic_t kReappear = 16; + if (counter() < kReappear && !is_shrapnel()) + { + counter(counter() + 1); + if (counter() == kReappear) + { + renderflags &= ~RF_DONTDRAW; + } + } + + angle += ANGLE_11hh; + rollangle += ANGLE_11hh; + + if (is_shrapnel() && leveltime % 2 == 0) + { + if (Mobj* x = spawn_from({}, MT_BOOMEXPLODE)) + { + x->color = SKINCOLOR_RUBY; + x->scale_between(x->scale() / 2, x->scale() * 8, x->scale() / 16); + x->state(S_SLOWBOOM2); + } + } + + spritescale({FRACUNIT, FRACUNIT}); // unsquish + } + + void on_land() + { + if (!fuse) + { + fuse = (is_shrapnel() ? 70 : 90); + } + + auto squash = [&](int tics) + { + hitlag(tics); + spritescale({2*FRACUNIT, FRACUNIT/2}); // squish + }; + + switch (sprite) + { + case SPR_DIEB: // bar + squash(2); + break; + + case SPR_DIEH: // struts + squash(4); + break; + + case SPR_DIEI: // screws + squash(1); + break; + + case SPR_DIEK: // engine + squash(5); + break; + + default: + break; + } + + if (!is_shrapnel() && fuse > 7 && (bounces() & 1)) // 7 = 0.2/(1/35) + { + voice( + static_cast(P_RandomRange(PR_ITEM_DEBRIS, sfx_die01, sfx_die03)), + P_RandomRange(PR_ITEM_DEBRIS, 20, 40) * 255 / 100 + ); + } + + bounces(bounces() + 1); + } +}; + +struct Kart : Mobj +{ + static constexpr tic_t kVibrateTimer = 70; + static constexpr UINT32 kNoClipFlags = MF_NOCLIP | MF_NOCLIPTHING; + + static tic_t burn_duration() { return (gametyperules & GTR_CLOSERPLAYERS ? 10 : 20) * TICRATE; } + + void extravalue1() = delete; + UINT8 weight() const { return mobj_t::extravalue1; } + void weight(UINT8 n) { mobj_t::extravalue1 = n; } + + void extravalue2() = delete; + tic_t timer() const { return mobj_t::extravalue2; } + void timer(tic_t n) { mobj_t::extravalue2 = n; } + + void threshold() = delete; + tic_t cooldown() const { return mobj_t::threshold; } + void cooldown(tic_t n) { mobj_t::threshold = n; } + + void movecount() = delete; + tic_t burning() const { return mobj_t::movecount; } + void burning(tic_t n) { mobj_t::movecount = n; } + + void target() = delete; + Mobj* player() const { return Mobj::target(); } + void player(Mobj* n) { Mobj::target(n); } + + static void spawn(Mobj* target) + { + SRB2_ASSERT(target->player != nullptr); + + Kart* kart = target->spawn_from({}, MT_KART_LEFTOVER); + if (!kart) + return; + + kart->angle = target->angle; + kart->color = target->color; + P_SetObjectMomZ(kart, 20*FRACUNIT, false); + kart->weight(target->player->kartweight); + kart->flags |= kNoClipFlags; + + if (target->player->pflags & PF_NOCONTEST) + target->tracer(kart); + + kart->state(S_INVISIBLE); + kart->timer(kVibrateTimer); + kart->exact_hitlag(15, true); + kart->player(target); + + Obj_SpawnCustomBrolyKi(target, kart->hitlag() - 2, 32 * mapobjectscale, 0); + + target->exact_hitlag(kart->hitlag() + 1, true); + target->frame |= FF_SEMIBRIGHT; + target->lightlevel = 128; + } + + void think() + { + if (burning() > 0) + { + burning(burning() - 1); + fire(); + } + + if (cooldown() > 0) + { + cooldown(cooldown() - 1); + } + + if (timer() > 0) + { + timer(timer() - 1); + animate(); + } + } + + bool destroy() + { + if (cooldown()) + { + // no-op P_DamageMobj + return true; + } + + if (health <= 1) + { + return false; + } + + Particle::spew(this); + scale(3 * scale() / 2); + health = 1; + state(S_KART_LEFTOVER_NOTIRES); + cooldown(20); + burning(burn_duration()); + + if (!cv_reducevfx.value) + { + voice(sfx_die00); + } + + if (Mobj* p = player(); Mobj::valid(p)) + { + if (p->player && skins[p->player->skin].flags & SF_BADNIK) + { + P_SpawnBadnikExplosion(p); + p->spritescale({2*FRACUNIT, 2*FRACUNIT}); + p->flags |= MF_NOSQUISH; + } + + p->state(S_KART_DEAD); + } + + return true; + } + +private: + void fire() + { + auto spread = [&](const Vec2& range, const Vec2& zrange) + { + angle_t ang = P_Random(PR_ITEM_DEBRIS); + Fixed r = P_RandomRange(PR_ITEM_DEBRIS, range.x, range.y) * mapobjectscale * 4; + Fixed z = P_RandomRange(PR_ITEM_DEBRIS, zrange.x, zrange.y) * mapobjectscale * 4; + return spawn_from({angle_vector(ang) * r, z}, MT_THOK); + }; + + auto vfx = [&](fixed_t f) + { + if (Mobj* x = spread({16, 32}, {0, 0})) + { + x->state(S_KART_FIRE); + x->lightlevel = 176; + x->renderflags |= RF_ABSOLUTELIGHTLEVEL | RF_SEMIBRIGHT | RF_REDUCEVFX; + } + + if (f < 3*FRACUNIT/4) + { + auto smoke = [&] + { + if (Mobj* x = spread({3, 6}, {0, 8})) + { + Fixed from = x->scale() / 3; + Fixed to = 5 * x->scale() / 4; + x->scale_between(from, to, (to - from) / 35); + x->state(S_KART_SMOKE); + x->lightlevel = -112; + x->momz = 16 * mapobjectscale; + } + }; + + smoke(); + smoke(); + } + }; + + UINT32 rf = RF_SEMIBRIGHT; + + if (burning() && P_IsObjectOnGround(this)) + { + fixed_t f = burning() * FRACUNIT / burn_duration(); + + if ((leveltime % std::max(1, Easing_OutCubic(f, 8, 1))) == 0) + { + vfx(f); + } + + if (f < 3*FRACUNIT/4) + { + auto spark = [&](int degr) + { + angle_t ang = angle + degr_to_angle(degr); + if (Particle* x = spawn_from({angle_vector(ang) * Fixed {radius}, 0}, MT_KART_PARTICLE)) + { + x->state(S_BRAKEDRIFT); + x->fuse = 12; + x->color = SKINCOLOR_PASTEL; + x->angle = ang - ANGLE_90; + x->scale(2 * x->scale() / 5); + x->flags |= MF_NOGRAVITY | MF_NOCLIPHEIGHT; + x->renderflags |= RF_ADD; + } + }; + + if (leveltime % 16 == 0) + { + radial_generic(45, 4, spark); + } + } + + if (leveltime & 1) + { + rf = RF_FULLBRIGHT; + } + + voice_loop(sfx_kc51); + } + + renderflags = (renderflags & ~RF_BRIGHTMASK) | rf; + } + + void animate() + { + Mobj* p = player(); + + if (!Mobj::valid(p)) + { + return; + } + + if (timer()) + { + // Vibration on the death sprite eases downward + p->exact_hitlag(Easing_InCubic(timer() * FRACUNIT / kVibrateTimer, 2, 90), true); + } + else + { + flags &= ~kNoClipFlags; + P_PlayDeathSound(p); + } + + // First tick after hitlag: destroyed kart appears! + if (state()->num() == S_INVISIBLE) + { + destroy(); + } + } + + static void P_SpawnBadnikExplosion(mobj_t *target) + { + UINT8 count = 24; + angle_t ang = 0; + angle_t step = ANGLE_MAX / count; + fixed_t spd = 8 * mapobjectscale; + for (UINT8 i = 0; i < count; ++i) + { + mobj_t *x = P_SpawnMobjFromMobjUnscaled( + target, + P_RandomRange(PR_EXPLOSION, -48, 48) * target->scale, + P_RandomRange(PR_EXPLOSION, -48, 48) * target->scale, + P_RandomRange(PR_EXPLOSION, -48, 48) * target->scale, + MT_THOK + ); + x->hitlag = 0; + P_InstaScale(x, 3 * x->scale / 2); + P_InstaThrust(x, ang, spd); + x->momz = P_RandomRange(PR_EXPLOSION, -4, 4) * mapobjectscale; + P_SetMobjStateNF(x, S_BADNIK_EXPLOSION1); + ang += step; + } + // burst effects (copied from MT_ITEMCAPSULE) + ang = FixedAngle(360*P_RandomFixed(PR_ITEM_DEBRIS)); + for (UINT8 i = 0; i < 2; i++) + { + mobj_t *blast = P_SpawnMobjFromMobj(target, 0, 0, target->info->height >> 1, MT_BATTLEBUMPER_BLAST); + blast->hitlag = 0; + blast->angle = ang + i*ANGLE_90; + P_SetScale(blast, 2*blast->scale/3); + blast->destscale = 6*blast->scale; + blast->scalespeed = (blast->destscale - blast->scale) / 30; + P_SetMobjStateNF(blast, static_cast(S_BADNIK_EXPLOSION_SHOCKWAVE1 + i)); + } + } +}; + +}; // namespace + +void Obj_SpawnDestroyedKart(mobj_t *player) +{ + Kart::spawn(static_cast(player)); +} + +void Obj_DestroyedKartThink(mobj_t *kart) +{ + static_cast(kart)->think(); +} + +boolean Obj_DestroyKart(mobj_t *kart) +{ + return static_cast(kart)->destroy(); +} + +void Obj_DestroyedKartParticleThink(mobj_t *part) +{ + static_cast(part)->think(); +} + +void Obj_DestroyedKartParticleLanding(mobj_t *part) +{ + static_cast(part)->on_land(); +} diff --git a/src/p_inter.c b/src/p_inter.c index e4f527a0b..de516865f 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1651,40 +1651,6 @@ boolean P_CheckRacers(void) return false; } -static void P_SpawnBadnikExplosion(mobj_t *target) -{ - UINT8 count = 24; - angle_t ang = 0; - angle_t step = ANGLE_MAX / count; - fixed_t spd = 8 * mapobjectscale; - for (UINT8 i = 0; i < count; ++i) - { - mobj_t *x = P_SpawnMobjFromMobjUnscaled( - target, - P_RandomRange(PR_EXPLOSION, -48, 48) * target->scale, - P_RandomRange(PR_EXPLOSION, -48, 48) * target->scale, - P_RandomRange(PR_EXPLOSION, -48, 48) * target->scale, - MT_THOK - ); - P_InstaScale(x, 3 * x->scale / 2); - P_InstaThrust(x, ang, spd); - x->momz = P_RandomRange(PR_EXPLOSION, -4, 4) * mapobjectscale; - P_SetMobjStateNF(x, S_BADNIK_EXPLOSION1); - ang += step; - } - // burst effects (copied from MT_ITEMCAPSULE) - ang = FixedAngle(360*P_RandomFixed(PR_ITEM_DEBRIS)); - for (UINT8 i = 0; i < 2; i++) - { - mobj_t *blast = P_SpawnMobjFromMobj(target, 0, 0, target->info->height >> 1, MT_BATTLEBUMPER_BLAST); - blast->angle = ang + i*ANGLE_90; - P_SetScale(blast, 2*blast->scale/3); - blast->destscale = 6*blast->scale; - blast->scalespeed = (blast->destscale - blast->scale) / 30; - P_SetMobjStateNF(blast, S_BADNIK_EXPLOSION_SHOCKWAVE1 + i); - } -} - /** Kills an object. * * \param target The victim. @@ -1885,31 +1851,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget { fixed_t flingSpeed = FixedHypot(target->momx, target->momy); angle_t flingAngle; - mobj_t *kart; target->fuse = TICRATE*3; // timer before mobj disappears from view (even if not an actual player) target->momx = target->momy = target->momz = 0; - kart = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_KART_LEFTOVER); - - if (kart && !P_MobjWasRemoved(kart)) - { - kart->angle = target->angle; - kart->color = target->color; - kart->hitlag = target->hitlag; - kart->eflags |= MFE_DAMAGEHITLAG; - P_SetObjectMomZ(kart, 6*FRACUNIT, false); - kart->extravalue1 = target->player->kartweight; - - // Copy interp data - kart->old_angle = target->old_angle; - kart->old_x = target->old_x; - kart->old_y = target->old_y; - kart->old_z = target->old_z; - - if (target->player->pflags & PF_NOCONTEST) - P_SetTarget(&target->tracer, kart); - } + Obj_SpawnDestroyedKart(target); if (source && !P_MobjWasRemoved(source)) { @@ -1951,16 +1897,6 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget boolean battle = (gametyperules & (GTR_BUMPERS | GTR_BOSS)) == GTR_BUMPERS; P_InstaThrust(target, flingAngle, max(flingSpeed, 6 * target->scale) / (battle ? 1 : 3)); P_SetObjectMomZ(target, battle ? 20*FRACUNIT : 18*FRACUNIT, false); - - P_PlayDeathSound(target); - - if (skins[target->player->skin].flags & SF_BADNIK) - { - P_SpawnBadnikExplosion(target); - target->spritexscale = 2*FRACUNIT; - target->spriteyscale = 2*FRACUNIT; - target->flags |= MF_NOSQUISH; - } } // Prisons Free Play: don't eliminate P1 for @@ -2549,6 +2485,9 @@ static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *sou static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 type) { + (void)inflictor; + (void)source; + const boolean beforeexit = !(player->exiting || (player->pflags & PF_NOCONTEST)); if (type == DMG_SPECTATOR && (G_GametypeHasTeams() || G_GametypeHasSpectators())) @@ -2667,7 +2606,7 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, } K_DropEmeraldsFromPlayer(player, player->emeralds); - K_SetHitLagForObjects(player->mo, inflictor, source, MAXHITLAGTICS, true); + //K_SetHitLagForObjects(player->mo, inflictor, source, MAXHITLAGTICS, true); player->carry = CR_NONE; @@ -2692,19 +2631,6 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, if (type == DMG_TIMEOVER) { - if (gametyperules & GTR_CIRCUIT) - { - mobj_t *boom; - - player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP); - player->mo->renderflags |= RF_DONTDRAW; - - boom = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FZEROBOOM); - boom->scale = player->mo->scale; - boom->angle = player->mo->angle; - P_SetTarget(&boom->target, player->mo); - } - player->pflags |= PF_ELIMINATED; } @@ -2893,6 +2819,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da case MT_ICECAPBLOCK: return Obj_TryCrateDamage(target, inflictor); + case MT_KART_LEFTOVER: + // intangible (do not let instawhip shred damage) + if (Obj_DestroyKart(target)) + return false; + + P_SetObjectMomZ(target, 12*FRACUNIT, false); + break; + default: break; } @@ -3462,7 +3396,8 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if ((damagetype & DMG_TYPEMASK) != DMG_WHUMBLE && (gametyperules & GTR_BUMPERS) && !battleprisons) laglength /= 2; - K_SetHitLagForObjects(target, inflictor, source, laglength, true); + if (!(target->player && (damagetype & DMG_DEATHMASK))) + K_SetHitLagForObjects(target, inflictor, source, laglength, true); target->flags2 |= MF2_ALREADYHIT; diff --git a/src/p_mobj.c b/src/p_mobj.c index 1fda781c6..ba938c21a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1282,6 +1282,10 @@ fixed_t P_GetMobjGravity(mobj_t *mo) if (mo->fuse) gravityadd /= 10; break; + case MT_KART_PARTICLE: + if (!mo->fuse) + gravityadd *= 2; + break; default: break; } @@ -2468,68 +2472,17 @@ boolean P_ZMovement(mobj_t *mo) mom.z = -mom.z; else if (mo->type == MT_KART_LEFTOVER) { - if (mo->health > 1) - { - const fixed_t tireOffset = 32; - const angle_t aOffset = ANGLE_22h; - - UINT8 i; - angle_t tireAngle; - mobj_t *tire; - - // Spawn tires! - mo->health = 1; - P_SetMobjState(mo, S_KART_LEFTOVER_NOTIRES); - - // Front tires - tireAngle = mo->angle - aOffset; - for (i = 0; i < 2; i++) - { - tire = P_SpawnMobjFromMobj( - mo, - tireOffset * FINECOSINE(tireAngle >> ANGLETOFINESHIFT), - tireOffset * FINESINE(tireAngle >> ANGLETOFINESHIFT), - 0, - MT_KART_TIRE - ); - - tire->angle = mo->angle; - tire->fuse = 3*TICRATE; - P_InstaThrust(tire, tireAngle, 4 * mo->scale); - P_SetObjectMomZ(tire, 4*FRACUNIT, false); - - tireAngle += (aOffset * 2); - } - - // Back tires - tireAngle = (mo->angle + ANGLE_180) - aOffset; - for (i = 0; i < 2; i++) - { - tire = P_SpawnMobjFromMobj( - mo, - tireOffset * FINECOSINE(tireAngle >> ANGLETOFINESHIFT), - tireOffset * FINESINE(tireAngle >> ANGLETOFINESHIFT), - 0, - MT_KART_TIRE - ); - - tire->angle = mo->angle; - tire->fuse = 3*TICRATE; - P_InstaThrust(tire, tireAngle, 4 * mo->scale); - P_SetObjectMomZ(tire, 4*FRACUNIT, false); - - P_SetMobjState(tire, S_KART_TIRE2); - - tireAngle += (aOffset * 2); - } - } - mom.z = 0; } else if (mo->type == MT_KART_TIRE) { mom.z = -mom.z; } + else if (mo->type == MT_KART_PARTICLE) + { + mom.z = -mom.z / (mo->fuse ? 1 : 2); + Obj_DestroyedKartParticleLanding(mo); + } else if (mo->type == MT_BIGTUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED || mo->type == MT_CANNONBALLDECOR @@ -9895,6 +9848,22 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; } + case MT_KART_PARTICLE: + { + Obj_DestroyedKartParticleThink(mobj); + break; + } + + case MT_KART_LEFTOVER: + { + Obj_DestroyedKartThink(mobj); + if (P_MobjWasRemoved(mobj)) + { + return false; + } + break; + } + default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -9958,6 +9927,7 @@ static boolean P_CanFlickerFuse(mobj_t *mobj) case MT_POGOSPRING: case MT_EMERALD: case MT_BLENDEYE_PUYO: + case MT_KART_PARTICLE: if (mobj->fuse <= TICRATE) { return true; diff --git a/src/p_user.c b/src/p_user.c index 1c55f0236..97781ea73 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2825,7 +2825,6 @@ static void P_DeathThink(player_t *player) if (player->mo) { player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP); - player->mo->renderflags |= RF_DONTDRAW; } } else diff --git a/src/sounds.c b/src/sounds.c index 7da94ee76..eb80574f6 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1529,6 +1529,12 @@ sfxinfo_t S_sfx[NUMSFX] = // Patching up base sounds {"s226l", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // s2 spikes LOUD + // Destroyed Kart + {"die00", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"die01", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"die02", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"die03", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // SRB2kart - Skin sounds {"kwin", false, 64, 96, -1, NULL, 0, SKSKWIN, -1, LUMPERROR, ""}, {"klose", false, 64, 96, -1, NULL, 0, SKSKLOSE, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index 66e5a3d18..9c361ba7e 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1605,6 +1605,12 @@ typedef enum // Patch-up sfx_s226l, + // Destroyed Kart + sfx_die00, + sfx_die01, + sfx_die02, + sfx_die03, + // And LASTLY, Kart's skin sounds. sfx_kwin, sfx_klose,