Merge branch 'jartha/stone-shoe' into 'master'

Stone Shoe

See merge request kart-krew-dev/ring-racers-internal!2627
This commit is contained in:
Oni VelocitOni 2025-06-11 21:33:06 +00:00
commit 4d89627f13
17 changed files with 654 additions and 24 deletions

View file

@ -686,6 +686,7 @@ consvar_t cv_items[] = {
UnsavedNetVar("droptarget", "On").on_off(),
UnsavedNetVar("gardentop", "On").on_off(),
UnsavedNetVar("gachabom", "On").on_off(),
UnsavedNetVar("stoneshoe", "On").on_off(),
UnsavedNetVar("dualsneaker", "On").on_off(),
UnsavedNetVar("triplesneaker", "On").on_off(),
UnsavedNetVar("triplebanana", "On").on_off(),

View file

@ -193,7 +193,8 @@ Run this macro, then #undef FOREACH afterward
FOREACH (KITCHENSINK, 20),\
FOREACH (DROPTARGET, 21),\
FOREACH (GARDENTOP, 22),\
FOREACH (GACHABOM, 23)
FOREACH (GACHABOM, 23),\
FOREACH (STONESHOE, 24)
typedef enum
{
@ -214,6 +215,8 @@ typedef enum
NUMKARTRESULTS,
KDROP_STONESHOETRAP,
// Power-ups exist in the same enum as items so it's easy
// for paper items to be reused for them.
FIRSTPOWERUP,
@ -771,6 +774,7 @@ struct player_t
fixed_t accelboost; // Boost value smoothing for acceleration
fixed_t handleboost; // Boost value smoothing for handling
angle_t boostangle; // angle set when not spun out OR boosted to determine what direction you should keep going at if you're spun out and boosted.
fixed_t stonedrag;
fixed_t draftpower; // (0 to FRACUNIT) - Drafting power, doubles your top speed & acceleration at max
UINT16 draftleeway; // Leniency timer before removing draft power
@ -1047,6 +1051,7 @@ struct player_t
mobj_t *whip;
mobj_t *hand;
mobj_t *flickyAttacker;
mobj_t *stoneShoe;
SINT8 pitblame; // Index of last player that hit you, resets after being in control for a bit. If you deathpit, credit the old attacker!

View file

@ -3109,6 +3109,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
// Flybot767 (stun)
"S_FLYBOT767",
"S_STON",
};
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@ -4009,6 +4011,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_AMPS",
"MT_FLYBOT767",
"MT_STONESHOE",
"MT_STONESHOE_CHAIN",
};
const char *const MOBJFLAG_LIST[] = {
@ -5194,6 +5199,7 @@ struct int_const_s const INT_CONST[] = {
{"KRITEM_DUALJAWZ",KRITEM_DUALJAWZ},
{"KRITEM_TRIPLEGACHABOM",KRITEM_TRIPLEGACHABOM},
{"NUMKARTRESULTS",NUMKARTRESULTS},
{"KDROP_STONESHOETRAP",KDROP_STONESHOETRAP},
{"FIRSTPOWERUP",FIRSTPOWERUP},
{"POWERUP_SMONITOR",POWERUP_SMONITOR},
{"POWERUP_BARRIER",POWERUP_BARRIER},

View file

@ -792,6 +792,8 @@ char sprnames[NUMSPRITES + 1][5] =
// Flybot767 (stun)
"STUN",
"STON",
// Pulley
"HCCH",
"HCHK",
@ -3680,6 +3682,8 @@ state_t states[NUMSTATES] =
// Flybot767 (stun)
{SPR_STUN, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 4, 4, S_NULL}, // S_FLYBOT767
{SPR_STON, 0, -1, {NULL}, 0, 0, S_STON}, // S_STON
};
mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@ -13336,7 +13340,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_ITEMICON, // spawnstate
1, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
sfx_tossed, // seesound
0, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
@ -22529,6 +22533,58 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING, // flags
S_NULL // raisestate
},
{ // MT_STONESHOE
-1, // doomednum
S_STON, // 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_pop, // deathsound
4*FRACUNIT, // speed
64*FRACUNIT, // radius
64*FRACUNIT, // height
0, // dispoffset
0, // mass
0, // damage
sfx_None, // activesound
MF_SOLID|MF_NOSQUISH|MF_NOHITLAGFORME|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_STONESHOE_CHAIN
-1, // doomednum
S_SHRINK_CHAIN, // spawnstate
1000, // 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_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
32*FRACUNIT, // radius
64*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_SPECIAL|MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_PICKUPFROMBELOW|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
};

View file

