From 6d80b741aeb863a46643c2aa2abbe0640826efce Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 10 Jun 2025 16:37:00 -0700 Subject: [PATCH] Add Stone Shoe --- src/k_hud_track.cpp | 8 + src/k_kart.c | 93 ++++++++-- src/k_objects.h | 7 + src/objects/CMakeLists.txt | 1 + src/objects/stone-shoe.cpp | 355 +++++++++++++++++++++++++++++++++++++ src/p_inter.c | 45 ++++- src/p_map.c | 11 ++ src/p_mobj.c | 29 +++ 8 files changed, 532 insertions(+), 17 deletions(-) create mode 100644 src/objects/stone-shoe.cpp diff --git a/src/k_hud_track.cpp b/src/k_hud_track.cpp index c01e34cb7..a99eda70f 100644 --- a/src/k_hud_track.cpp +++ b/src/k_hud_track.cpp @@ -389,6 +389,14 @@ bool is_object_tracking_target(const mobj_t* mobj) return !(mobj->renderflags & (RF_TRANSMASK | RF_DONTDRAW)) && // the spraycan wasn't collected yet P_CheckSight(stplyr->mo, const_cast(mobj)); + case MT_FLOATINGITEM: + if (mobj->threshold != KDROP_STONESHOETRAP) + return false; + + if (cv_debugpickmeup.value) + return false; + + // FALLTHRU default: if (K_IsPickMeUpItem(mobj->type)) return (mobj->target && !P_MobjWasRemoved(mobj->target) && ( diff --git a/src/k_kart.c b/src/k_kart.c index 9b99f04b2..d25ba2714 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -802,6 +802,7 @@ fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) case MT_ORBINAUT_SHIELD: case MT_GACHABOM: case MT_DUELBOMB: + case MT_STONESHOE: if (against->player) weight = K_PlayerWeight(against, NULL); break; @@ -927,6 +928,8 @@ static boolean K_JustBumpedException(mobj_t *mobj) } break; } + case MT_STONESHOE: + return true; default: break; } @@ -3521,6 +3524,9 @@ static void K_GetKartBoostPower(player_t *player) if (player->bananadrag > TICRATE) boostpower = (4*boostpower)/5; + if (player->stonedrag) + boostpower = (4*boostpower)/5; + // Note: Handling will ONLY stack when sliptiding! // > (NB 2023-03-06: This was previously unintentionally applied while drifting as well.) // > (This only affected drifts where you were under the effect of multiple handling boosts.) @@ -4663,7 +4669,7 @@ void K_RemoveGrowShrink(player_t *player) static boolean K_IsScaledItem(mobj_t *mobj) { return mobj && !P_MobjWasRemoved(mobj) && - (mobj->type == MT_ORBINAUT || mobj->type == MT_JAWZ || mobj->type == MT_GACHABOM + (mobj->type == MT_ORBINAUT || mobj->type == MT_JAWZ || mobj->type == MT_GACHABOM || mobj->type == MT_STONESHOE || mobj->type == MT_BANANA || mobj->type == MT_EGGMANITEM || mobj->type == MT_BALLHOG || mobj->type == MT_SSMINE || mobj->type == MT_LANDMINE || mobj->type == MT_SINK || mobj->type == MT_GARDENTOP || mobj->type == MT_DROPTARGET || mobj->type == MT_PLAYER); @@ -6882,8 +6888,13 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing if (dir > 0) { // Shoot forward - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, mapthing); - mo->angle = player->mo->angle; + if (mapthing == MT_FLOATINGITEM) + mo = K_CreatePaperItem(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, player->mo->angle, 0, 1, 0); + else + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, mapthing); + mo->angle = player->mo->angle; + } // These are really weird so let's make it a very specific case to make SURE it works... if (player->mo->eflags & MFE_VERTICALFLIP) @@ -6901,7 +6912,7 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing mo->extravalue2 = dir; fixed_t HEIGHT = ((20 * FRACUNIT) + (dir * 10)) + (FixedDiv(player->mo->momz, mapobjectscale) * P_MobjFlip(player->mo)); // Also intentionally not player scale - P_SetObjectMomZ(mo, HEIGHT, false); + mo->momz = FixedMul(HEIGHT, mapobjectscale) * P_MobjFlip(mo); angle_t fa = (player->mo->angle >> ANGLETOFINESHIFT); mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), FixedMul(PROJSPEED, dir)); @@ -6930,8 +6941,11 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing if (mo->eflags & MFE_UNDERWATER) mo->momz = (117 * mo->momz) / 200; - P_SetScale(mo, finalscale); - mo->destscale = finalscale; + if (mapthing != MT_FLOATINGITEM) + { + P_SetScale(mo, finalscale); + mo->destscale = finalscale; + } switch (mapthing) { @@ -6978,6 +6992,13 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing newy = player->mo->y + player->mo->momy; newz = player->mo->z; } + else if (mapthing == MT_FLOATINGITEM) // Stone Shoe + { + newangle = player->mo->angle; + newx = player->mo->x + player->mo->momx - FixedMul(2 * player->mo->radius + 40 * mapobjectscale, FCOS(newangle)); + newy = player->mo->y + player->mo->momy - FixedMul(2 * player->mo->radius + 40 * mapobjectscale, FSIN(newangle)); + newz = player->mo->z; + } else if (lasttrail) { newangle = lasttrail->angle; @@ -6997,14 +7018,23 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing newz = player->mo->z; } - mo = P_SpawnMobj(newx, newy, newz, mapthing); // this will never return null because collision isn't processed here + if (mapthing == MT_FLOATINGITEM) + mo = K_CreatePaperItem(newx, newy, newz, newangle, 0, 1, 0); + else + { + mo = P_SpawnMobj(newx, newy, newz, mapthing); // this will never return null because collision isn't processed here + mo->angle = newangle; + } K_FlipFromObject(mo, player->mo); mo->threshold = 10; P_SetTarget(&mo->target, player->mo); - P_SetScale(mo, finalscale); - mo->destscale = finalscale; + if (mapthing != MT_FLOATINGITEM) + { + P_SetScale(mo, finalscale); + mo->destscale = finalscale; + } if (P_IsObjectOnGround(player->mo)) { @@ -7033,8 +7063,6 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing if (player->mo->eflags & MFE_VERTICALFLIP) mo->eflags |= MFE_VERTICALFLIP; - mo->angle = newangle; - if (mapthing == MT_SSMINE) mo->extravalue1 = 49; // Pads the start-up length from 21 frames to a full 2 seconds else if (mapthing == MT_BUBBLESHIELDTRAP) @@ -10500,6 +10528,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) // S_StartSound(NULL, sfx_s26d); P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL); } + + player->stonedrag = 0; } void K_KartResetPlayerColor(player_t *player) @@ -14601,6 +14631,37 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->botvars.itemconfirm = 0; } break; + case KITEM_STONESHOE: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + if (player->throwdir == -1) + { + // Do not spawn a shoe if you're already dragging one + if (!P_MobjWasRemoved(player->stoneShoe)) + break; + P_SetTarget(&player->stoneShoe, Obj_SpawnStoneShoe(player - players, player->mo)); + K_AddHitLag(player->mo, 8, false); + } + else + { + K_SetItemOut(player); // need this to set itemscale + + mobj_t *drop = K_ThrowKartItem(player, false, MT_FLOATINGITEM, -1, 0, 0); + drop->threshold = KDROP_STONESHOETRAP; + drop->movecount = 1; + drop->extravalue2 = player - players; + drop->radius = 32 * drop->scale; + drop->flags |= MF_SHOOTABLE; // let it whip/lightning shield + + K_UnsetItemOut(player); + } + + player->itemamount--; + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + player->botvars.itemconfirm = 0; + } + break; case KITEM_SAD: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && !player->sadtimer) @@ -15889,6 +15950,7 @@ boolean K_IsPickMeUpItem(mobjtype_t type) case MT_BUBBLESHIELDTRAP: case MT_SSMINE: case MT_SSMINE_SHIELD: + case MT_FLOATINGITEM: // Stone Shoe return true; default: return false; @@ -15932,6 +15994,9 @@ static boolean K_PickUp(player_t *player, mobj_t *picked) case MT_GACHABOM: type = KITEM_GACHABOM; break; + case MT_STONESHOE: + type = KITEM_STONESHOE; + break; case MT_BUBBLESHIELDTRAP: type = KITEM_BUBBLESHIELD; break; @@ -15942,6 +16007,12 @@ static boolean K_PickUp(player_t *player, mobj_t *picked) case MT_SSMINE_SHIELD: type = KITEM_MINE; break; + case MT_FLOATINGITEM: + if (picked->threshold == KDROP_STONESHOETRAP) + type = KITEM_STONESHOE; + else + type = KITEM_SAD; + break; default: type = KITEM_SAD; break; diff --git a/src/k_objects.h b/src/k_objects.h index c6cc64db9..b1b9b8a93 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -460,6 +460,13 @@ boolean Obj_TickLightningShieldVisual(mobj_t *mobj); void Obj_SpawnFlameShieldVisuals(mobj_t *source); boolean Obj_TickFlameShieldVisual(mobj_t *mobj); +/* Stone Shoe */ +mobj_t *Obj_SpawnStoneShoe(INT32 owner, mobj_t *victim); +boolean Obj_TickStoneShoe(mobj_t *shoe); +boolean Obj_TickStoneShoeChain(mobj_t *chain); +player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe); +void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index d82a2d77c..f050047ca 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -64,6 +64,7 @@ target_sources(SRB2SDL2 PRIVATE flybot767.c lightning-shield.cpp flame-shield.cpp + stone-shoe.cpp ) add_subdirectory(versus) diff --git a/src/objects/stone-shoe.cpp b/src/objects/stone-shoe.cpp new file mode 100644 index 000000000..083b7129b --- /dev/null +++ b/src/objects/stone-shoe.cpp @@ -0,0 +1,355 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 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 + +#include "objects.hpp" + +#include "../g_game.h" +#include "../k_kart.h" +#include "../m_easing.h" +#include "../m_fixed.h" +#include "../p_spec.h" +#include "../r_main.h" +#include "../tables.h" + +using namespace srb2::objects; + +namespace +{ + +struct Player : Mobj +{ + bool valid() const { return player != nullptr; } +}; + +struct Shoe; + +struct Chain : Mobj +{ + void hnext() = delete; + Chain* next() const { return Mobj::hnext(); } + void next(Chain* n) { Mobj::hnext(n); } + + void target() = delete; + Shoe* shoe() const { return Mobj::target(); } + void shoe(Shoe* n) { Mobj::target(n); } + + bool valid() const; + + bool try_damage(Player* pmo); + + bool tick() + { + if (!valid()) + { + remove(); + return false; + } + + return true; + } +}; + +struct Shoe : Mobj +{ + void target() = delete; + Player* follow() const { return Mobj::target(); } + void follow(Player* n) { Mobj::target(n); } + + void movedir() = delete; + INT32 dir() const { return Mobj::movedir; } + void dir(INT32 n) { Mobj::movedir = n; } + + void hnext() = delete; + Chain* chain() const { return Mobj::hnext(); } + void chain(Chain* n) { Mobj::hnext(n); } + + void extravalue1() = delete; + INT32 chainLength() const { return mobj_t::extravalue1; } + void chainLength(INT32 n) { mobj_t::extravalue1 = n; } + + void extravalue2() = delete; + INT32 owner() const { return mobj_t::extravalue2; } + void owner(INT32 n) { mobj_t::extravalue2 = n; } + + void threshold() = delete; + bool bouncing() const { return mobj_t::threshold; } + void bouncing(bool n) { mobj_t::threshold = n; } + + bool valid() const { return Mobj::valid(follow()) && follow()->valid() && Mobj::valid(chain()); } + + Fixed minDist() const { return 200 * mapobjectscale; } + Fixed maxDist() const { return 800 * mapobjectscale; } + + angle_t followAngle() const { return R_PointToAngle2(x, y, follow()->x, follow()->y); } + Fixed followDistance() const { return FixedHypot(x - follow()->x, y - follow()->y); } + + static Vec2 followVector(angle_t a) { return Vec2 {FCOS(a), FSIN(a)}; } + Vec2 followVector() const { return followVector(followAngle()); } + + player_t* ownerPlayer() const + { + if (owner() < 0 || owner() >= MAXPLAYERS) + return nullptr; + return &players[owner()]; + } + + static Shoe* spawn + ( INT32 owner, + Player* victim) + { + Vec2 P = followVector(victim->angle) * Fixed {-40 * mapobjectscale}; + Shoe* shoe = victim->spawn_from({P, 0}, MT_STONESHOE); + + shoe->follow(victim); + shoe->owner(owner); + shoe->dir(0); + shoe->fuse = 15 * TICRATE; + + INT32 numLinks = 8; + Chain* link = nullptr; + + for (INT32 i = 0; i < numLinks; ++i) + { + Chain* node = shoe->spawn_from(MT_STONESHOE_CHAIN); + node->next(link); + node->shoe(shoe); + link = node; + } + + shoe->chain(link); + shoe->chainLength(numLinks); + + return shoe; + } + + bool tick() + { + if (!valid()) + { + remove(); + return false; + } + + move(); + move_chain(); + + return true; + } + + bool try_damage + ( Player* pmo, + Mobj* inflictor) + { + if (pmo == follow()) + return false; + + if (!valid()) + return false; + + mobj_t* source = nullptr; + + if (ownerPlayer()) + source = ownerPlayer()->mo; + + bool hit = false; + + if (bouncing()) + hit = P_DamageMobj(pmo, inflictor, source, 1, DMG_TUMBLE); + else if (FixedHypot(momx, momy) > 16 * mapobjectscale) + hit = P_DamageMobj(pmo, inflictor, source, 1, DMG_NORMAL); + + if (hit && ownerPlayer() && follow()->player && pmo->player) + { + // Give Amps to both the originator of the Shoe and the person dragging it. + K_SpawnAmps(ownerPlayer(), K_PvPAmpReward(10, ownerPlayer(), pmo->player), pmo); + K_SpawnAmps(follow()->player, K_PvPAmpReward(10, follow()->player, pmo->player), pmo); + } + + return true; + } + +private: + void animate() + { + INT32 speed = 20; // TODO + tic_t t = leveltime / speed; + INT32 th = ANGLE_180 / speed * 2; + UINT32 ff = 0; + + if (t % 8 > 3) + { + ff = FF_VERTICALFLIP; + angle = ANGLE_180 + dir(); + } + else + angle = dir(); + + frame = (t % 4) | ff; + dir(dir() + th); + rollangle -= th; + + old_angle = angle; + } + + void move() + { + Fixed dist = followDistance(); + angle_t a = followAngle(); + bool close = true; + + Fixed dz = z - follow()->z; + + if (dz < -maxDist()) + z = follow()->z - maxDist(); + else if (dz > maxDist()) + z = follow()->z + maxDist(); + + if (dist > minDist()) + { + if (dist > maxDist()) + { + move_origin({ + follow()->x - maxDist() * Fixed {FCOS(a)}, + follow()->y - maxDist() * Fixed {FSIN(a)}, + z, + }); + + close = false; + + if (P_IsObjectOnGround(this)) + { + momz = 32 * mapobjectscale; + bouncing(true); + } + } + + thrust(a, 8 * mapobjectscale); + + Fixed maxSpeed = 32 * mapobjectscale; + Fixed speed = FixedHypot(momx, momy); + + if (speed > maxSpeed) + instathrust(a, maxSpeed); + } + else + { + if (!P_IsObjectOnGround(this)) + bouncing(false); + } + + if (close) + friction -= 200; + else + friction += 500; + + if (dist > maxDist()) + animate(); + else + { + frame = 1; + rollangle = 0; + angle = a; + } + + follow()->player->stonedrag = dist > minDist(); + + sprzoff(30 * scale()); + } + + void move_chain() + { + const Fixed shoeSpriteRadius = 48 * scale(); + const Fixed chainSpriteRadius = 26 * chain()->scale(); + + Fixed fd = std::max(followDistance() - shoeSpriteRadius - follow()->radius - chainSpriteRadius * 2, 0); + Fixed fdz = follow()->z - z; + Fixed nd = fd / std::max(chainLength(), 1); + Fixed ndz = fdz / std::max(chainLength(), 1); + + Vec2 v = followVector(); + Vec2 p = pos2d() + v * nd / 2 + v * Fixed {shoeSpriteRadius + chainSpriteRadius}; + Fixed pz = z + ndz / 2; + + Chain* node = chain(); + + while (Mobj::valid(node)) + { + node->move_origin({p, pz}); + node->sprzoff(sprzoff()); + + // Let chain flicker like shoe does + node->renderflags = renderflags; + + p += v * nd; + pz += ndz; + + node = node->next(); + } + } +}; + +bool Chain::valid() const +{ + return Mobj::valid(shoe()); +} + +bool Chain::try_damage(Player* pmo) +{ + if (!Mobj::valid(shoe())) + return false; + + return shoe()->try_damage(pmo, this); +} + +}; // namespace + +mobj_t *Obj_SpawnStoneShoe(INT32 owner, mobj_t *victim) +{ + return Shoe::spawn(owner, static_cast(victim)); +} + +boolean Obj_TickStoneShoe(mobj_t *shoe) +{ + return static_cast(shoe)->tick(); +} + +boolean Obj_TickStoneShoeChain(mobj_t *chain) +{ + return static_cast(chain)->tick(); +} + +player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe) +{ + return static_cast(shoe)->ownerPlayer(); +} + +void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj) +{ + switch (mobj->type) + { + case MT_STONESHOE: { + Shoe* shoe = static_cast(mobj); + + if (!shoe->try_damage(static_cast(mover), shoe)) + K_KartSolidBounce(mover, shoe); + break; + } + + case MT_STONESHOE_CHAIN: + static_cast(mobj)->try_damage(static_cast(mover)); + break; + + default: + break; + } +} diff --git a/src/p_inter.c b/src/p_inter.c index 0a74ab9c4..689860dd4 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -425,14 +425,41 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->scale < special->destscale/2) return; - if (!P_CanPickupItem(player, PICKUP_PAPERITEM) || (player->itemamount && player->itemtype != special->threshold)) + if (!P_CanPickupItem(player, PICKUP_PAPERITEM)) return; - player->itemtype = special->threshold; - if ((UINT16)(player->itemamount) + special->movecount > 255) - player->itemamount = 255; + if (special->threshold == KDROP_STONESHOETRAP) + { + if (K_TryPickMeUp(special, toucher, false)) + return; + + if (!P_MobjWasRemoved(player->stoneShoe)) + { + player->pflags |= PF_CASTSHADOW; + return; + } + + P_SetTarget(&player->stoneShoe, Obj_SpawnStoneShoe(special->extravalue2, toucher)); + K_AddHitLag(toucher, 8, false); + + player_t *owner = Obj_StoneShoeOwnerPlayer(special); + if (owner) + { + K_SpawnAmps(player, K_PvPAmpReward(20, owner, player), toucher); + K_SpawnAmps(owner, K_PvPAmpReward(20, owner, player), toucher); + } + } else - player->itemamount += special->movecount; + { + if (player->itemamount && player->itemtype != special->threshold) + return; + + player->itemtype = special->threshold; + if ((UINT16)(player->itemamount) + special->movecount > 255) + player->itemamount = 255; + else + player->itemamount += special->movecount; + } } S_StartSound(special, special->info->deathsound); @@ -1087,6 +1114,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) Obj_PulleyHookTouch(special, toucher); return; + case MT_STONESHOE_CHAIN: + Obj_CollideStoneShoe(toucher, special); + return; + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; @@ -3202,7 +3233,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (source && source != player->mo && source->player) { - K_SpawnAmps(source->player, K_PvPAmpReward((type == DMG_WHUMBLE) ? 30 : 20, source->player, player), target); + // Stone Shoe handles amps on its own + if (inflictor->type != MT_STONESHOE && inflictor->type != MT_STONESHOE_CHAIN) + K_SpawnAmps(source->player, K_PvPAmpReward((type == DMG_WHUMBLE) ? 30 : 20, source->player, player), target); K_BotHitPenalty(player); if (G_SameTeam(source->player, player)) diff --git a/src/p_map.c b/src/p_map.c index 07090967f..76f3d80bc 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1531,6 +1531,17 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) K_KartBouncing(g_tm.thing, thing); return BMIT_CONTINUE; } + else if (thing->type == MT_STONESHOE) + { + // see if it went over / under + if (g_tm.thing->z > thing->z + thing->height) + return BMIT_CONTINUE; // overhead + if (g_tm.thing->z + g_tm.thing->height < thing->z) + return BMIT_CONTINUE; // underneath + + Obj_CollideStoneShoe(g_tm.thing, thing); + return BMIT_CONTINUE; + } else if ((thing->flags & MF_SHOOTABLE) && K_PlayerCanPunt(g_tm.thing->player)) { // see if it went over / under diff --git a/src/p_mobj.c b/src/p_mobj.c index 4b1194dd7..eb5ccabc4 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1274,6 +1274,12 @@ fixed_t P_GetMobjGravity(mobj_t *mo) gravityadd *= 6; break; case MT_FLOATINGITEM: { + if (mo->threshold == KDROP_STONESHOETRAP) + { + gravityadd = (5*gravityadd)/2; + break; + } + // Basically this accelerates gravity after // the object reached its peak vertical // momentum. It's a gradual acceleration up @@ -1296,6 +1302,9 @@ fixed_t P_GetMobjGravity(mobj_t *mo) if (!mo->fuse) gravityadd *= 2; break; + case MT_STONESHOE: + gravityadd *= 4; + break; default: break; } @@ -6704,6 +6713,11 @@ static void P_MobjSceneryThink(mobj_t *mobj) } break; } + case MT_STONESHOE_CHAIN: + { + Obj_TickStoneShoeChain(mobj); + return; + } default: if (mobj->fuse) { // Scenery object fuse! Very basic! @@ -10228,6 +10242,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; } + case MT_STONESHOE: + return Obj_TickStoneShoe(mobj); + default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -10297,6 +10314,12 @@ static boolean P_CanFlickerFuse(mobj_t *mobj) return true; } break; + case MT_STONESHOE: + if (mobj->fuse <= 3 * TICRATE) + { + return true; + } + break; default: break; @@ -11096,6 +11119,12 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) thing->shadowscale = FRACUNIT; thing->whiteshadow = false; break; + case MT_STONESHOE: + thing->shadowscale = 2*FRACUNIT/3; + break; + case MT_STONESHOE_CHAIN: + thing->shadowscale = FRACUNIT/5; + break; default: if (thing->flags & (MF_ENEMY|MF_BOSS)) thing->shadowscale = FRACUNIT;