Merge branch 'gacha-rebound' into 'master'

Gachabom rebound

See merge request KartKrew/Kart!1286
This commit is contained in:
Oni 2023-06-14 02:31:23 +00:00
commit ce2ea138b4
8 changed files with 318 additions and 3 deletions

View file

@ -4556,6 +4556,15 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_SPECIAL_UFO_STEM",
"S_GACHABOM",
"S_GACHABOM_DEAD",
"S_GACHABOM_EXPLOSION_1",
"S_GACHABOM_EXPLOSION_2",
"S_GACHABOM_EXPLOSION_3A",
"S_GACHABOM_EXPLOSION_3B",
"S_GACHABOM_EXPLOSION_4",
"S_GACHABOM_WAITING",
"S_GACHABOM_RETURNING",
};
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@ -5415,6 +5424,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_SINKTRAIL",
"MT_GACHABOM",
"MT_GACHABOM_REBOUND",
"MT_DUELBOMB", // Duel mode bombs

View file

@ -810,6 +810,7 @@ char sprnames[NUMSPRITES + 1][5] =
"UQMK",
"GBOM",
"GCHX",
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
"VIEW",
@ -5216,6 +5217,15 @@ state_t states[NUMSTATES] =
{SPR_UFOS, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SPECIAL_UFO_STEM
{SPR_GBOM, FF_ANIMATE, -1, {NULL}, 3, 1, S_NULL}, // S_GACHABOM
{SPR_GBOM, FF_INVERT, 2, {NULL}, 0, 0, S_NULL}, // S_GACHABOM_DEAD
{SPR_NULL, 0, 1, {NULL}, 0, 0, S_GACHABOM_EXPLOSION_2},
{SPR_GCHX, 0|FF_PAPERSPRITE|FF_ANIMATE, 14, {NULL}, 6, 2, S_GACHABOM_EXPLOSION_3A}, // S_GACHABOM_EXPLOSION_2
{SPR_GCHX, 6|FF_PAPERSPRITE|FF_ANIMATE, 4, {NULL}, 1, 2, S_GACHABOM_EXPLOSION_3B}, // S_GACHABOM_EXPLOSION_3A
{SPR_NULL, 0|FF_PAPERSPRITE, 0, {A_Repeat}, 8, S_GACHABOM_EXPLOSION_3A, S_GACHABOM_EXPLOSION_4}, // S_GACHABOM_EXPLOSION_3B
{SPR_GCHX, 6|FF_PAPERSPRITE|FF_ANIMATE|FF_REVERSEANIM, 14, {NULL}, 6, 2, S_GACHABOM_WAITING}, // S_GACHABOM_EXPLOSION_4
{SPR_GBOM, FF_INVERT, 8, {A_SetScale}, FRACUNIT, 0, S_GACHABOM_RETURNING}, // S_GACHABOM_WAITING
{SPR_GBOM, FF_INVERT, -1, {A_SetScale}, FRACUNIT/2, 1, S_NULL}, // S_GACHABOM_RETURNING
};
mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@ -24573,7 +24583,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_GACHABOM, // deathstate
S_GACHABOM_DEAD, // deathstate
S_NULL, // xdeathstate
sfx_s3k5d, // deathsound
28*FRACUNIT, // speed
@ -24587,6 +24597,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL // raisestate
},
{ // MT_GACHABOM_REBOUND
-1, // doomednum
S_GACHABOM_EXPLOSION_1, // spawnstate
1, // 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_GACHABOM_DEAD, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
16*FRACUNIT, // radius
32*FRACUNIT, // height
0, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_DONTENCOREMAP|MF_NOSQUISH, // flags
S_NULL // raisestate
},
{ // MT_DUELBOMB
2050, // doomednum
S_SPB1, // spawnstate

View file

@ -1363,6 +1363,7 @@ typedef enum sprite
SPR_UQMK,
SPR_GBOM,
SPR_GCHX,
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
SPR_VIEW,
@ -5647,6 +5648,15 @@ typedef enum state
S_SPECIAL_UFO_STEM,
S_GACHABOM,
S_GACHABOM_DEAD,
S_GACHABOM_EXPLOSION_1,
S_GACHABOM_EXPLOSION_2,
S_GACHABOM_EXPLOSION_3A,
S_GACHABOM_EXPLOSION_3B,
S_GACHABOM_EXPLOSION_4,
S_GACHABOM_WAITING,
S_GACHABOM_RETURNING,
S_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
@ -6525,6 +6535,7 @@ typedef enum mobj_type
MT_SINKTRAIL,
MT_GACHABOM,
MT_GACHABOM_REBOUND,
MT_DUELBOMB, // Duel mode bombs

View file

@ -133,6 +133,10 @@ boolean Obj_RandomItemSpawnIn(mobj_t *mobj);
fixed_t Obj_RandomItemScale(fixed_t oldScale);
void Obj_RandomItemSpawn(mobj_t *mobj);
/* Gachabom Rebound */
void Obj_GachaBomReboundThink(mobj_t *mobj);
void Obj_SpawnGachaBomRebound(mobj_t *source, mobj_t *target);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -19,4 +19,5 @@ target_sources(SRB2SDL2 PRIVATE
random-item.c
instawhip.c
block.c
gachabom-rebound.cpp
)

View file

@ -0,0 +1,233 @@
#include <algorithm>
#include "../d_player.h"
#include "../k_objects.h"
#include "../m_fixed.h"
#include "../info.h"
#include "../p_local.h"
#include "../r_main.h"
#include "../tables.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 rebound_target(o) ((o)->target)
#define rebound_mode(o) ((o)->threshold)
#define rebound_timer(o) ((o)->reactiontime)
namespace
{
constexpr int kReboundSpeed = 128;
constexpr int kReboundAcceptPause = 17;
constexpr int kReboundAcceptDuration = 8;
constexpr int kOrbitRadius = 0;
enum class Mode : int
{
kAlpha = 0,
kBeta,
kOrbit,
};
fixed_t z_center(const mobj_t* mobj)
{
return mobj->z + (mobj->height / 2);
}
bool rebound_is_returning(const mobj_t* mobj)
{
return mobj->state == &states[S_GACHABOM_RETURNING];
}
fixed_t distance_to_target(const mobj_t* mobj)
{
const mobj_t* target = rebound_target(mobj);
const fixed_t zDelta = z_center(target) - mobj->z;
return FixedHypot(FixedHypot(target->x - mobj->x, target->y - mobj->y), zDelta);
}
bool award_target(mobj_t* mobj)
{
mobj_t* target = rebound_target(mobj);
player_t* player = target->player;
if (mobj->fuse)
{
return false;
}
if (player == nullptr)
{
return true;
}
if ((player->itemtype == KITEM_GACHABOM || player->itemtype == KITEM_NONE) && !player->itemRoulette.active)
{
rebound_timer(mobj)--;
if (rebound_timer(mobj) < 1)
{
player->itemtype = KITEM_GACHABOM;
player->itemamount++;
return true;
}
}
return false;
}
void chase_rebound_target(mobj_t* mobj)
{
const mobj_t* target = rebound_target(mobj);
const fixed_t zDelta = z_center(target) - mobj->z;
const fixed_t distance = distance_to_target(mobj);
const fixed_t travelDistance = kReboundSpeed * mapobjectscale;
if (distance <= travelDistance)
{
rebound_mode(mobj) = static_cast<int>(Mode::kOrbit);
// Freeze
mobj->momx = 0;
mobj->momy = 0;
mobj->momz = 0;
}
else
{
const angle_t facing = R_PointToAngle2(mobj->x, mobj->y, target->x, target->y);
P_InstaThrust(mobj, facing, travelDistance);
mobj->angle = facing;
// This has a nice effect of "jumping up" rather quickly
mobj->momz = zDelta / 4;
const tic_t t = distance_to_target(mobj) / travelDistance;
const fixed_t newSpeed = std::abs(mobj->scale - mobj->destscale) / std::max(t, 1u);
if (newSpeed > mobj->scalespeed)
{
mobj->scalespeed = newSpeed;
}
}
}
void orbit_target(mobj_t* mobj)
{
if (award_target(mobj))
{
mobj->fuse = kReboundAcceptDuration + BUFFER_TICS;
mobj->destscale = 0;
mobj->scalespeed = mobj->scale / kReboundAcceptDuration;
}
const mobj_t* target = rebound_target(mobj);
const fixed_t rad = (2 * (mobj->radius + target->radius)) + (kOrbitRadius * mapobjectscale);
P_MoveOrigin(mobj,
target->x - FixedMul(FCOS(mobj->angle), rad),
target->y - FixedMul(FSIN(mobj->angle), rad),
target->z + (target->height / 2));
constexpr angle_t kOrbitSpeed = ANGLE_MAX / (kReboundAcceptPause + kReboundAcceptDuration);
mobj->angle -= 2 * kOrbitSpeed;
}
// Copied from MT_BANANA_SPARK
void squish(mobj_t* mobj)
{
if (leveltime & 1)
{
mobj->spritexscale = mobj->spriteyscale = FRACUNIT;
}
else
{
if ((leveltime / 2) & 1)
{
mobj->spriteyscale = 3*FRACUNIT;
}
else
{
mobj->spritexscale = 3*FRACUNIT;
}
}
}
void spawn_afterimages(mobj_t* mobj)
{
mobj_t* ghost = P_SpawnGhostMobj(mobj);
// Flickers every frame
ghost->extravalue1 = 1;
ghost->extravalue2 = 2;
// No transparency
ghost->renderflags = 0;
ghost->tics = 8;
}
}; // namespace
void Obj_GachaBomReboundThink(mobj_t* mobj)
{
if (P_MobjWasRemoved(rebound_target(mobj)))
{
P_RemoveMobj(mobj);
return;
}
if (static_cast<Mode>(rebound_mode(mobj)) == Mode::kOrbit)
{
// Ready to be delivered to the player
orbit_target(mobj);
squish(mobj);
}
else if (rebound_is_returning(mobj))
{
// Travelling back
chase_rebound_target(mobj);
squish(mobj);
spawn_afterimages(mobj);
}
// Now the gummy animation is over
if (mobj->sprite == SPR_GBOM)
{
if (static_cast<Mode>(rebound_mode(mobj)) == Mode::kBeta)
{
// Only the alpha object remains
P_RemoveMobj(mobj);
return;
}
}
}
void Obj_SpawnGachaBomRebound(mobj_t* source, mobj_t* target)
{
auto spawn = [&](angle_t angle, Mode mode)
{
mobj_t *x = P_SpawnMobjFromMobjUnscaled(source, 0, 0, target->height / 2, MT_GACHABOM_REBOUND);
x->color = target->color;
x->angle = angle;
P_InstaScale(x, 2 * x->scale);
rebound_mode(x) = static_cast<int>(mode);
rebound_timer(x) = kReboundAcceptPause;
P_SetTarget(&rebound_target(x), target);
};
spawn(0, Mode::kAlpha);
spawn(ANGLE_45, Mode::kBeta);
spawn(ANGLE_90, Mode::kBeta);
spawn(ANGLE_135, Mode::kBeta);
}

View file

@ -277,9 +277,20 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
S_StartSound(t1, t1->info->deathsound);
P_KillMobj(t1, t2, t2, DMG_NORMAL);
P_SetObjectMomZ(t1, 24*FRACUNIT, false);
if (t1->type == MT_GACHABOM)
{
// Instead of flying out at an angle when
// destroyed, spawn an explosion and eventually
// return to sender. The original Gachabom will be
// removed next tic (see deathstate).
Obj_SpawnGachaBomRebound(t1, orbinaut_owner(t1));
}
else
{
P_SetObjectMomZ(t1, 24*FRACUNIT, false);
P_InstaThrust(t1, bounceangle, 16*FRACUNIT);
P_InstaThrust(t1, bounceangle, 16*FRACUNIT);
}
}
if (sprung)

View file

@ -9522,6 +9522,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
}
break;
case MT_GACHABOM_REBOUND:
Obj_GachaBomReboundThink(mobj);
if (P_MobjWasRemoved(mobj))
{
return false;
}
break;
default:
// check mobj against possible water content, before movement code
P_MobjCheckWater(mobj);