@ -1329,6 +1329,8 @@ typedef enum sprite
// Flybot767 (stun)
SPR_STUN,
SPR_STON,
// Pulley
SPR_HCCH,
SPR_HCHK,
@ -4167,6 +4169,8 @@ typedef enum state
// Flybot767 (stun)
S_FLYBOT767,
S_STON,
S_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
NUMSTATES
@ -5090,6 +5094,9 @@ typedef enum mobj_type
MT_FLYBOT767,
MT_STONESHOE,
MT_STONESHOE_CHAIN,
MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES

View file

@ -171,6 +171,7 @@ static patch_t *kp_kitchensink[3];
static patch_t *kp_droptarget[3];
static patch_t *kp_gardentop[3];
static patch_t *kp_gachabom[3];
static patch_t *kp_stoneshoe[3];
static patch_t *kp_bar[2];
static patch_t *kp_doublebar[2];
static patch_t *kp_triplebar[2];
@ -639,6 +640,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_droptarget[0], "K_ITDTRG");
HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP");
HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM");
HU_UpdatePatch(&kp_stoneshoe[0], "K_ITSTON");
HU_UpdatePatch(&kp_bar[0], "K_RBBAR");
HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2");
HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3");
@ -699,6 +701,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_droptarget[1], "K_ISDTRG");
HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP");
HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM");
HU_UpdatePatch(&kp_stoneshoe[1], "K_ISSTON");
HU_UpdatePatch(&kp_bar[1], "K_SBBAR");
HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2");
HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3");
@ -757,6 +760,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_droptarget[2], "ISPYDTRG");
HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP");
HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM");
HU_UpdatePatch(&kp_stoneshoe[2], "ISPYSTON");
// CHECK indicators
sprintf(buffer, "K_CHECKx");
@ -1177,6 +1181,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
kp_droptarget,
kp_gardentop,
kp_gachabom,
kp_stoneshoe,
};
if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS))

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

@ -808,6 +808,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;
@ -933,6 +934,8 @@ static boolean K_JustBumpedException(mobj_t *mobj)
}
break;
}
case MT_STONESHOE:
return true;
default:
break;
}
@ -3527,6 +3530,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.)
@ -4669,7 +4675,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);
@ -6888,8 +6894,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)
@ -6907,7 +6918,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));
@ -6936,8 +6947,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)
{
@ -6984,6 +6998,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;
@ -7003,14 +7024,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))
{
@ -7039,8 +7069,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)
@ -10506,6 +10534,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)
@ -14607,6 +14637,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)
@ -15448,6 +15509,10 @@ void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount)
part->sprite = SPR_ITEM;
part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE;
break;
case KDROP_STONESHOETRAP:
part->sprite = SPR_STON;
part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|4;
break;
default:
if (itemType >= FIRSTPOWERUP)
{
@ -15887,6 +15952,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;
@ -15930,6 +15996,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;
@ -15940,6 +16009,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

@ -103,6 +103,7 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] =
{1, 3}, // droptarget
{43, 5}, // gardentop
{0, 0}, // gachabom
{1, 3}, // stoneshoe
{45, 6}, // dualsneaker
{55, 8}, // triplesneaker
{25, 2}, // triplebanana
@ -138,6 +139,7 @@ static UINT32 K_DynamicItemOddsBattle[NUMKARTRESULTS-1][2] =
{0, 0}, // droptarget
{0, 0}, // gardentop
{10, 5}, // gachabom
{0, 0}, // stoneshoe
{0, 0}, // dualsneaker
{20, 1}, // triplesneaker
{0, 0}, // triplebanana
@ -173,6 +175,7 @@ static UINT32 K_DynamicItemOddsSpecial[NUMKARTRESULTS-1][2] =
{0, 0}, // droptarget
{0, 0}, // gardentop
{0, 0}, // gachabom
{0, 0}, // stoneshoe
{35, 2}, // dualsneaker
{0, 0}, // triplesneaker
{0, 0}, // triplebanana
@ -208,6 +211,7 @@ static UINT8 K_KartLegacyBattleOdds[NUMKARTRESULTS-1][2] =
{ 0, 0 }, // Drop Target
{ 0, 0 }, // Garden Top
{ 5, 0 }, // Gachabom
{ 0, 1 }, // Stone Shoe
{ 0, 0 }, // Sneaker x2
{ 0, 1 }, // Sneaker x3
{ 0, 0 }, // Banana x3
@ -368,6 +372,7 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result)
case KITEM_DROPTARGET:
case KITEM_EGGMAN:
case KITEM_GACHABOM:
case KITEM_STONESHOE:
case KITEM_KITCHENSINK:
{
// Used when in 1st place and relatively far from players.
@ -1044,6 +1049,7 @@ static boolean K_IsItemFirstOnly(kartitems_t item)
case KITEM_LIGHTNINGSHIELD:
case KITEM_HYUDORO:
case KITEM_DROPTARGET:
case KITEM_STONESHOE:
return true;
default:
return false;

View file

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

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

@ -0,0 +1,362 @@
// 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);
shoe->voice(sfx_s3k5d);
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);
voice(sfx_s3k5f);
P_StartQuakeFromMobj(5, 40 * scale(), 512 * scale(), this);
}
}
thrust(a, 8 * mapobjectscale);
Fixed maxSpeed = 32 * mapobjectscale;
Fixed speed = FixedHypot(momx, momy);
if (speed > maxSpeed)
instathrust(a, maxSpeed);
if (P_IsObjectOnGround(this) && leveltime % 5 == 0)
voice(sfx_s3k6f);
}
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;

