Merge branch 'destroy-kart' into 'master'

Destroyed Kart effects

Closes #1146

See merge request KartKrew/Kart!2196
This commit is contained in:
Oni 2024-03-31 18:45:30 +00:00
commit 531e4ccd4a
13 changed files with 681 additions and 148 deletions

View file

@ -370,6 +370,13 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_KART_TIRE1", "S_KART_TIRE1",
"S_KART_TIRE2", "S_KART_TIRE2",
"S_KART_FIRE",
"S_KART_SMOKE",
"S_KART_XPL01",
"S_KART_XPL02",
"S_KART_XPL03",
// Boss Explosion // Boss Explosion
"S_BOSSEXPLODE", "S_BOSSEXPLODE",
@ -3061,6 +3068,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_PLAYER", "MT_PLAYER",
"MT_KART_LEFTOVER", "MT_KART_LEFTOVER",
"MT_KART_TIRE", "MT_KART_TIRE",
"MT_KART_PARTICLE",
// Generic Boss Items // Generic Boss Items
"MT_BOSSEXPLODE", "MT_BOSSEXPLODE",

View file

@ -746,6 +746,22 @@ char sprnames[NUMSPRITES + 1][5] =
// Tutorial // Tutorial
"TLKP", // Talk Point "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 // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
"VIEW", "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_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_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_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_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_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_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, 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, 0, -1, {NULL}, 0, 0, S_NULL}, // S_KART_TIRE1
{SPR_TIRE, 1, -1, {NULL}, 0, 0, S_NULL}, // S_KART_TIRE2 {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 // Boss Explosion
{SPR_BOM2, FF_FULLBRIGHT|FF_ANIMATE, (5*7), {NULL}, 6, 5, S_NULL}, // S_BOSSEXPLODE {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 sfx_None, // painsound
S_NULL, // meleestate S_NULL, // meleestate
S_NULL, // missilestate S_NULL, // missilestate
S_KART_DEAD, // deathstate S_KART_SPINOUT, // deathstate
S_NULL, // xdeathstate S_NULL, // xdeathstate
sfx_None, // deathsound sfx_None, // deathsound
1, // speed 1, // speed
@ -3738,13 +3761,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
{ // MT_KART_LEFTOVER { // MT_KART_LEFTOVER
4095, // doomednum 4095, // doomednum
S_KART_LEFTOVER, // spawnstate S_KART_LEFTOVER_NOTIRES, // spawnstate
2, // spawnhealth 2, // spawnhealth
S_NULL, // seestate S_NULL, // seestate
sfx_None, // seesound sfx_None, // seesound
0, // reactiontime 0, // reactiontime
sfx_None, // attacksound sfx_None, // attacksound
S_NULL, // painstate S_KART_LEFTOVER_NOTIRES, // painstate
0, // painchance 0, // painchance
sfx_None, // painsound sfx_None, // painsound
S_NULL, // meleestate S_NULL, // meleestate
@ -3790,6 +3813,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL // raisestate 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 { // MT_BOSSEXPLODE
-1, // doomednum -1, // doomednum
S_BOSSEXPLODE, // spawnstate S_BOSSEXPLODE, // spawnstate

View file

@ -1281,6 +1281,22 @@ typedef enum sprite
// Tutorial // Tutorial
SPR_TLKP, // Talk Point 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 // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
SPR_VIEW, SPR_VIEW,
@ -1371,6 +1387,13 @@ typedef enum state
S_KART_TIRE1, S_KART_TIRE1,
S_KART_TIRE2, S_KART_TIRE2,
S_KART_FIRE,
S_KART_SMOKE,
S_KART_XPL01,
S_KART_XPL02,
S_KART_XPL03,
// Boss Explosion // Boss Explosion
S_BOSSEXPLODE, S_BOSSEXPLODE,
@ -4089,6 +4112,7 @@ typedef enum mobj_type
MT_PLAYER, MT_PLAYER,
MT_KART_LEFTOVER, MT_KART_LEFTOVER,
MT_KART_TIRE, MT_KART_TIRE,
MT_KART_PARTICLE,
// Generic Boss Items // Generic Boss Items
MT_BOSSEXPLODE, MT_BOSSEXPLODE,

View file

@ -83,6 +83,7 @@ void Obj_DuelBombInit(mobj_t *bomb);
/* Broly Ki */ /* Broly Ki */
mobj_t *Obj_SpawnBrolyKi(mobj_t *source, tic_t duration); 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); boolean Obj_BrolyKiThink(mobj_t *ki);
/* Special Stage UFO */ /* Special Stage UFO */
@ -417,6 +418,13 @@ void Obj_TalkPointOrbThink(mobj_t* mo);
void Obj_SpawnPowerUpSpinner(mobj_t *source, INT32 powerup, tic_t duration); void Obj_SpawnPowerUpSpinner(mobj_t *source, INT32 powerup, tic_t duration);
void Obj_TickPowerUpSpinner(mobj_t *mobj); 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 #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -1,6 +1,6 @@
// DR. ROBOTNIK'S RING RACERS // 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 // This program is free software distributed under the
// terms of the GNU General Public License, version 2. // terms of the GNU General Public License, version 2.
@ -157,7 +157,7 @@ struct Mobj : mobj_t
statenum_t num() const { return static_cast<statenum_t>(static_cast<const state_t*>(this) - states); } statenum_t num() const { return static_cast<statenum_t>(static_cast<const state_t*>(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<const State*>(mobj_t::state); } const State* state() const { return static_cast<const State*>(mobj_t::state); }
@ -276,6 +276,11 @@ struct Mobj : mobj_t
{ {
K_SetHitLagForObjects(this, inflictor, source, tics, damage); 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 }; // namespace srb2

View file

@ -56,6 +56,7 @@ target_sources(SRB2SDL2 PRIVATE
talk-point.cpp talk-point.cpp
powerup-spinner.cpp powerup-spinner.cpp
adventure-air-booster.c adventure-air-booster.c
destroyed-kart.cpp
) )
add_subdirectory(versus) add_subdirectory(versus)

View file

@ -1,6 +1,6 @@
// DR. ROBOTNIK'S RING RACERS // 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 // This program is free software distributed under the
// terms of the GNU General Public License, version 2. // terms of the GNU General Public License, version 2.
@ -16,11 +16,13 @@
using namespace srb2::objects; using namespace srb2::objects;
mobj_t * mobj_t *
Obj_SpawnBrolyKi Obj_SpawnCustomBrolyKi
( mobj_t * source, ( mobj_t * source,
tic_t duration) tic_t duration,
fixed_t start,
fixed_t end)
{ {
Broly* x = Broly::spawn<Broly>(static_cast<Mobj*>(source), duration, {64 * mapobjectscale, 0}); Broly* x = Broly::spawn<Broly>(static_cast<Mobj*>(source), duration, {start, end});
if (!x) if (!x)
{ {
@ -36,6 +38,14 @@ Obj_SpawnBrolyKi
return x; return x;
} }
mobj_t *
Obj_SpawnBrolyKi
( mobj_t * source,
tic_t duration)
{
return Obj_SpawnCustomBrolyKi(source, duration, 64 * mapobjectscale, 0);
}
boolean boolean
Obj_BrolyKiThink (mobj_t *x) Obj_BrolyKiThink (mobj_t *x)
{ {

View file

@ -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 <algorithm>
#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<Fixed> angle_vector(angle_t x)
{
return Vec2<Fixed> {FCOS(x), FSIN(x)};
}
template <typename F>
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<int>& momz)
{
Particle* x = source->spawn_from<Particle>({}, 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<Mobj>({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<Mobj>({}, 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<sfxenum_t>(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<Kart>({}, 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<Fixed>& range, const Vec2<Fixed>& 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<Mobj>({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<Particle>({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<statenum_t>(S_BADNIK_EXPLOSION_SHOCKWAVE1 + i));
}
}
};
}; // namespace
void Obj_SpawnDestroyedKart(mobj_t *player)
{
Kart::spawn(static_cast<Mobj*>(player));
}
void Obj_DestroyedKartThink(mobj_t *kart)
{
static_cast<Kart*>(kart)->think();
}
boolean Obj_DestroyKart(mobj_t *kart)
{
return static_cast<Kart*>(kart)->destroy();
}
void Obj_DestroyedKartParticleThink(mobj_t *part)
{
static_cast<Particle*>(part)->think();
}
void Obj_DestroyedKartParticleLanding(mobj_t *part)
{
static_cast<Particle*>(part)->on_land();
}

View file

@ -1656,40 +1656,6 @@ boolean P_CheckRacers(void)
return false; 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. /** Kills an object.
* *
* \param target The victim. * \param target The victim.
@ -1890,31 +1856,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
{ {
fixed_t flingSpeed = FixedHypot(target->momx, target->momy); fixed_t flingSpeed = FixedHypot(target->momx, target->momy);
angle_t flingAngle; angle_t flingAngle;
mobj_t *kart;
target->fuse = TICRATE*3; // timer before mobj disappears from view (even if not an actual player) target->fuse = TICRATE*3; // timer before mobj disappears from view (even if not an actual player)
target->momx = target->momy = target->momz = 0; target->momx = target->momy = target->momz = 0;
kart = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_KART_LEFTOVER); Obj_SpawnDestroyedKart(target);
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);
}
if (source && !P_MobjWasRemoved(source)) if (source && !P_MobjWasRemoved(source))
{ {
@ -1956,16 +1902,6 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
boolean battle = (gametyperules & (GTR_BUMPERS | GTR_BOSS)) == GTR_BUMPERS; boolean battle = (gametyperules & (GTR_BUMPERS | GTR_BOSS)) == GTR_BUMPERS;
P_InstaThrust(target, flingAngle, max(flingSpeed, 6 * target->scale) / (battle ? 1 : 3)); P_InstaThrust(target, flingAngle, max(flingSpeed, 6 * target->scale) / (battle ? 1 : 3));
P_SetObjectMomZ(target, battle ? 20*FRACUNIT : 18*FRACUNIT, false); 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 // Prisons Free Play: don't eliminate P1 for
@ -2554,6 +2490,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) 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)); const boolean beforeexit = !(player->exiting || (player->pflags & PF_NOCONTEST));
if (type == DMG_SPECTATOR && (G_GametypeHasTeams() || G_GametypeHasSpectators())) if (type == DMG_SPECTATOR && (G_GametypeHasTeams() || G_GametypeHasSpectators()))
@ -2672,7 +2611,7 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source,
} }
K_DropEmeraldsFromPlayer(player, player->emeralds); 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; player->carry = CR_NONE;
@ -2697,19 +2636,6 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source,
if (type == DMG_TIMEOVER) 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; player->pflags |= PF_ELIMINATED;
} }
@ -2898,6 +2824,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
case MT_ICECAPBLOCK: case MT_ICECAPBLOCK:
return Obj_TryCrateDamage(target, inflictor); 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: default:
break; break;
} }
@ -3467,7 +3401,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) if ((damagetype & DMG_TYPEMASK) != DMG_WHUMBLE && (gametyperules & GTR_BUMPERS) && !battleprisons)
laglength /= 2; 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; target->flags2 |= MF2_ALREADYHIT;

View file

@ -1282,6 +1282,10 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
if (mo->fuse) if (mo->fuse)
gravityadd /= 10; gravityadd /= 10;
break; break;
case MT_KART_PARTICLE:
if (!mo->fuse)
gravityadd *= 2;
break;
default: default:
break; break;
} }
@ -2468,68 +2472,17 @@ boolean P_ZMovement(mobj_t *mo)
mom.z = -mom.z; mom.z = -mom.z;
else if (mo->type == MT_KART_LEFTOVER) 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; mom.z = 0;
} }
else if (mo->type == MT_KART_TIRE) else if (mo->type == MT_KART_TIRE)
{ {
mom.z = -mom.z; 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 else if (mo->type == MT_BIGTUMBLEWEED
|| mo->type == MT_LITTLETUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED
|| mo->type == MT_CANNONBALLDECOR || mo->type == MT_CANNONBALLDECOR
@ -9895,6 +9848,22 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
break; break;
} }
case MT_KART_PARTICLE:
{
Obj_DestroyedKartParticleThink(mobj);
break;
}
case MT_KART_LEFTOVER:
{
Obj_DestroyedKartThink(mobj);
if (P_MobjWasRemoved(mobj))
{
return false;
}
break;
}
default: default:
// check mobj against possible water content, before movement code // check mobj against possible water content, before movement code
P_MobjCheckWater(mobj); P_MobjCheckWater(mobj);
@ -9958,6 +9927,7 @@ static boolean P_CanFlickerFuse(mobj_t *mobj)
case MT_POGOSPRING: case MT_POGOSPRING:
case MT_EMERALD: case MT_EMERALD:
case MT_BLENDEYE_PUYO: case MT_BLENDEYE_PUYO:
case MT_KART_PARTICLE:
if (mobj->fuse <= TICRATE) if (mobj->fuse <= TICRATE)
{ {
return true; return true;

View file

@ -2828,7 +2828,6 @@ static void P_DeathThink(player_t *player)
if (player->mo) if (player->mo)
{ {
player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP); player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP);
player->mo->renderflags |= RF_DONTDRAW;
} }
} }
else else

View file

@ -1529,6 +1529,12 @@ sfxinfo_t S_sfx[NUMSFX] =
// Patching up base sounds // Patching up base sounds
{"s226l", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // s2 spikes LOUD {"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 // SRB2kart - Skin sounds
{"kwin", false, 64, 96, -1, NULL, 0, SKSKWIN, -1, LUMPERROR, ""}, {"kwin", false, 64, 96, -1, NULL, 0, SKSKWIN, -1, LUMPERROR, ""},
{"klose", false, 64, 96, -1, NULL, 0, SKSKLOSE, -1, LUMPERROR, ""}, {"klose", false, 64, 96, -1, NULL, 0, SKSKLOSE, -1, LUMPERROR, ""},

View file

@ -1605,6 +1605,12 @@ typedef enum
// Patch-up // Patch-up
sfx_s226l, sfx_s226l,
// Destroyed Kart
sfx_die00,
sfx_die01,
sfx_die02,
sfx_die03,
// And LASTLY, Kart's skin sounds. // And LASTLY, Kart's skin sounds.
sfx_kwin, sfx_kwin,
sfx_klose, sfx_klose,