From 643cf46b61087c00ca481a6cd32d9437d22c96ee Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 26 Sep 2023 08:30:41 -0400 Subject: [PATCH] Ball switch --- src/deh_tables.c | 8 ++ src/info.c | 61 ++++++++++ src/info.h | 10 ++ src/k_collide.cpp | 8 +- src/k_objects.h | 6 + src/objects/CMakeLists.txt | 1 + src/objects/ball-switch.cpp | 237 ++++++++++++++++++++++++++++++++++++ src/p_inter.c | 12 ++ src/p_mobj.c | 8 ++ 9 files changed, 345 insertions(+), 6 deletions(-) create mode 100644 src/objects/ball-switch.cpp diff --git a/src/deh_tables.c b/src/deh_tables.c index 7a730b6ce..0272e605b 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4671,6 +4671,11 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_CHECKPOINT_SPARK9", "S_CHECKPOINT_SPARK10", "S_CHECKPOINT_SPARK11", + + "S_BALLSWITCH_BALL", + "S_BALLSWITCH_BALL_ACTIVE", + "S_BALLSWITCH_PAD", + "S_BALLSWITCH_PAD_ACTIVE", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -5823,6 +5828,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_CHECKPOINT_END", "MT_SCRIPT_THING", + + "MT_BALLSWITCH_BALL", + "MT_BALLSWITCH_PAD", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index 0a311d369..06c9b9ee5 100644 --- a/src/info.c +++ b/src/info.c @@ -892,6 +892,8 @@ char sprnames[NUMSPRITES + 1][5] = "CPT2", // Checkpoint Stick "CPT3", // Checkpoint Base + "SA2S", // SA2-style Ball Switch + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5417,6 +5419,11 @@ state_t states[NUMSTATES] = {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|8, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK10}, // S_CHECKPOINT_SPARK9 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|3, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK11}, // S_CHECKPOINT_SPARK10 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK1}, // S_CHECKPOINT_SPARK11 + + {SPR_SA2S, FF_SEMIBRIGHT|3, -1, {NULL}, 0, 0, S_NULL}, // S_BALLSWITCH_BALL + {SPR_SA2S, FF_FULLBRIGHT|FF_ANIMATE|FF_GLOBALANIM|4, -1, {NULL}, 1, 1, S_NULL}, // S_BALLSWITCH_BALL_ACTIVE + {SPR_SA2S, FF_FLOORSPRITE|FF_SEMIBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BALLSWITCH_PAD + {SPR_SA2S, FF_FLOORSPRITE|FF_FULLBRIGHT|FF_ANIMATE|FF_GLOBALANIM|1, -1, {NULL}, 1, 1, S_NULL}, // S_BALLSWITCH_PAD_ACTIVE }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -30381,6 +30388,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags S_NULL // raisestate }, + + { // MT_BALLSWITCH_BALL + 5000, // doomednum + S_BALLSWITCH_BALL, // spawnstate + 1000, // 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 + 36*FRACUNIT, // radius + 64*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BALLSWITCH_PAD + -1, // doomednum + S_BALLSWITCH_PAD, // spawnstate + 1000, // 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 + 64*FRACUNIT, // radius + 16*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags + S_NULL // raisestate + }, }; skincolor_t skincolors[MAXSKINCOLORS] = { diff --git a/src/info.h b/src/info.h index 1a4054638..f21e4d359 100644 --- a/src/info.h +++ b/src/info.h @@ -1446,6 +1446,8 @@ typedef enum sprite SPR_CPT2, // Checkpoint Stick SPR_CPT3, // Checkpoint Base + SPR_SA2S, // SA2-style Ball Switch + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -5845,6 +5847,11 @@ typedef enum state S_CHECKPOINT_SPARK10, S_CHECKPOINT_SPARK11, + S_BALLSWITCH_BALL, + S_BALLSWITCH_BALL_ACTIVE, + S_BALLSWITCH_PAD, + S_BALLSWITCH_PAD_ACTIVE, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -7016,6 +7023,9 @@ typedef enum mobj_type MT_CHECKPOINT_END, MT_SCRIPT_THING, + MT_BALLSWITCH_BALL, + MT_BALLSWITCH_PAD, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_collide.cpp b/src/k_collide.cpp index b897873a8..e743776bc 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -931,16 +931,12 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) } else { - if (victim->type == MT_ORBINAUT || victim->type == MT_JAWZ || victim->type == MT_GACHABOM - || victim->type == MT_BANANA || victim->type == MT_EGGMANITEM || victim->type == MT_BALLHOG - || victim->type == MT_SSMINE || victim->type == MT_LANDMINE || victim->type == MT_SINK - || victim->type == MT_GARDENTOP || victim->type == MT_DROPTARGET || victim->type == MT_BATTLECAPSULE - || victim->type == MT_MONITOR || victim->type == MT_SPECIAL_UFO || victim->type == MT_BATTLEUFO) + if (victim->flags & MF_SHOOTABLE) { // Monitor hack. We can hit monitors once per instawhip, no multihit shredding! // Damage values in Obj_MonitorGetDamage. // Apply to UFO also -- steelt 29062023 - if (victim->type == MT_MONITOR || victim->type == MT_BATTLEUFO) + if (victim->type == MT_MONITOR || victim->type == MT_BATTLEUFO || victim->type == MT_BALLSWITCH_BALL) { if (shield->extravalue1 == 1) return false; diff --git a/src/k_objects.h b/src/k_objects.h index d5e06e8e8..78df2bb62 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -233,6 +233,12 @@ boolean Obj_GetCheckpointRespawnPosition(const mobj_t *checkpoint, vector3_t *re angle_t Obj_GetCheckpointRespawnAngle(const mobj_t *checkpoint); void Obj_ActivateCheckpointInstantly(mobj_t* mobj); +/* Ball Switch */ +void Obj_BallSwitchInit(mobj_t *mobj); +void Obj_BallSwitchThink(mobj_t *mobj); +void Obj_BallSwitchTouched(mobj_t *mobj, mobj_t *toucher); +void Obj_BallSwitchDamaged(mobj_t *mobj, mobj_t *inflictor, mobj_t *source); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 89d5a17c1..694588783 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -30,4 +30,5 @@ target_sources(SRB2SDL2 PRIVATE emerald.c checkpoint.cpp shadow.cpp + ball-switch.cpp ) diff --git a/src/objects/ball-switch.cpp b/src/objects/ball-switch.cpp new file mode 100644 index 000000000..c272452a0 --- /dev/null +++ b/src/objects/ball-switch.cpp @@ -0,0 +1,237 @@ +#include "../k_objects.h" + +#include "../doomdef.h" +#include "../info.h" +#include "../p_local.h" +#include "../r_main.h" +#include "../k_hitlag.h" + +#define ball_pad(o) ((o)->target) +#define ball_instawhipped(o) ((o)->extravalue1) // see instawhip collide + +#define ball_cooldown(o) ((o)->cvmem) + +#define ball_activedefer(o) ((o)->extravalue2) +#define ball_activator(o) ((o)->tracer) + +namespace +{ + +struct BallSwitch_Pad : mobj_t +{ + statenum_t Anim() const { return static_cast(this->state - states); } + void Anim(statenum_t n) + { + if (Anim() != n) + { + P_SetMobjState(this, n); + } + } + + void Spawned() + { + renderflags |= RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_SLOPESPLAT|RF_NOSPLATROLLANGLE; + } + + void Tick(boolean active) + { + if (active == true) + { + Anim(S_BALLSWITCH_PAD_ACTIVE); + } + else + { + Anim(S_BALLSWITCH_PAD); + } + } +}; + +struct BallSwitch_Ball : mobj_t +{ + BallSwitch_Pad *Pad() const { return static_cast( ball_pad(this) ); } + void Pad(BallSwitch_Pad *n) { P_SetTarget(&ball_pad(this), n); } + + statenum_t Anim() const { return static_cast(this->state - states); } + void Anim(statenum_t n) + { + if (Anim() != n) + { + P_SetMobjState(this, n); + } + } + + INT32 Cooldown() const { return ball_cooldown(this); } + void Cooldown(INT32 n) { ball_cooldown(this) = n; } + boolean Active() const { return (ball_cooldown(this) != 0); } + + boolean DeferActivation() const { return ball_activedefer(this); } + mobj_t *Activator() const { return ball_activator(this); } + + void DeferActivation(boolean n, mobj_t *src) + { + ball_activedefer(this) = n; + P_SetTarget(&ball_activator(this), src); + } + + SINT8 IntSign(int value) const + { + if (value > 0) + { + return 1; + } + + if (value < 0) + { + return -1; + } + + return 0; + } + + void Spawned() + { + Pad( static_cast( P_SpawnMobjFromMobj(this, 0, 0, 0, MT_BALLSWITCH_PAD) ) ); + Pad()->Spawned(); + + this->z += Pad()->height * P_MobjFlip(this); + } + + void Tick() + { + if (P_MobjWasRemoved(Pad()) == true) + { + P_RemoveMobj(this); + return; + } + + ball_instawhipped(this) = 0; + + if (DeferActivation() == true) + { + P_ActivateThingSpecial(this, Activator()); + Cooldown(-1); // maybe later? + DeferActivation(false, nullptr); + } + + fixed_t ourZ = P_GetMobjFeet(this); + fixed_t theirZ = P_GetMobjHead(Pad()); + + fixed_t dist = P_AproxDistance(P_AproxDistance(Pad()->x - this->x, Pad()->y - this->y), theirZ - ourZ); + fixed_t move = P_AproxDistance(P_AproxDistance(this->momx, this->momy), this->momz); + + constexpr INT32 accelScale = 4; + + if (dist < accelScale * this->scale && move < accelScale * this->scale) + { + P_SetOrigin(this, Pad()->x, Pad()->y, theirZ); + this->momx = this->momy = this->momz = 0; + } + else + { + static constexpr const INT32 accel[2] = { FRACUNIT*3/4, FRACUNIT*3/16 }; + constexpr fixed_t frict = FRACUNIT*99/100; + + this->momx = FixedMul(this->momx, frict); + this->momy = FixedMul(this->momy, frict); + this->momz = FixedMul(this->momz, frict); + + SINT8 xSign = IntSign(Pad()->x - this->x); + SINT8 ySign = IntSign(Pad()->y - this->y); + SINT8 zSign = IntSign(theirZ - ourZ); + + boolean xAway = (IntSign(this->momx) == xSign); + boolean yAway = (IntSign(this->momy) == ySign); + boolean zAway = (IntSign(this->momz) == zSign); + + this->momx += FixedMul(accel[xAway], accelScale * this->scale) * xSign; + this->momy += FixedMul(accel[yAway], accelScale * this->scale) * ySign; + this->momz += FixedMul(accel[zAway], accelScale * this->scale) * zSign; + + this->angle += FixedAngle(move * 2); + if (dist > this->radius * 2) + { + P_Thrust(this, this->angle, (move / accelScale) * 2 / 3); + } + } + + if (Active() == true) + { + INT32 cool = Cooldown(); + if (cool > 0) + { + Cooldown(cool - 1); + } + + Anim(S_BALLSWITCH_BALL_ACTIVE); + } + else + { + Anim(S_BALLSWITCH_BALL); + } + + Pad()->Tick(Active()); + } + + void Push(mobj_t *toucher, const fixed_t pushValue, const fixed_t repelValue) + { + fixed_t push = FixedMul(pushValue, toucher->scale); + fixed_t repel = FixedMul(repelValue, this->scale); + + angle_t thrustAngle = R_PointToAngle2(toucher->x, toucher->y, this->x, this->y); + fixed_t thrustAngleCos = FINECOSINE(thrustAngle >> ANGLETOFINESHIFT); + fixed_t thrustAngleSin = FINESINE(thrustAngle >> ANGLETOFINESHIFT); + + fixed_t thisZ = this->z + (this->height / 2); + fixed_t toucherZ = toucher->z + (toucher->height / 2); + + angle_t thrustPitch = R_PointToAngle2(0, toucherZ, R_PointToDist2(toucher->x, toucher->y, this->x, this->y), thisZ); + fixed_t thrustPitchCos = FINECOSINE(thrustPitch >> ANGLETOFINESHIFT); + fixed_t thrustPitchSin = FINESINE(thrustPitch >> ANGLETOFINESHIFT); + + this->momx += FixedMul(FixedMul(push, thrustAngleCos), thrustPitchCos); + this->momy += FixedMul(FixedMul(push, thrustAngleSin), thrustPitchCos); + this->momz += FixedMul(push, thrustPitchSin); + + toucher->momx -= FixedMul(FixedMul(repel, thrustAngleCos), thrustPitchCos); + toucher->momy -= FixedMul(FixedMul(repel, thrustAngleSin), thrustPitchCos); + toucher->momz -= FixedMul(repel, thrustPitchSin); + } + + void Touch(mobj_t *toucher) + { + Push(toucher, 4 << FRACBITS, 6 << FRACBITS); + } + + void Hit(mobj_t *inflictor, mobj_t *source) + { + Push(inflictor, 64 << FRACBITS, 1 << FRACBITS); + K_SetHitLagForObjects(this, inflictor, source, 4, true); + DeferActivation(true, source); + } +}; + +}; // namespace + +void Obj_BallSwitchInit(mobj_t *mobj) +{ + BallSwitch_Ball *ball = static_cast(mobj); + ball->Spawned(); +} + +void Obj_BallSwitchThink(mobj_t *mobj) +{ + BallSwitch_Ball *ball = static_cast(mobj); + ball->Tick(); +} + +void Obj_BallSwitchTouched(mobj_t *mobj, mobj_t *toucher) +{ + BallSwitch_Ball *ball = static_cast(mobj); + ball->Touch(toucher); +} + +void Obj_BallSwitchDamaged(mobj_t *mobj, mobj_t *inflictor, mobj_t *source) +{ + BallSwitch_Ball *ball = static_cast(mobj); + ball->Hit(inflictor, source); +} diff --git a/src/p_inter.c b/src/p_inter.c index 6176201ec..62f842277 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -817,6 +817,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) Obj_DashRingTouch(special, player); return; + case MT_BALLSWITCH_BALL: + { + Obj_BallSwitchTouched(special, toucher); + return; + } + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; @@ -2398,6 +2404,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da return false; } + if (target->type == MT_BALLSWITCH_BALL) + { + Obj_BallSwitchDamaged(target, inflictor, source); + return false; + } + if (!force) { if (!spbpop) diff --git a/src/p_mobj.c b/src/p_mobj.c index 599dd37e8..fcb6fa830 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9703,6 +9703,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_RAINBOWDASHRING: Obj_RainbowDashRingThink(mobj); break; + case MT_BALLSWITCH_BALL: + { + Obj_BallSwitchThink(mobj); + break; + } default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -11142,6 +11147,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_SNEAKERPANELSPAWNER: Obj_SneakerPanelSpawnerSpawn(mobj); break; + case MT_BALLSWITCH_BALL: + Obj_BallSwitchInit(mobj); + break; default: break; }