Gachabom rebound

- Gachabom hits an object and is destroyed immediately
  (does not bounce off like Orbinauts or Jawz).
- Gachabom explosion spawns in its place.
- (New) Gachabom physically returns to the player who
  threw it.
This commit is contained in:
James R 2023-06-13 14:38:07 -07:00
parent 74e3ea89d6
commit 696e9e09c9
6 changed files with 260 additions and 3 deletions

View file

@ -24583,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

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);