Add Stone Shoe

This commit is contained in:
James R 2025-06-10 16:37:00 -07:00
parent 74f4708f7f
commit 6d80b741ae
8 changed files with 532 additions and 17 deletions

View file

@ -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_t*>(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) && (

View file

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

View file

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

View file

@ -64,6 +64,7 @@ target_sources(SRB2SDL2 PRIVATE
flybot767.c
lightning-shield.cpp
flame-shield.cpp
stone-shoe.cpp
)
add_subdirectory(versus)

355
src/objects/stone-shoe.cpp Normal file
View file

@ -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 <algorithm>
#include <cstdlib>
#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<Chain>(); }
void next(Chain* n) { Mobj::hnext(n); }
void target() = delete;
Shoe* shoe() const { return Mobj::target<Shoe>(); }
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<Player>(); }
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<Chain>(); }
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<Fixed> followVector(angle_t a) { return Vec2<Fixed> {FCOS(a), FSIN(a)}; }
Vec2<Fixed> 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<Fixed> P = followVector(victim->angle) * Fixed {-40 * mapobjectscale};
Shoe* shoe = victim->spawn_from<Shoe>({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<Chain>(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<Fixed>(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<Fixed> v = followVector();
Vec2<Fixed> 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<Player*>(victim));
}
boolean Obj_TickStoneShoe(mobj_t *shoe)
{
return static_cast<Shoe*>(shoe)->tick();
}
boolean Obj_TickStoneShoeChain(mobj_t *chain)
{
return static_cast<Chain*>(chain)->tick();
}
player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe)
{
return static_cast<Shoe*>(shoe)->ownerPlayer();
}
void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj)
{
switch (mobj->type)
{
case MT_STONESHOE: {
Shoe* shoe = static_cast<Shoe*>(mobj);
if (!shoe->try_damage(static_cast<Player*>(mover), shoe))
K_KartSolidBounce(mover, shoe);
break;
}
case MT_STONESHOE_CHAIN:
static_cast<Chain*>(mobj)->try_damage(static_cast<Player*>(mover));
break;
default:
break;
}
}

View file

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

View file

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

View file

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