View file

@ -91,7 +91,8 @@ typedef enum
FLICKYCONTROLLER = 0x1000,
TRICKINDICATOR = 0x2000,
BARRIER = 0x4000,
BALLHOGRETICULE = 0x8000, // uh oh, we're full now...
BALLHOGRETICULE = 0x8000,
STONESHOE = 0x10000,
} player_saveflags;
static inline void P_ArchivePlayer(savebuffer_t *save)
@ -204,7 +205,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
TracyCZone(__zone, true);
INT32 i, j;
UINT16 flags;
UINT32 flags;
size_t q;
WRITEUINT32(save->p, ARCHIVEBLOCK_PLAYERS);
@ -364,7 +365,10 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (players[i].powerup.barrier)
flags |= BARRIER;
WRITEUINT16(save->p, flags);
if (players[i].stoneShoe)
flags |= STONESHOE;
WRITEUINT32(save->p, flags);
if (flags & SKYBOXVIEW)
WRITEUINT32(save->p, players[i].skybox.viewpoint->mobjnum);
@ -411,6 +415,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (flags & BARRIER)
WRITEUINT32(save->p, players[i].powerup.barrier->mobjnum);
if (flags & STONESHOE)
WRITEUINT32(save->p, players[i].stoneShoe->mobjnum);
WRITEUINT32(save->p, (UINT32)players[i].followitem);
WRITEUINT32(save->p, players[i].charflags);
@ -499,6 +506,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEFIXED(save->p, players[i].accelboost);
WRITEFIXED(save->p, players[i].handleboost);
WRITEANGLE(save->p, players[i].boostangle);
WRITEFIXED(save->p, players[i].stonedrag);
WRITEFIXED(save->p, players[i].draftpower);
WRITEUINT16(save->p, players[i].draftleeway);
@ -897,7 +905,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
TracyCZone(__zone, true);
INT32 i, j;
UINT16 flags;
UINT32 flags;
size_t q;
if (READUINT32(save->p) != ARCHIVEBLOCK_PLAYERS)
@ -1011,7 +1019,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].splitscreenindex = READUINT8(save->p);
flags = READUINT16(save->p);
flags = READUINT32(save->p);
if (flags & SKYBOXVIEW)
players[i].skybox.viewpoint = (mobj_t *)(size_t)READUINT32(save->p);
@ -1058,6 +1066,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
if (flags & BARRIER)
players[i].powerup.barrier = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & STONESHOE)
players[i].stoneShoe = (mobj_t *)(size_t)READUINT32(save->p);
players[i].followitem = (mobjtype_t)READUINT32(save->p);
//SetPlayerSkinByNum(i, players[i].skin);
@ -1147,6 +1158,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].accelboost = READFIXED(save->p);
players[i].handleboost = READFIXED(save->p);
players[i].boostangle = READANGLE(save->p);
players[i].stonedrag = READFIXED(save->p);
players[i].draftpower = READFIXED(save->p);
players[i].draftleeway = READUINT16(save->p);
@ -6218,6 +6230,11 @@ static void P_RelinkPointers(void)
if (!RelinkMobj(&players[i].powerup.barrier))
CONS_Debug(DBG_GAMELOGIC, "powerup.barrier not found on player %d\n", i);
}
if (players[i].stoneShoe)
{
if (!RelinkMobj(&players[i].stoneShoe))
CONS_Debug(DBG_GAMELOGIC, "stoneShoe not found on player %d\n", i);
}
}
}

View file

@ -4241,6 +4241,7 @@ void P_PlayerThink(player_t *player)
PlayerPointerErase(player->hoverhyudoro);
PlayerPointerErase(player->ballhogreticule);
PlayerPointerErase(player->flickyAttacker);
PlayerPointerErase(player->stoneShoe);
PlayerPointerErase(player->powerup.flickyController);
PlayerPointerErase(player->powerup.barrier);