From da991e5300e33074ce97c7ebe01bf29afdadb238 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 10 Jun 2025 16:32:04 -0700 Subject: [PATCH 1/7] p_saveg: bump player mobj relink flags to UINT32 --- src/p_saveg.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 5bdd0e338..0c08a1044 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -91,7 +91,7 @@ typedef enum FLICKYCONTROLLER = 0x1000, TRICKINDICATOR = 0x2000, BARRIER = 0x4000, - BALLHOGRETICULE = 0x8000, // uh oh, we're full now... + BALLHOGRETICULE = 0x8000, } player_saveflags; static inline void P_ArchivePlayer(savebuffer_t *save) @@ -204,7 +204,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 +364,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (players[i].powerup.barrier) flags |= BARRIER; - WRITEUINT16(save->p, flags); + WRITEUINT32(save->p, flags); if (flags & SKYBOXVIEW) WRITEUINT32(save->p, players[i].skybox.viewpoint->mobjnum); @@ -897,7 +897,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 +1011,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); From 4e6c501888b616f4d79f25c2574c81dfb868b6b9 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 10 Jun 2025 16:33:35 -0700 Subject: [PATCH 2/7] Add Stone Shoe states - Cvars - stoneshoe - Items enum - KITEM_STONESHOE - KDROP_STONESHOETRAP - Player vars - stonedrag (fixed_t) - stoneShoe (mobj pointer) - States - S_STON - Mobjs - MT_STONESHOE - MT_STONESHOE_CHAIN - Sprites - STON --- src/cvars.cpp | 1 + src/d_player.h | 7 +++++- src/deh_tables.c | 6 ++++++ src/info.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ src/info.h | 7 ++++++ src/k_hud.cpp | 5 +++++ src/k_kart.c | 4 ++++ src/p_saveg.cpp | 17 +++++++++++++++ src/p_user.c | 1 + 9 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 633250a73..ebf279432 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -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(), diff --git a/src/d_player.h b/src/d_player.h index 96af19f80..1e7d1f8b5 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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! diff --git a/src/deh_tables.c b/src/deh_tables.c index 63a8929ff..b8ba3ebea 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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}, diff --git a/src/info.c b/src/info.c index f15ec9b9f..0cde7d230 100644 --- a/src/info.c +++ b/src/info.c @@ -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] = @@ -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 + }, }; diff --git a/src/info.h b/src/info.h index 6a6b1f60f..737daf64b 100644 --- a/src/info.h +++ b/src/info.h @@ -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 diff --git a/src/k_hud.cpp b/src/k_hud.cpp index d36d74169..f37af401b 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -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)) diff --git a/src/k_kart.c b/src/k_kart.c index 8a1a6869a..9b99f04b2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -15446,6 +15446,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) { diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 0c08a1044..00c0c5864 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -92,6 +92,7 @@ typedef enum TRICKINDICATOR = 0x2000, BARRIER = 0x4000, BALLHOGRETICULE = 0x8000, + STONESHOE = 0x10000, } player_saveflags; static inline void P_ArchivePlayer(savebuffer_t *save) @@ -364,6 +365,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (players[i].powerup.barrier) flags |= BARRIER; + if (players[i].stoneShoe) + flags |= STONESHOE; + WRITEUINT32(save->p, flags); if (flags & SKYBOXVIEW) @@ -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); @@ -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); + } } } diff --git a/src/p_user.c b/src/p_user.c index 5859aa916..604cc506b 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -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); From 74f4708f7f7ab5f8cc300aa24656badc07995f11 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 10 Jun 2025 16:34:28 -0700 Subject: [PATCH 3/7] Add Stone Shoe item odds --- src/k_roulette.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/k_roulette.c b/src/k_roulette.c index bba953a64..229e95954 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -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 @@ -1044,6 +1048,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; From 6d80b741aeb863a46643c2aa2abbe0640826efce Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 10 Jun 2025 16:37:00 -0700 Subject: [PATCH 4/7] 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; From 5f572c8e9a4ccc0f1861382997bee723203c8c62 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 11 Jun 2025 13:42:09 -0700 Subject: [PATCH 5/7] Add Stone Shoe sounds --- src/info.c | 2 +- src/objects/stone-shoe.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/info.c b/src/info.c index 0cde7d230..559aeab94 100644 --- a/src/info.c +++ b/src/info.c @@ -13340,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 diff --git a/src/objects/stone-shoe.cpp b/src/objects/stone-shoe.cpp index 083b7129b..ca82ac8bc 100644 --- a/src/objects/stone-shoe.cpp +++ b/src/objects/stone-shoe.cpp @@ -129,6 +129,8 @@ struct Shoe : Mobj shoe->chain(link); shoe->chainLength(numLinks); + shoe->voice(sfx_s3k5d); + return shoe; } @@ -230,6 +232,7 @@ private: { momz = 32 * mapobjectscale; bouncing(true); + voice(sfx_s3k5f); } } @@ -240,6 +243,9 @@ private: if (speed > maxSpeed) instathrust(a, maxSpeed); + + if (P_IsObjectOnGround(this) && leveltime % 5 == 0) + voice(sfx_s3k6f); } else { From ba96b1011104134a95e2a1e69b8210e1ba639e82 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 11 Jun 2025 13:57:15 -0700 Subject: [PATCH 6/7] Stone Shoe: screenshake --- src/objects/stone-shoe.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/objects/stone-shoe.cpp b/src/objects/stone-shoe.cpp index ca82ac8bc..63ad55686 100644 --- a/src/objects/stone-shoe.cpp +++ b/src/objects/stone-shoe.cpp @@ -233,6 +233,7 @@ private: momz = 32 * mapobjectscale; bouncing(true); voice(sfx_s3k5f); + P_StartQuakeFromMobj(5, 40 * scale(), 512 * scale(), this); } } From 7efc7b97a7adaf2345dfbd6b540c1baf00ecdde7 Mon Sep 17 00:00:00 2001 From: VelocitOni Date: Wed, 11 Jun 2025 17:30:03 -0400 Subject: [PATCH 7/7] Bots know about stone shoe Added KITEM_STONESHOE to K_GetBotItemPriority, bots will use it as a frontrunner item --- src/k_roulette.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_roulette.c b/src/k_roulette.c index 229e95954..ab97aeed6 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -372,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.