From 2c04bb2ef5d18e4fd5d559667057fdb76bcd146c Mon Sep 17 00:00:00 2001 From: minenice55 Date: Tue, 4 Nov 2025 21:55:30 -0500 Subject: [PATCH 01/76] generalize alt invinc to apply to any item (with some work) --- src/command.h | 6 +++++- src/d_netcmd.c | 25 ++++++++++++++++++++++--- src/deh_tables.c | 3 +++ src/doomdef.h | 2 +- src/doomstat.h | 4 +++- src/g_game.c | 4 +++- src/info/mobjs.h | 6 +++++- src/info/states.h | 5 +++++ src/k_collide.c | 12 +++++++----- src/k_hud.c | 16 ++++++++-------- src/k_kart.c | 38 ++++++++++++++++++++++++++++---------- src/k_kart.h | 1 + src/k_odds.c | 10 +++++----- src/p_inter.c | 2 +- src/p_mobj.c | 10 ++++++++++ src/p_user.c | 2 +- src/r_patchrotation.c | 2 +- 17 files changed, 109 insertions(+), 39 deletions(-) diff --git a/src/command.h b/src/command.h index b6e99ebf2..f021caccd 100644 --- a/src/command.h +++ b/src/command.h @@ -188,10 +188,14 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTGP_MASTER 4 // Not a speed setting, gives hard speed with maxed out bots #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots extern CV_PossibleValue_t kartspeed_cons_t[], gpdifficulty_cons_t[]; -// Invincibility types. +// Invincibility types #define KARTINVIN_LEGACY 0 #define KARTINVIN_ALTERN 1 +// Alt item type (alias of kartinvin_x for now until refactor complete) +#define KARTITEM_NORMAL 0 +#define KARTITEM_ALTERN 1 + extern consvar_t cv_execversion; extern consvar_t cv_resetnetvars; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 79548c2ab..47a2ccb5a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -594,9 +594,10 @@ consvar_t cv_kartspbdist = CVAR_INIT ("kartspbdist", "4432", CV_NETVAR|CV_CHEAT| consvar_t cv_kartlegacyspbdist = CVAR_INIT ("kartlegacyspbdist", "2216", CV_NETVAR|CV_CHEAT|CV_GUARD, spbdist_cons_t, NULL); #undef MAXODDSDIST -// Invincibility modifiers -static CV_PossibleValue_t invintype_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; -consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, invintype_cons_t, KartInvinType_OnChange); +// Alt Item Modifiers +static CV_PossibleValue_t altitemtype_cons_t[] = {{KARTITEM_NORMAL, "Legacy"}, {KARTITEM_ALTERN, "Alternative"}, {0, NULL}}; +consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, altitemtype_cons_t, KartInvinType_OnChange); +consvar_t cv_karteggmantype = CVAR_INIT ("karteggmantype", "Legacy", CV_NETVAR|CV_CALL, altitemtype_cons_t, KartEggmanType_OnChange); // How far the player must be from the cluster to begin frequently rolling Invincibility. static CV_PossibleValue_t invindist_cons_t[] = {{1, "MIN"}, {32000, "MAX"}, {0, NULL}}; @@ -7831,6 +7832,24 @@ static void KartInvinType_OnChange(void) } } +static void KartEggmanType_OnChange(void) +{ + if (K_CanChangeRules(false) == false) + { + return; + } + + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Eggman Monitor type has been changed to \"%s\".\n"), cv_karteggmantype.string); + eggmantype = (UINT8)cv_karteggmantype.value; + } + else + { + CONS_Printf(M_GetText("Eggman Monitor type will be changed to \"%s\" next round.\n"), cv_karteggmantype.string); + } +} + static void KartBumpSpark_OnChange(void) { if (K_CanChangeRules(false) == false) diff --git a/src/deh_tables.c b/src/deh_tables.c index 345e9488d..e650302a1 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1686,6 +1686,9 @@ struct int_const_s const INT_CONST[] = { // invintype {"KARTINVIN_LEGACY", KARTINVIN_LEGACY}, {"KARTINVIN_ALTERN", KARTINVIN_ALTERN}, + // alt item type (alias of invintype for now until refactor complete) + {"KARTITEM_NORMAL", KARTITEM_NORMAL}, + {"KARTITEM_ALTERN", KARTITEM_ALTERN}, // k_waypoint.h values {"DEFAULT_WAYPOINT_RADIUS",DEFAULT_WAYPOINT_RADIUS}, diff --git a/src/doomdef.h b/src/doomdef.h index b707eb214..d7df4e4f2 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -91,7 +91,7 @@ extern "C" { // Special Hashing. //#define NOFILEHASH -//#define NOVERIFYIWADS +#define NOVERIFYIWADS // Uncheck this to compile debugging code //#define RANGECHECK diff --git a/src/doomstat.h b/src/doomstat.h index 6f6fe218d..41173c77d 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -665,11 +665,13 @@ extern INT32 cheats; // SRB2kart extern UINT8 numlaps; extern UINT8 gamespeed; -extern UINT8 invintype; extern boolean franticitems; extern boolean encoremode, prevencoremode; extern boolean comeback; +extern UINT8 invintype; +extern UINT8 eggmantype; + extern SINT8 mostwanted; extern SINT8 battlewanted[4]; extern tic_t wantedcalcdelay; diff --git a/src/g_game.c b/src/g_game.c index e9249f005..297e6e3fa 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -295,11 +295,13 @@ INT32 cheats; //for multiplayer cheat commands // Cvars that we don't want changed mid-game UINT8 numlaps; // Removed from Cvar hell UINT8 gamespeed; // Game's current speed (or difficulty, or cc, or etc); 0 for easy, 1 for normal, 2 for hard -UINT8 invintype; // How Invincibility functions. 0 for Legacy/Vanilla, 1 for Alternative. boolean encoremode = false; // Encore Mode currently enabled? boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? boolean comeback; // Battle Mode's karma comeback is on/off +// alt item values +UINT8 invintype; // How Invincibility functions. 0 for Legacy/Vanilla, 1 for Alternative. +UINT8 eggmantype; // How Eggman Monitor functions. 0 for Legacy/Vanilla, 1 for Alternative (Egg Mine). // Voting system INT16 votelevels[12][2]; // Levels that were rolled by the host diff --git a/src/info/mobjs.h b/src/info/mobjs.h index 4d1f31325..c44760310 100644 --- a/src/info/mobjs.h +++ b/src/info/mobjs.h @@ -602,7 +602,11 @@ _(SMOLDERING) // New explosion _(BOOMEXPLODE) _(BOOMPARTICLE) -_(LANDMINE) // Land Mine +// Land Mine +_(LANDMINE) +// Egg Mine +_(EGGMINE_CAPSULE) +_(EGGMINE_SHIELD) _(BALLHOG) // Ballhog _(BALLHOGBOOM) diff --git a/src/info/states.h b/src/info/states.h index 0423ca11b..86c8c67f4 100644 --- a/src/info/states.h +++ b/src/info/states.h @@ -2816,6 +2816,11 @@ _(SLOWBOOM10) _(LANDMINE) _(LANDMINE_EXPLODE) +// Egg mine +_(EGGMINE) +_(EGGMINE_POP) +_(EGGMINE_SHIELD) + // Ballhog _(BALLHOG1) _(BALLHOG2) diff --git a/src/k_collide.c b/src/k_collide.c index 44769c1d5..34ff4b101 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -573,12 +573,13 @@ static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble, INT16 d static boolean K_BubbleReflectingTrapItem(const mobj_t *t) { return t->type == MT_BANANA || (t->type == MT_ORBINAUT && t->flags2 & MF2_AMBUSH) || t->type == MT_JAWZ_DUD || - t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE; + t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE || + t->type == MT_EGGMINE_CAPSULE || t->type == MT_EGGMINE_SHIELD; } static boolean K_StrongPlayerBump(const player_t *player) { - return (((K_GetKartInvinType() == KARTINVIN_LEGACY) && (player->invincibilitytimer)) + return (((!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (player->invincibilitytimer)) || (player->growshrinktimer > 0)); } @@ -636,7 +637,7 @@ boolean K_BubbleShieldCanReflect(mobj_t *t) { return (t->type == MT_ORBINAUT || t->type == MT_JAWZ || t->type == MT_JAWZ_DUD || t->type == MT_BANANA || t->type == MT_EGGMANITEM || t->type == MT_BALLHOG - || t->type == MT_SSMINE || t->type == MT_LANDMINE || t->type == MT_SINK + || t->type == MT_SSMINE || t->type == MT_LANDMINE || t->type == MT_EGGMINE_CAPSULE || t->type == MT_SINK || t->type == MT_KART_LEFTOVER || t->type == MT_PLAYER); } @@ -656,6 +657,7 @@ static INT16 K_GetBubbleDamage(mobj_t* itm) { case MT_EGGMANITEM: case MT_BALLHOG: + case MT_EGGMINE_CAPSULE: // Experiment: Let Bubble Shields hard-counter Ballhogs and Eggboxes. dmg = 0; break; @@ -820,7 +822,7 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) boolean t2Condition = false; // Rim suggestion: Flipover damage is negligible at best, just cull it from Invincibility as a whole. - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { t1Condition = (t1->player->invincibilitytimer > 0); t2Condition = (t2->player->invincibilitytimer > 0); @@ -832,7 +834,7 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) K_DoInstashield(t2->player); return false; } - else if (K_GetKartInvinType() == KARTINVIN_LEGACY) + else if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { if (t1Condition == true && t2Condition == false) { diff --git a/src/k_hud.c b/src/k_hud.c index 4f46dcb4f..795639634 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1433,7 +1433,7 @@ static void K_drawKartItem(void) else localpatch = kp_nodraw; } - else if ((stplyr->invincibilitytimer) && (K_GetKartInvinType() == KARTINVIN_ALTERN)) + else if ((stplyr->invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY))) { itembar = FixedDiv(stplyr->invincibilitytimer, max(1, stplyr->maxinvincibilitytime)); @@ -2102,7 +2102,7 @@ static boolean K_drawKartPositionFaces(void) V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, V_HUDTRANS|V_SNAPTOLEFT, facerank, colormap); - if ((players[rankplayer[i]].invincibilitytimer) && (K_GetKartInvinType() == KARTINVIN_ALTERN)) + if ((players[rankplayer[i]].invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY))) { colormap = R_GetTranslationColormap(TC_BLINK, K_AltInvincibilityColor(leveltime / 2), GTC_CACHE); invinchudtrans = K_InvincibilityHUDVisibility(players[rankplayer[i]].invincibilitytimer); @@ -2488,7 +2488,7 @@ static void K_DrawServerMods(INT32 x, INT32 y) {"Bump Drift", 0, NULL, K_GetBumpSpark() > 0, true}, {"Bump Spark", 0, NULL, K_GetBumpSpark() > BUMPSPARK_NOCHARGE, true}, {"Bump Spring", 0, &cv_kartbumpspring, -1, true}, - {"Alt. Invin.", 0, NULL, K_GetKartInvinType() == KARTINVIN_ALTERN, true} + {"Alt. Invin.", 0, NULL, K_IsKartItemAlternate(KITEM_INVINCIBILITY), true} }; for (j = 0; j < 2; j++) @@ -4335,9 +4335,9 @@ static void K_drawKartMinimap(void) invingradient = K_InvincibilityGradient(players[i].invincibilitytimer); - if ((players[i].invincibilitytimer) && ((invingradient > (FRACUNIT/2)) || (K_GetKartInvinType() == KARTINVIN_LEGACY))) + if ((players[i].invincibilitytimer) && ((invingradient > (FRACUNIT/2)) || (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))) { - usecolor = ((K_GetKartInvinType() == KARTINVIN_ALTERN) ? K_AltInvincibilityColor(leveltime / 2) : K_RainbowColor(leveltime / 2)); + usecolor = ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) ? K_AltInvincibilityColor(leveltime / 2) : K_RainbowColor(leveltime / 2)); colorizeplayer = true; } else @@ -4370,7 +4370,7 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, &iconoffsets); #ifdef ROTSPRITE - if ((K_GetKartInvinType() == KARTINVIN_ALTERN) && + if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (players[i].invincibilitytimer) && (invingradient)) { // Draw Alt. Invin. sparkles @@ -4665,7 +4665,7 @@ static void K_drawKartMinimap(void) if ((players[localplayers[i]].invincibilitytimer) && ((invingradient > (FRACUNIT / 2)) || - (K_GetKartInvinType() == KARTINVIN_LEGACY))) + (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))) { usecolor = (K_RainbowColor(leveltime / 2)); colorizeplayer = true; @@ -4735,7 +4735,7 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, &iconoffsets); #ifdef ROTSPRITE - if ((!nocontest) && (K_GetKartInvinType() == KARTINVIN_ALTERN) && + if ((!nocontest) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (players[localplayers[i]].invincibilitytimer) && (invingradient)) { // Draw Alt. Invin. sparkles diff --git a/src/k_kart.c b/src/k_kart.c index 08fb83f3a..b848b6a1b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -912,7 +912,7 @@ static fixed_t K_CheckOffroadCollide(mobj_t *mo) static fixed_t K_OffroadGradient(player_t *player, fixed_t offroad) { // At 50% or lower Invincibility, offroad creeps up on you. - fixed_t invinoffroad = (K_GetKartInvinType() == KARTINVIN_LEGACY) ? ((player->invincibilitytimer) ? FRACUNIT : 0) : min(FRACUNIT, K_InvincibilityGradient(player->invincibilitytimer) << 1); + fixed_t invinoffroad = (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) ? ((player->invincibilitytimer) ? FRACUNIT : 0) : min(FRACUNIT, K_InvincibilityGradient(player->invincibilitytimer) << 1); fixed_t fac = CLAMP(FRACUNIT - invinoffroad, 0, FRACUNIT); return FixedMul(offroad, fac); @@ -2412,7 +2412,7 @@ UINT16 K_GetInvincibilityTime(player_t *player) UINT32 i, pingame; fixed_t distmul = FRACUNIT; - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return BASEINVINTIME; pingame = 0; @@ -2449,7 +2449,7 @@ UINT16 K_GetInvincibilityTime(player_t *player) static fixed_t K_GetInvincibilitySpeed(UINT16 time) { - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return INVINSPEEDBOOSTLGC; fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); @@ -2458,7 +2458,7 @@ static fixed_t K_GetInvincibilitySpeed(UINT16 time) static fixed_t K_GetInvincibilityAccel(UINT16 time) { - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return INVINACCELBOOSTLGC; fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); @@ -5035,7 +5035,7 @@ void K_DoInvincibility(player_t *player, tic_t time) overlay->destscale = player->mo->scale; P_SetScale(overlay, player->mo->scale); - if (K_GetKartInvinType() == KARTINVIN_ALTERN) + if (K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { mobj_t *aura = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_OVERLAY); P_SetTarget(&aura->target, player->mo); @@ -5045,7 +5045,7 @@ void K_DoInvincibility(player_t *player, tic_t time) } } - if (K_GetKartInvinType() == KARTINVIN_ALTERN) + if (K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { // Rim suggestion: Don't allow already invincible players to chain. if (K_InvincibilityGradient(player->invincibilitytimer) < (FRACUNIT/2)) @@ -7286,7 +7286,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->invincibilitytimer) { INT16 invinfac = 1; - if ((K_GetKartInvinType() == KARTINVIN_ALTERN) && (player->invincibilitytimer > 2)) + if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (player->invincibilitytimer > 2)) { UINT32 invindist = INVINDIST >> 2; @@ -7684,7 +7684,7 @@ void K_KartResetPlayerColor(player_t *player) { boolean skip = false; - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { fullbright = true; @@ -10221,7 +10221,7 @@ void K_UnsetItemOut(player_t *player) static boolean K_InvincibilitySlotHogging(player_t *player) { - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return false; return ((player->invincibilitytimer) != 0); @@ -10263,7 +10263,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) || player->itemroulette || player->rocketsneakertimer || player->eggmanexplode - || ((K_GetKartInvinType() == KARTINVIN_ALTERN) && player->invincibilitytimer) + || ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && player->invincibilitytimer) || (player->growshrinktimer > 0) || player->flametimer || (leveltime < starttime) @@ -11455,6 +11455,24 @@ boolean K_GetKartInvinType(void) return invintype; } +boolean K_IsKartItemAlternate(UINT8 itemtype) +{ + switch (itemtype) + { + case KITEM_INVINCIBILITY: + return invintype == KARTITEM_ALTERN; + break; + case KITEM_EGGMAN: + return eggmantype == KARTITEM_ALTERN; + break; + + default: + return false; + break; + } + return false; +} + boolean K_UsingLegacyCheckpoints(void) { if (K_UsingPatchedMap() && waypointcap != NULL) diff --git a/src/k_kart.h b/src/k_kart.h index d800661e7..e85aae584 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -345,6 +345,7 @@ boolean K_AirDropActive(void); boolean K_ItemLitterActive(void); boolean K_ItemPushingActive(void); boolean K_GetKartInvinType(void); +boolean K_IsKartItemAlternate(UINT8 itemtype); INT32 K_GetBumpSpark(void); boolean K_BoostChain(player_t *player, INT32 timer, boolean chainsound); INT32 K_ChainOrDeincrementTime(player_t *player, INT32 timer, INT32 deincrement, boolean chainsound); diff --git a/src/k_odds.c b/src/k_odds.c index a5f7d5747..7ad24ea05 100644 --- a/src/k_odds.c +++ b/src/k_odds.c @@ -108,7 +108,7 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Pogo Spring { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Kitchen Sink { 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Super Ring - { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Land Mine + { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Land Mine / Egg Mine { 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Bubble Shield { 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0}, // Flame Shield { 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0}, // Sneaker x2 @@ -140,7 +140,7 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { 3, 0 }, // Pogo Spring { 0, 0 }, // Kitchen Sink { 0, 0 }, // Super Ring - { 2, 0 }, // Land Mine + { 2, 0 }, // Land Mine / Egg Mine { 1, 0 }, // Bubble Shield { 1, 0 }, // Flame Shield { 0, 0 }, // Sneaker x2 @@ -173,7 +173,7 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 0, 0, 0 }, // Pogo Spring { 0, 0, 0, 0 }, // Kitchen Sink { 0, 0, 0, 0 }, // Super Ring - { 0, 0, 0, 0 }, // Land Mine + { 0, 0, 0, 0 }, // Land Mine / Egg Mine { 0, 0, 0, 0 }, // Bubble Shield { 0, 0, 0, 0 }, // Flame Shield { 0, 1, 1, 0 }, // Sneaker x2 @@ -209,7 +209,7 @@ tic_t ItemBGone[NUMKARTRESULTS][2] = { 0, 0 }, // Pogo Spring { 0, 0 }, // Kitchen Sink { 0, 0 }, // Super Ring - { 0, 0 }, // Land Mine + { 0, 0 }, // Land Mine / Egg Mine { 5, 0 }, // Bubble Shield { 8, 0 }, // Flame Shield { 0, 0 }, // Sneaker x2 @@ -758,7 +758,7 @@ INT32 K_KartGetItemOdds( notNearEnd = true; break; case KITEM_INVINCIBILITY: - if ((K_GetKartInvinType() == KARTINVIN_ALTERN) && (gametyperules & GTR_RACEODDS)) + if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (gametyperules & GTR_RACEODDS)) { // It's a power item, yes, but we don't want mashing to lessen // its chances, so we lie to the game's face. diff --git a/src/p_inter.c b/src/p_inter.c index 951bd2ffe..64cf29405 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -147,7 +147,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) if (player->stealingtimer || player->stolentimer || player->rocketsneakertimer || player->eggmanexplode - || ((K_GetKartInvinType() == KARTINVIN_ALTERN) && (player->invincibilitytimer)) + || ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (player->invincibilitytimer)) || (player->growshrinktimer > 0) || player->flametimer) return false; diff --git a/src/p_mobj.c b/src/p_mobj.c index e3847f74b..599ddf2ea 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1229,6 +1229,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo) case MT_EGGMANITEM: case MT_SSMINE: case MT_LANDMINE: + case MT_EGGMINE_CAPSULE: case MT_SINK: if (mo->extravalue2 > 0) { @@ -6514,6 +6515,7 @@ boolean P_IsKartFieldItem(INT32 type) case MT_JAWZ_DUD: case MT_SSMINE: case MT_LANDMINE: + case MT_EGGMINE_CAPSULE: case MT_BALLHOG: case MT_BUBBLESHIELDTRAP: case MT_SINK: @@ -8014,6 +8016,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj) case MT_BANANA: case MT_EGGMANITEM: case MT_LANDMINE: + case MT_EGGMINE_CAPSULE: case MT_SPB: if (P_IsObjectOnGround(mobj)) { @@ -8806,6 +8809,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (mobj->threshold > 0) mobj->threshold--; break; + case MT_EGGMINE_CAPSULE: + // todo + // decrement fuse while on ground + // when fuse runs out pop open and spawn a land mine + break; case MT_SPBEXPLOSION: mobj->health--; break; @@ -10921,6 +10929,8 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_SSMINE: case MT_SSMINE_SHIELD: case MT_LANDMINE: + case MT_EGGMINE_CAPSULE: + case MT_EGGMINE_SHIELD: case MT_BALLHOG: case MT_SINK: case MT_ROCKETSNEAKER: diff --git a/src/p_user.c b/src/p_user.c index 315556831..160790608 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2286,7 +2286,7 @@ void P_MovePlayer(player_t *player) if ((player->invincibilitytimer > 0) && (((leveltime % max(1, 10 - FixedMul(9, K_InvincibilityGradient(player->invincibilitytimer)))) == 0) || - (K_GetKartInvinType() == KARTINVIN_LEGACY))) + (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))) { K_SpawnSparkleTrail(player->mo); } diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index f2ccfe6ec..607a8afe4 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -84,7 +84,7 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) // Hacky boolean to check if we're the rainbow Invincibility overlay boolean R_IsOverlayingInvinciblePlayer(mobj_t* mobj) { - return ((K_GetKartInvinType() == KARTINVIN_ALTERN) && (mobj->type == MT_OVERLAY) && + return ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (mobj->type == MT_OVERLAY) && (mobj->target) && (mobj->extravalue2) && (mobj->target->player) && (mobj->target->player->invincibilitytimer)); } From b59a9272ea4e0720dbd4501c393b7c867c80845d Mon Sep 17 00:00:00 2001 From: minenice55 Date: Tue, 4 Nov 2025 23:30:28 -0500 Subject: [PATCH 02/76] egg mine bracket --- src/d_player.h | 3 +++ src/k_kart.c | 2 +- src/k_odds.c | 53 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 872b3dc32..e97940990 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -182,6 +182,9 @@ typedef enum KRITEM_TRIPLEORBINAUT, KRITEM_QUADORBINAUT, KRITEM_DUALJAWZ, + + // alt items implemented as separate things + KAITEM_EGGMINE, // also landmine NUMKARTRESULTS } kartitems_t; diff --git a/src/k_kart.c b/src/k_kart.c index b848b6a1b..d425d51f2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -251,7 +251,7 @@ void K_RegisterKartStuff(void) // Nu-ITEMS CV_RegisterVar(&cv_superring); - CV_RegisterVar(&cv_landmine); + // CV_RegisterVar(&cv_landmine); CV_RegisterVar(&cv_bubbleshield); CV_RegisterVar(&cv_flameshield); diff --git a/src/k_odds.c b/src/k_odds.c index 7ad24ea05..c17277a55 100644 --- a/src/k_odds.c +++ b/src/k_odds.c @@ -56,7 +56,7 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_pogospring, &cv_kitchensink, &cv_superring, - &cv_landmine, + // &cv_landmine, &cv_bubbleshield, &cv_flameshield, &cv_dualsneaker, @@ -108,7 +108,7 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Pogo Spring { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Kitchen Sink { 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Super Ring - { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Land Mine / Egg Mine + { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Land Mine { 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Bubble Shield { 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0}, // Flame Shield { 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0}, // Sneaker x2 @@ -117,7 +117,8 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Banana x10 { 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x3 { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x4 - { 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Jawz x2 + { 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Jawz x2 + { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Egg Mine }; static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = @@ -140,7 +141,7 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { 3, 0 }, // Pogo Spring { 0, 0 }, // Kitchen Sink { 0, 0 }, // Super Ring - { 2, 0 }, // Land Mine / Egg Mine + { 2, 0 }, // Land Mine { 1, 0 }, // Bubble Shield { 1, 0 }, // Flame Shield { 0, 0 }, // Sneaker x2 @@ -149,7 +150,8 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { 1, 1 }, // Banana x10 { 2, 0 }, // Orbinaut x3 { 1, 1 }, // Orbinaut x4 - { 5, 1 } // Jawz x2 + { 5, 1 }, // Jawz x2 + { 2, 0 } // Egg Mine }; /* @@ -173,7 +175,7 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 0, 0, 0 }, // Pogo Spring { 0, 0, 0, 0 }, // Kitchen Sink { 0, 0, 0, 0 }, // Super Ring - { 0, 0, 0, 0 }, // Land Mine / Egg Mine + { 0, 0, 0, 0 }, // Land Mine { 0, 0, 0, 0 }, // Bubble Shield { 0, 0, 0, 0 }, // Flame Shield { 0, 1, 1, 0 }, // Sneaker x2 @@ -182,7 +184,8 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 0, 0, 0 }, // Banana x10 { 0, 1, 1, 0 }, // Orbinaut x3 { 0, 0, 1, 1 }, // Orbinaut x4 - { 0, 0, 1, 1 } // Jawz x2 + { 0, 0, 1, 1 }, // Jawz x2 + { 0, 0, 0, 0 } // Egg Mine }; */ @@ -209,7 +212,7 @@ tic_t ItemBGone[NUMKARTRESULTS][2] = { 0, 0 }, // Pogo Spring { 0, 0 }, // Kitchen Sink { 0, 0 }, // Super Ring - { 0, 0 }, // Land Mine / Egg Mine + { 0, 0 }, // Land Mine { 5, 0 }, // Bubble Shield { 8, 0 }, // Flame Shield { 0, 0 }, // Sneaker x2 @@ -218,7 +221,8 @@ tic_t ItemBGone[NUMKARTRESULTS][2] = { 30, 0 }, // Banana x10 { 10, 0 }, // Orbinaut x3 { 20, 0 }, // Orbinaut x4 - { 10, 0 } // Jawz x2 + { 10, 0 }, // Jawz x2 + { 0, 0 } // Egg Mine }; // TODO: Vectorize all item tables and shove them into gamemode-uniqe pools (k_oddstable.cpp?). @@ -718,12 +722,27 @@ INT32 K_KartGetItemOdds( notForBottom = true; break; case KITEM_EGGMAN: - // It blows you up and is overall ridiculous. This was *overdue*. - cooldownOnStart = true; - powerItem = true; - - notNearEnd = true; - notForBottom = true; + if (K_IsKartItemAlternate(KITEM_EGGMAN)) + { + notForBottom = true; + if (gametyperules & GTR_BATTLEODDS) + { + newodds = K_KartItemOddsBattle[KAITEM_EGGMINE-1][pos]; + } + else if (gametyperules & GTR_RACEODDS) + { + newodds = K_KartItemOddsRace[KAITEM_EGGMINE-1][pos]; + } + } + else + { + // It blows you up and is overall ridiculous. This was *overdue*. + cooldownOnStart = true; + powerItem = true; + + notNearEnd = true; + notForBottom = true; + } break; case KITEM_SUPERRING: notNearEnd = true; @@ -734,9 +753,11 @@ INT32 K_KartGetItemOdds( newodds = 0; } + break; + case KITEM_LANDMINE: // au revoir + newodds = 0; break; case KITEM_ROCKETSNEAKER: - case KITEM_LANDMINE: case KRITEM_TRIPLESNEAKER: powerItem = true; break; From 47fced5c094ee264066729d3e92e081b867ce648 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Tue, 4 Nov 2025 23:44:23 -0500 Subject: [PATCH 03/76] Alt Shrink * Player gains a big speed boost * Anything that would squish a shrunken player in Legacy (save for Grow players) flips them over here * Most items rolled are doubled: singles become doubles, doubles become quadruples, and you can roll 6 bananas * Certain multi items (3sneaker, 3orbi and 4orbi, 2jawz, decanana, ballhog) do NOT get doubled for balance reasons * Shrunken players get rival bot odds, making their rolls more aggressive and frantic --- src/Sourcefile | 1 + src/command.h | 2 +- src/d_netcmd.c | 37 ++++++++++++-- src/d_player.h | 1 + src/g_game.c | 6 ++- src/g_game.h | 3 +- src/k_altshrink.c | 81 +++++++++++++++++++++++++++++ src/k_altshrink.h | 70 +++++++++++++++++++++++++ src/k_collide.c | 86 ++++++++++++++++++++++--------- src/k_kart.c | 127 +++++++++++++++++++++++++++++++++------------- src/k_odds.c | 60 +++++++++++++++++++--- src/p_saveg.c | 2 + src/p_setup.c | 2 + src/p_spec.c | 4 +- src/p_user.c | 11 ++++ 15 files changed, 420 insertions(+), 73 deletions(-) create mode 100644 src/k_altshrink.c create mode 100644 src/k_altshrink.h diff --git a/src/Sourcefile b/src/Sourcefile index 8453c55e6..87fa585b3 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -134,6 +134,7 @@ k_botitem.cpp k_botsearch.cpp k_cluster.cpp k_odds.c +k_altshrink.c k_grandprix.c k_boss.c k_hud.c diff --git a/src/command.h b/src/command.h index b6e99ebf2..1cbf3d617 100644 --- a/src/command.h +++ b/src/command.h @@ -188,7 +188,7 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTGP_MASTER 4 // Not a speed setting, gives hard speed with maxed out bots #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots extern CV_PossibleValue_t kartspeed_cons_t[], gpdifficulty_cons_t[]; -// Invincibility types. +// Invincibility types. FIXME: Make this a more generalized name eventually (please...) #define KARTINVIN_LEGACY 0 #define KARTINVIN_ALTERN 1 diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 79548c2ab..e7d501769 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -68,6 +68,7 @@ #include "m_perfstats.h" #include "g_party.h" #include "k_specialstage.h" +#include "k_altshrink.h" #define CV_RESTRICT CV_NETVAR @@ -167,6 +168,7 @@ static void KartItemPush_OnChange(void); static void KartAntiBump_OnChange(void); static void KartItemBreaker_OnChange(void); static void KartInvinType_OnChange(void); +static void KartShrinkType_OnChange(void); static void KartBumpSpark_OnChange(void); static void Schedule_OnChange(void); @@ -499,6 +501,10 @@ consvar_t cv_kartstacking_grow_speedboost = CVAR_INIT ("vanillaboost_grow_speedb consvar_t cv_kartstacking_grow_accelboost = CVAR_INIT ("vanillaboost_grow_accelboost", "0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_grow_stackable = CVAR_INIT ("vanillaboost_grow_stackable", "Off", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); +consvar_t cv_kartstacking_altshrink_speedboost = CVAR_INIT ("vanillaboost_altshrink_speedboost", "0.56", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); +consvar_t cv_kartstacking_altshrink_accelboost = CVAR_INIT ("vanillaboost_altshrink_accelboost", "0.15", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); +consvar_t cv_kartstacking_altshrink_stackable = CVAR_INIT ("vanillaboost_altshrink_stackable", "On", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); + consvar_t cv_kartstacking_bubble_speedboost = CVAR_INIT ("vanillaboost_bubble_speedboost", "0.35", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_bubble_accelboost = CVAR_INIT ("vanillaboost_bubble_accelboost", "8.0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_bubble_stackable = CVAR_INIT ("vanillaboost_bubble_stackable", "Off", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); @@ -594,9 +600,14 @@ consvar_t cv_kartspbdist = CVAR_INIT ("kartspbdist", "4432", CV_NETVAR|CV_CHEAT| consvar_t cv_kartlegacyspbdist = CVAR_INIT ("kartlegacyspbdist", "2216", CV_NETVAR|CV_CHEAT|CV_GUARD, spbdist_cons_t, NULL); #undef MAXODDSDIST -// Invincibility modifiers -static CV_PossibleValue_t invintype_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; -consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, invintype_cons_t, KartInvinType_OnChange); +// Items with alternative types +static CV_PossibleValue_t kartitemtype_cons_t[] = {{KARTINVIN_LEGACY, "Legacy"}, {KARTINVIN_ALTERN, "Alternative"}, {0, NULL}}; +consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, kartitemtype_cons_t, KartInvinType_OnChange); +consvar_t cv_kartshrinktype = CVAR_INIT ("kartshrinktype", "Legacy", CV_NETVAR|CV_CALL, kartitemtype_cons_t, KartShrinkType_OnChange); + +// Time limit for Alt. Shrink +static CV_PossibleValue_t altshrinktime_cons_t[] = {{0, "MIN"}, {(INT16_MAX / TICRATE), "MAX"}, {0, NULL}}; +consvar_t cv_kartaltshrinktime = CVAR_INIT ("kartaltshrinktime", "13", CV_NETVAR|CV_CHEAT|CV_GUARD, altshrinktime_cons_t, NULL); // How far the player must be from the cluster to begin frequently rolling Invincibility. static CV_PossibleValue_t invindist_cons_t[] = {{1, "MIN"}, {32000, "MAX"}, {0, NULL}}; @@ -1271,6 +1282,7 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_growmusic); CV_RegisterVar(&cv_supermusic); + CV_RegisterVar(&cv_altshrinkmusic); CV_RegisterVar(&cv_songcredits); CV_RegisterVar(&cv_tutorialprompt); @@ -1337,6 +1349,7 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_invincmusicfade); CV_RegisterVar(&cv_growmusicfade); + CV_RegisterVar(&cv_altshrinkmusicfade); CV_RegisterVar(&cv_resetspecialmusic); @@ -7831,6 +7844,24 @@ static void KartInvinType_OnChange(void) } } +static void KartShrinkType_OnChange(void) +{ + if (K_CanChangeRules(false) == false) + { + return; + } + + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Shrink type has been changed to \"%s\".\n"), cv_kartshrinktype.string); + shrinktype = (UINT8)cv_kartshrinktype.value; + } + else + { + CONS_Printf(M_GetText("Shrink type will be changed to \"%s\" next round.\n"), cv_kartshrinktype.string); + } +} + static void KartBumpSpark_OnChange(void) { if (K_CanChangeRules(false) == false) diff --git a/src/d_player.h b/src/d_player.h index 872b3dc32..afd782980 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -724,6 +724,7 @@ struct player_t fixed_t slopeaccel; // Handle slopeboost accel INT16 growshrinktimer; // > 0 = Big, < 0 = small + INT16 altshrinktimeshit; // (Alt. Shrink) How many times were you flipped over while shrunk? INT16 growcancel; // Duration of grow canceling INT16 squishedtimer; // Duration of being squished diff --git a/src/g_game.c b/src/g_game.c index e9249f005..fa0022f81 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -62,6 +62,7 @@ #include "k_specialstage.h" #include "k_bot.h" #include "k_odds.h" +#include "k_altshrink.h" #include "doomstat.h" #include "acs/interface.h" #include "k_director.h" @@ -442,11 +443,13 @@ consvar_t cv_showfreeplay = CVAR_INIT ("showfreeplay", "On", CV_SAVE, CV_OnOff, static CV_PossibleValue_t powermusic_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "SFX"}, {0, NULL}}; consvar_t cv_growmusic = CVAR_INIT ("growmusic", "On", CV_SAVE, powermusic_cons_t, NULL); consvar_t cv_supermusic = CVAR_INIT ("supermusic", "On", CV_SAVE, powermusic_cons_t, NULL); +consvar_t cv_altshrinkmusic = CVAR_INIT ("altshrinkmusic", "On", CV_SAVE, powermusic_cons_t, NULL); consvar_t cv_invertmouse = CVAR_INIT ("invertmouse", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_invincmusicfade = CVAR_INIT ("invincmusicfade", "300", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_growmusicfade = CVAR_INIT ("growmusicfade", "500", CV_SAVE, CV_Unsigned, NULL); +consvar_t cv_altshrinkmusicfade = CVAR_INIT ("altshrinkmusicfade", "500", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_resetspecialmusic = CVAR_INIT ("resetspecialmusic", "Yes", CV_SAVE, CV_YesNo, NULL); @@ -2864,7 +2867,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) } // Keep Shrink status, remove Grow status - if (players[player].growshrinktimer < 0) + // Alt. Shrink is a powerup, so don't keep that. + if (K_IsLegacyShrunk(&players[player])) growshrinktimer = players[player].growshrinktimer; else growshrinktimer = 0; diff --git a/src/g_game.h b/src/g_game.h index da46b853c..f61668e9a 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -71,7 +71,7 @@ extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, extern consvar_t cv_shoutname, cv_shoutcolor, cv_autoshout; extern consvar_t cv_songcredits; extern consvar_t cv_showfreeplay; -extern consvar_t cv_growmusic, cv_supermusic; +extern consvar_t cv_growmusic, cv_supermusic, cv_altshrinkmusic; extern consvar_t cv_pauseifunfocused; @@ -88,6 +88,7 @@ extern consvar_t cv_ghost_besttime, cv_ghost_bestlap, cv_ghost_last, cv_ghost_gu extern consvar_t cv_invincmusicfade; extern consvar_t cv_growmusicfade; +extern consvar_t cv_altshrinkmusicfade; extern consvar_t cv_resetspecialmusic; diff --git a/src/k_altshrink.c b/src/k_altshrink.c new file mode 100644 index 000000000..ff94a98ab --- /dev/null +++ b/src/k_altshrink.c @@ -0,0 +1,81 @@ +// BLANKART +//----------------------------------------------------------------------------- +/// \file k_altshrink.c +/// \brief Alternative Shrink item type functions. + +#include "doomdef.h" +#include "doomtype.h" +#include "doomstat.h" + +#include "command.h" +#include "console.h" +#include "d_player.h" +#include "k_altshrink.h" +#include "k_cluster.hpp" +#include "m_fixed.h" +#include "p_local.h" + + +UINT8 shrinktype; + +void K_UpdateShrinkType(void) +{ + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Shrink type has been changed to \"%s\".\n"), cv_kartshrinktype.string); + } + + shrinktype = (UINT8)cv_kartshrinktype.value; +} + +/** \brief Depending on the Shrink type set, this returns true or false if the player is shrunk. + + \param player (player_t) Player to test shrunken status for + \param legacy (boolean) Legacy shrink? + + \return void +*/ +boolean K_IsShrunkMode(player_t *player, boolean legacy) +{ + boolean shrunk = K_IsShrunk(player); + boolean legacytype = (shrinktype == KARTINVIN_LEGACY); + + if (legacy) + { + return ((shrunk) && (legacytype)); + } + + return ((shrunk) && (!legacytype)); +} + +#define ALTSHRINK_EPSILON (320 * FRACUNIT) +#define NEIGHBOR_IFRAMES (TICRATE / 2) +#define BASE_IFRAMES (2 * TICRATE) + +void K_AltShrinkIFrames(player_t *player) +{ + vector3_t tempclusterpoint; + + // Find neighbors + INT32 N = K_CountNeighboringPlayers(player, ALTSHRINK_EPSILON, &tempclusterpoint); + + // For every neighbor, add some iframes for a clean breakaway. + player->flashing = (UINT16)min((INT32)(UINT16_MAX), BASE_IFRAMES + (NEIGHBOR_IFRAMES * N)); +} + +#define PITY_SHRINKINCREASE_BASE (3) +#define PITY_SHRINKINCREASE (TICRATE / 2) + +void K_AltShrinkPityIncrease(player_t *player) +{ + // Increase your shrink timer by a little bit for every player you run into. + INT32 shrinktime = (UINT32)(K_GetShrinkTime(player)); + + fixed_t dimin = FRACUNIT; + + dimin = max(0, 5 - player->altshrinktimeshit) * FRACUNIT / 5; + + shrinktime = min(INT16_MAX, shrinktime + PITY_SHRINKINCREASE_BASE + FixedMul(PITY_SHRINKINCREASE, dimin)); + + player->growshrinktimer = ((INT16)shrinktime) * -1; +} \ No newline at end of file diff --git a/src/k_altshrink.h b/src/k_altshrink.h new file mode 100644 index 000000000..e535f94f1 --- /dev/null +++ b/src/k_altshrink.h @@ -0,0 +1,70 @@ +// BLANKART +//----------------------------------------------------------------------------- +/// \file k_altshrink.h +/// \brief Alternative Shrink item type functions. + +#ifndef __K_ALTSHRINK__ +#define __K_ALTSHRINK__ + +#include "doomdef.h" +#include "d_player.h" // Need for player_t +#include "command.h" // Need for player_t +#include "m_fixed.h" +#include "k_kart.h" + +extern consvar_t cv_kartaltshrinktime; +extern consvar_t cv_kartshrinktype; + +extern consvar_t cv_kartstacking_altshrink_speedboost; +extern consvar_t cv_kartstacking_altshrink_accelboost; +extern consvar_t cv_kartstacking_altshrink_stackable; + +extern UINT8 shrinktype; + +#define ALTSHRINKTIME CV_Get(&cv_kartaltshrinktime) + +#define SHRINKSPEEDBOOST CV_Get(&cv_kartstacking_altshrink_speedboost) +#define SHRINKACCELBOOST CV_Get(&cv_kartstacking_altshrink_accelboost) +#define SHRINKSTACKABLE CV_Get(&cv_kartstacking_altshrink_stackable) + +/** \brief Returns true or false if Alt. Shrink is active. + + \return Are we using the alternative Shrink type? +*/ +FUNCINLINE static ATTRINLINE boolean K_AltShrinkActive(void) +{ + return (shrinktype == KARTINVIN_ALTERN); +} + + +/** \brief Returns true or false if the player is shrunk. + + \param player (player_t) Player to test shrunken status for + + \return Is this player shrunk? +*/ +FUNCINLINE static ATTRINLINE boolean K_IsShrunk(player_t *player) +{ + return (player->growshrinktimer < 0); +} + +FUNCINLINE static ATTRINLINE INT16 K_GetShrinkTime(player_t *player) +{ + return (player->growshrinktimer * -1); +} + +FUNCINLINE static ATTRINLINE fixed_t K_AccomodateShrinkScaling(fixed_t x) +{ + return FixedDiv(x, SHRINK_SCALE); +} + +boolean K_IsShrunkMode(player_t *player, boolean legacy); + +#define K_IsLegacyShrunk(player) (K_IsShrunkMode(player, true)) +#define K_IsAltShrunk(player) (K_IsShrunkMode(player, false)) + +void K_UpdateShrinkType(void); +void K_AltShrinkIFrames(player_t *player); +void K_AltShrinkPityIncrease(player_t *player); + +#endif // __K_ALTSHRINK__ \ No newline at end of file diff --git a/src/k_collide.c b/src/k_collide.c index 44769c1d5..8e73ebe8b 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -2,6 +2,7 @@ /// \brief SRB2Kart item collision hooks #include "k_collide.h" +#include "k_altshrink.h" #include "k_stats.h" #include "doomstat.h" #include "doomtype.h" @@ -857,19 +858,37 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) { if (G_CompatLevel(0x0008) || !hyudoroT1) { - switch (cv_kartairsquish.value) + if ((t2->player) && (K_IsAltShrunk(t2->player)) && + (!(t1->player && t1->player->growshrinktimer > 0))) { - case 1: - P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH); - return true; - break; - case 2: - P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER); - return true; - break; - default: - return false; - break; + // Alt. Shrink: Just make the player flip over. + if (P_DamageMobj(t2, NULL, NULL, 1, DMG_FLIPOVER)) + { + K_AltShrinkPityIncrease(t2->player); + t2->player->altshrinktimeshit++; + K_PlayPainSound(t2, NULL); + } + } + if (P_IsObjectOnGround(t1) && P_IsObjectOnGround(t2)) + { + P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH); + } + else + { + switch (cv_kartairsquish.value) + { + case 1: + P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH); + return true; + break; + case 2: + P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER); + return true; + break; + default: + return false; + break; + } } } } @@ -878,20 +897,39 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) if (G_CompatLevel(0x0008) || !hyudoroT2) { // shitty code duplication 🥲 - switch (cv_kartairsquish.value) + + if ((t1->player) && (K_IsAltShrunk(t1->player)) && + (!(t2->player && t2->player->growshrinktimer > 0))) { - case 1: - P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH); - return true; - break; - case 2: - P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER); - return true; - break; - default: - return false; - break; + // Alt. Shrink: Just make the player flip over. + if (P_DamageMobj(t1, NULL, NULL, 1, DMG_FLIPOVER)) + { + K_AltShrinkPityIncrease(t1->player); + t1->player->altshrinktimeshit++; + K_PlayPainSound(t1, NULL); + } } + else if (P_IsObjectOnGround(t1) && P_IsObjectOnGround(t2)) + { + P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH); + } + else + { + switch (cv_kartairsquish.value) + { + case 1: + P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH); + return true; + break; + case 2: + P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER); + return true; + break; + default: + return false; + break; + } + } } return true; } diff --git a/src/k_kart.c b/src/k_kart.c index 08fb83f3a..80f3cd405 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -66,6 +66,7 @@ #include "k_cluster.hpp" #include "k_odds.h" #include "k_specialstage.h" +#include "k_altshrink.h" #include "h_timers.h" #include "blan/b_soc.h" @@ -345,6 +346,10 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartstacking_grow_accelboost); CV_RegisterVar(&cv_kartstacking_grow_stackable); + CV_RegisterVar(&cv_kartstacking_altshrink_speedboost); + CV_RegisterVar(&cv_kartstacking_altshrink_accelboost); + CV_RegisterVar(&cv_kartstacking_altshrink_stackable); + CV_RegisterVar(&cv_kartstacking_bubble_speedboost); CV_RegisterVar(&cv_kartstacking_bubble_accelboost); CV_RegisterVar(&cv_kartstacking_bubble_stackable); @@ -410,6 +415,9 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartslopeboost); + CV_RegisterVar(&cv_kartshrinktype); + CV_RegisterVar(&cv_kartaltshrinktime); + CV_RegisterVar(&cv_kartinvintype); CV_RegisterVar(&cv_kartinvindist); CV_RegisterVar(&cv_kartinvindistmul); @@ -1597,7 +1605,7 @@ static void K_RespawnChecker(player_t *player) } else if (player->respawn == 1) { - if (player->growshrinktimer < 0) + if (K_IsShrunk(player)) { player->mo->scalespeed = mapobjectscale/TICRATE; player->mo->destscale = (6*mapobjectscale)/8; @@ -2665,6 +2673,14 @@ static void K_GetKartBoostPower(player_t *player) K_DoBoost(player, GROWSPEEDBOOST, GROWACCELBOOST, GROWSTACKABLE, GROWSTACKABLE); // + 20% top speed, + 0% acceleration } + if (K_IsAltShrunk(player)) // Alt. Shrink + { + fixed_t shrinkspeed = K_AccomodateShrinkScaling(SHRINKSPEEDBOOST); + fixed_t shrinkaccel = K_AccomodateShrinkScaling(SHRINKACCELBOOST); + + K_DoBoost(player, FixedMul(shrinkspeed, GROW_SCALE), FixedMul(shrinkaccel, GROW_SCALE), SHRINKSTACKABLE, SHRINKSTACKABLE); // + 45% top speed, + 200% acceleration + } + if (player->bubbleboost) // Bubble Shield popping boost { K_DoBoost(player, BUBBLESPEEDBOOST, BUBBLEACCELBOOST, BUBBLESTACKABLE, BUBBLESTACKABLE); // + 35% top speed, + 800% acceleration @@ -3324,7 +3340,7 @@ static void K_RemoveGrowShrink(player_t *player) { if (player->growshrinktimer > 0) // Play Shrink noise S_StartSound(player->mo, sfx_kc59); - else if (player->growshrinktimer < 0) // Play Grow noise + else if (K_IsShrunk(player)) // Play Grow noise S_StartSound(player->mo, sfx_kc5a); K_KartResetPlayerColor(player); @@ -3351,8 +3367,8 @@ void K_SquishPlayer(player_t *player, mobj_t *inflictor, mobj_t *source) player->squishedtimer = TICRATE; - // Reduce Shrink timer - if (player->growshrinktimer < 0) + // Reduce Shrink timer for Legacy Shrink + if (K_IsLegacyShrunk(player)) { player->growshrinktimer += TICRATE; if (player->growshrinktimer >= 0) @@ -5073,6 +5089,44 @@ void K_DoInvincibility(player_t *player, tic_t time) P_RestoreMusic(player); } +static void K_DoGrowShrink(player_t *player, boolean shrinking) +{ + player->mo->scalespeed = mapobjectscale/TICRATE; + player->mo->destscale = FixedMul(mapobjectscale, (shrinking) ? SHRINK_SCALE : GROW_SCALE); + + if (K_PlayerShrinkCheat(player) == true) + { + player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); + } + + if ((shrinking) && (ALTSHRINKTIME > 0)) + { + player->growshrinktimer = ALTSHRINKTIME * -TICRATE; + } + else + { + player->growshrinktimer = (gametyperules & GTR_CLOSERPLAYERS ? 8 : 12) * ((shrinking) ? -TICRATE : TICRATE); + } + + if (player->invincibilitytimer > 0) + { + ; // invincibility has priority in P_RestoreMusic, no point in starting here + } + else if (P_IsLocalPlayer(player) == true) + { + S_ChangeMusicSpecial((shrinking) ? "kshrnk" : "kgrow"); + } + else //used to be "if (P_IsDisplayPlayer(player) == false)" + { + if (!shrinking) + { + S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); + } + } + + P_RestoreMusic(player); +} + void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source) { mobj_t *cachenext; @@ -6366,6 +6420,7 @@ static void K_UpdateInvincibilitySounds(player_t *player) sfxnum = cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow; else if (player->invincibilitytimer > 0 && (!localplayer || cv_supermusic.value == 2)) sfxnum = cv_kartinvinsfx.value ? sfx_alarmi : sfx_kinvnc; + // FIXME: Does Alt. Shrink need an alarm? } if (sfxnum != sfx_None && !S_SoundPlaying(player->mo, sfxnum)) @@ -7375,7 +7430,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { if (player->growshrinktimer > 0) player->growshrinktimer--; - if (player->growshrinktimer < 0) + if (K_IsShrunk(player)) player->growshrinktimer++; // Back to normal @@ -7709,7 +7764,7 @@ void K_KartResetPlayerColor(player_t *player) if (player->growshrinktimer % 5 == 0) { player->mo->colorized = true; - player->mo->color = (player->growshrinktimer < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE); + player->mo->color = (K_IsShrunk(player) ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE); fullbright = true; goto finalise; } @@ -8984,8 +9039,11 @@ INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue) if (K_SlipdashActive() && K_Sliptiding(player)) // slight handling boost based on weight turnvalue = FixedMul(turnvalue, FRACUNIT + (10 - player->kartweight)*FRACUNIT/48); - if (player->invincibilitytimer || player->sneakertimer || player->bubbleboost || player->growshrinktimer > 0) - turnvalue = FixedMul(turnvalue, FixedDiv(5*FRACUNIT, 4*FRACUNIT)); + if (player->invincibilitytimer || player->sneakertimer || player->bubbleboost || + player->growshrinktimer > 0 || K_IsAltShrunk(player)) + { + turnvalue = FixedMul(turnvalue, FixedDiv(5 * FRACUNIT, 4 * FRACUNIT)); + } if (player->flamedash && player->flamestore) // Reduce turning { @@ -10769,39 +10827,24 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; - if (player->growshrinktimer < 0) + if (K_IsLegacyShrunk(player)) { - // If you're shrunk, then "grow" will just make you normal again. + // If you're shrunk, then "grow" will just make you normal again... K_RemoveGrowShrink(player); } else { + // ...in Legacy mode. + // Alt. Shrink's a powerup, so Grow overrides! + if (K_IsAltShrunk(player)) + { + player->growshrinktimer = 0; // Paranoia + } + K_PlayPowerGloatSound(player->mo); - player->mo->scalespeed = mapobjectscale/TICRATE; - player->mo->destscale = FixedMul(mapobjectscale, GROW_SCALE); + K_DoGrowShrink(player, false); - if (K_PlayerShrinkCheat(player) == true) - { - player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); - } - - player->growshrinktimer = (gametyperules & GTR_CLOSERPLAYERS ? 8 : 12) * TICRATE; - - if (player->invincibilitytimer > 0) - { - ; // invincibility has priority in P_RestoreMusic, no point in starting here - } - else if (P_IsLocalPlayer(player) == true) - { - S_ChangeMusicSpecial("kgrow"); - } - else //used to be "if (P_IsDisplayPlayer(player) == false)" - { - S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); - } - - P_RestoreMusic(player); S_StartSound(player->mo, sfx_kc5a); } @@ -10811,7 +10854,18 @@ void K_MoveKartPlayer(player_t *player, boolean onground) case KITEM_SHRINK: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { - K_DoShrink(player); + if (K_AltShrinkActive()) + { + K_DoGrowShrink(player, true); + K_AltShrinkIFrames(player); + + S_StartSound(player->mo, sfx_kc46); + } + else + { + K_DoShrink(player); + } + player->itemamount--; K_PlayPowerGloatSound(player->mo); K_BotResetItemConfirm(player, false); @@ -11023,12 +11077,15 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_RemoveMobj(player->shieldtracer); } + if (player->growshrinktimer == 0) + player->altshrinktimeshit = 0; + if (player->growshrinktimer <= 0) player->growcancel = -1; if (player->itemtype == KITEM_SPB || player->itemtype == KITEM_SHRINK - || player->growshrinktimer < 0) + || K_IsLegacyShrunk(player)) indirectitemcooldown = 20*TICRATE; if (player->hyudorotimer > 0) diff --git a/src/k_odds.c b/src/k_odds.c index a5f7d5747..17ea88146 100644 --- a/src/k_odds.c +++ b/src/k_odds.c @@ -33,6 +33,7 @@ #include "k_bot.h" #include "k_kart.h" #include "k_waypoint.h" +#include "k_altshrink.h" #include "k_cluster.hpp" #include "k_odds.h" @@ -120,6 +121,11 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = { 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Jawz x2 }; +static UINT8 altshrinkodds[MAXODDS] = + { 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0}; + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + //B C D E F G H I J K L M N O P Q + static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { //R S @@ -203,7 +209,7 @@ tic_t ItemBGone[NUMKARTRESULTS][2] = { 10, 0 }, // Ballhog { 20, 0 }, // Self-Propelled Bomb { 3, 0 }, // Grow - { 20, 0 }, // Shrink + { 5, 0 }, // Shrink { 0, 0 }, // Thunder Shield { 20, 0 }, // Hyudoro { 0, 0 }, // Pogo Spring @@ -394,6 +400,24 @@ UINT8 K_ItemResultToAmount(SINT8 getitem) } } +/** \brief Prevent overpowered rolls while under the effects of Alt. Shrink. + + \param item the item to check + + \return Don't double for this item? +*/ +static boolean K_DontDoubleMyItems(SINT8 item) +{ + return ((item == KITEM_BALLHOG) || (item == KRITEM_TENFOLDBANANA) + || (item == KRITEM_TRIPLEORBINAUT) + || (item == KRITEM_QUADORBINAUT) || (item == KRITEM_TRIPLESNEAKER) + || (item == KRITEM_DUALJAWZ) || (item == KITEM_SPB) + || (item == KITEM_INVINCIBILITY) || (item == KITEM_GROW) + || (item == KITEM_BUBBLESHIELD) || (item == KITEM_FLAMESHIELD) + || (item == KITEM_ROCKETSNEAKER) || (item == KITEM_SHRINK) + || (item == KITEM_HYUDORO)); +} + /** \brief Item Roulette for Kart \param player player @@ -403,10 +427,11 @@ UINT8 K_ItemResultToAmount(SINT8 getitem) */ static void K_KartGetItemResult(player_t *player, SINT8 getitem) { - if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) // Indirect items + if (getitem == KITEM_SPB || (getitem == KITEM_SHRINK && (!K_AltShrinkActive()))) // Indirect items + { indirectitemcooldown = 20*TICRATE; - - if (K_GetBGone(getitem, true) > 0) // Item cooldowns + } + else if (K_GetBGone(getitem, true) > 0) // Item cooldowns { K_SetBGoneToBase(getitem); } @@ -415,6 +440,12 @@ static void K_KartGetItemResult(player_t *player, SINT8 getitem) player->itemtype = K_ItemResultToType(getitem); UINT8 itemamount = K_ItemResultToAmount(getitem); + + if (K_IsAltShrunk(player) && (!K_DontDoubleMyItems(getitem))) + { + itemamount *= 2; + } + if (cv_kartdebugitem.value != KITEM_NONE && cv_kartdebugitem.value == player->itemtype && cv_kartdebugamount.value > 1) itemamount = cv_kartdebugamount.value; player->itemamount = itemamount; @@ -638,7 +669,11 @@ INT32 K_KartGetItemOdds( else if (gametyperules & GTR_RACEODDS) { I_Assert(pos < MAXODDS); // Ditto - newodds = K_KartItemOddsRace[item-1][pos]; + + if (item == KITEM_SHRINK && K_AltShrinkActive()) + newodds = altshrinkodds[pos]; // Alt. Shrink has unique odds, since it's a power item. + else + newodds = K_KartItemOddsRace[item-1][pos]; } else { @@ -820,7 +855,11 @@ INT32 K_KartGetItemOdds( case KITEM_SHRINK: cooldownOnStart = true; powerItem = true; - indirectItem = true; + + if (!K_AltShrinkActive()) + indirectItem = true; + + // "Why keep this for Alt. Shrink?" Bag. Ging. notNearEnd = true; if (pingame-1 <= pexiting) @@ -920,10 +959,17 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b UINT8 disttable[oddsdiv]; UINT8 distlen = 0; boolean oddsvalid[MAXODDS]; + boolean rivalodds = false; // Unused now, oops :V (void)bestbumper; + if ((player->bot && player->botvars.rival) || K_IsAltShrunk(player)) + { + // Rival bots and players using Alt. Shrink get crazier items. + rivalodds = true; + } + for (i = 0; i < MAXODDS; i++) { boolean available = false; @@ -943,7 +989,7 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b mashed, spbrush, player->bot, - (player->bot && player->botvars.rival), + rivalodds, K_IsPlayerLosing(player) ) > 0) { diff --git a/src/p_saveg.c b/src/p_saveg.c index 744cdc30b..92982a02b 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -45,6 +45,7 @@ #include "acs/interface.h" #include "g_party.h" #include "k_waypoint.h" +#include "k_altshrink.h" #include @@ -4099,6 +4100,7 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNCBOOLEAN(itempushing); SYNC(bumpsparkactive); SYNC(invintype); + SYNC(shrinktype); SYNC(antibumptime); for (i = 0; i < sizeof(votelevels)/sizeof(*votelevels); i++) diff --git a/src/p_setup.c b/src/p_setup.c index 8de8390bd..99376d695 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -109,6 +109,7 @@ #include "k_mapuser.h" #include "p_deepcopy.h" #include "k_specialstage.h" +#include "k_altshrink.h" #include "blan/b_soc.h" @@ -8072,6 +8073,7 @@ static void P_InitLevelSettings(boolean reloadinggamestate) bumpsparkactive = (UINT8)cv_kartbumpspark.value; invintype = (UINT8)cv_kartinvintype.value; + K_UpdateShrinkType(); antibumptime = (tic_t)cv_kartantibump.value * TICRATE; diff --git a/src/p_spec.c b/src/p_spec.c index c5e86ce28..e0a7f990f 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -44,6 +44,7 @@ // SRB2kart #include "k_kart.h" +#include "k_altshrink.h" #include "console.h" // CON_LogMessage #include "k_terrain.h" #include "acs/interface.h" @@ -9189,7 +9190,8 @@ boolean P_AllowFriction(mobj_t *mobj) if (mobj->player->invincibilitytimer || mobj->player->hyudorotimer || mobj->player->sneakertimer - || mobj->player->growshrinktimer > 0) + || mobj->player->growshrinktimer > 0 + || K_IsAltShrunk(mobj->player)) return false; return true; diff --git a/src/p_user.c b/src/p_user.c index 315556831..13b9d038a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -62,6 +62,7 @@ #include "k_color.h" #include "k_follower.h" #include "k_odds.h" +#include "k_altshrink.h" #include "g_party.h" #include "acs/interface.h" @@ -831,6 +832,8 @@ void P_RestoreMusic(player_t *player) { wantedmus = 1; bestlocaltimer = players[p].growshrinktimer; } \ else if (players[p].invincibilitytimer > bestlocaltimer) \ { wantedmus = 2; bestlocaltimer = players[p].invincibilitytimer; } \ + else if ((K_AltShrinkActive()) && (K_GetShrinkTime(&players[p]) > bestlocaltimer)) \ + { wantedmus = 3; bestlocaltimer = K_GetShrinkTime(&players[p]); } \ } setbests(localplayertable[0]); setbests(localplayertable[1]); @@ -848,6 +851,8 @@ void P_RestoreMusic(player_t *player) wantedmus = 1; else if (player->invincibilitytimer > 1) wantedmus = 2; + else if ((K_AltShrinkActive()) && (K_GetShrinkTime(player) > 1)) + wantedmus = 3; } } @@ -864,6 +869,12 @@ void P_RestoreMusic(player_t *player) S_ChangeMusicInternal("kinvnc", true); S_SetRestoreMusicFadeInCvar(&cv_invincmusicfade); } + // Item - Shrink (Alternative) + else if ((wantedmus == 3) && cv_altshrinkmusic.value == 1) + { + S_ChangeMusicInternal("kshrnk", true); + S_SetRestoreMusicFadeInCvar(&cv_altshrinkmusicfade); + } else { #if 0 From 9396d58448396f109ed839139d9d2ff0375a070d Mon Sep 17 00:00:00 2001 From: minenice55 Date: Tue, 4 Nov 2025 23:49:56 -0500 Subject: [PATCH 04/76] use the other name --- src/d_netcmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 330d27efc..083f667fc 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -601,7 +601,7 @@ consvar_t cv_kartlegacyspbdist = CVAR_INIT ("kartlegacyspbdist", "2216", CV_NETV #undef MAXODDSDIST // Items with alternative types -static CV_PossibleValue_t altitemtype_cons_t[] = {{KARTITEM_NORMAL, "Legacy"}, {KARTITEM_ALTERN, "Alternative"}, {0, NULL}}; +static CV_PossibleValue_t kartitemtype_cons_t[] = {{KARTITEM_NORMAL, "Legacy"}, {KARTITEM_ALTERN, "Alternative"}, {0, NULL}}; consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, kartitemtype_cons_t, KartInvinType_OnChange); consvar_t cv_kartshrinktype = CVAR_INIT ("kartshrinktype", "Legacy", CV_NETVAR|CV_CALL, kartitemtype_cons_t, KartShrinkType_OnChange); consvar_t cv_karteggmantype = CVAR_INIT ("karteggmantype", "Legacy", CV_NETVAR|CV_CALL, kartitemtype_cons_t, KartEggmanType_OnChange); From c5649e0b237eeaa58e7fb97e86466a83659b3b85 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 5 Nov 2025 07:16:39 -0500 Subject: [PATCH 05/76] Fixes and consolidation * Changed every instance of K_AltShrinkActive to its K_IsKartItemAlternate equivalent * Same as above for Invincibility; some functions might still rely on K_GetKartInvinType though * (Balance change) Alt. Invin. needs a slightly farther distance to start appearing, hopefully means less 5sec dud invinces * Brought back cv_landmine to prevent SIGSEGVs in the item toggles menu * Introduce NUMBASEKARTRESULTS since all KAITEM_X items are overrides assigned via K_GetAlternateVersion --- src/command.h | 5 +- src/d_netcmd.c | 1 + src/d_player.h | 5 +- src/deh_tables.c | 9 ++-- src/k_altshrink.c | 7 +-- src/k_altshrink.h | 14 +----- src/k_hud.c | 2 +- src/k_kart.c | 15 +++--- src/k_odds.c | 125 +++++++++++++++++++++++++++++----------------- src/m_menu.c | 2 +- src/p_user.c | 6 ++- 11 files changed, 111 insertions(+), 80 deletions(-) diff --git a/src/command.h b/src/command.h index 7eaefc5e7..5c0d81d8d 100644 --- a/src/command.h +++ b/src/command.h @@ -188,11 +188,8 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTGP_MASTER 4 // Not a speed setting, gives hard speed with maxed out bots #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots extern CV_PossibleValue_t kartspeed_cons_t[], gpdifficulty_cons_t[]; -// Invincibility types. FIXME: Make this a more generalized name eventually (please...) -#define KARTINVIN_LEGACY 0 -#define KARTINVIN_ALTERN 1 -// Alt item type (alias of kartinvin_x for now until refactor complete) +// Alternative item types. #define KARTITEM_NORMAL 0 #define KARTITEM_ALTERN 1 diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 083f667fc..92230cb74 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -168,6 +168,7 @@ static void KartItemPush_OnChange(void); static void KartAntiBump_OnChange(void); static void KartItemBreaker_OnChange(void); static void KartInvinType_OnChange(void); +static void KartEggmanType_OnChange(void); static void KartShrinkType_OnChange(void); static void KartBumpSpark_OnChange(void); diff --git a/src/d_player.h b/src/d_player.h index b7a4d7087..979f1165f 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -182,9 +182,12 @@ typedef enum KRITEM_TRIPLEORBINAUT, KRITEM_QUADORBINAUT, KRITEM_DUALJAWZ, + + NUMBASEKARTRESULTS, // alt items implemented as separate things - KAITEM_EGGMINE, // also landmine + KAITEM_EGGMINE = NUMBASEKARTRESULTS, // also landmine + KAITEM_ALTERNSHRINK, NUMKARTRESULTS } kartitems_t; diff --git a/src/deh_tables.c b/src/deh_tables.c index e650302a1..c6b7c6b29 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1524,6 +1524,9 @@ struct int_const_s const INT_CONST[] = { {"KRITEM_TRIPLEORBINAUT",KRITEM_TRIPLEORBINAUT}, {"KRITEM_QUADORBINAUT",KRITEM_QUADORBINAUT}, {"KRITEM_DUALJAWZ",KRITEM_DUALJAWZ}, + {"NUMBASEKARTRESULTS",NUMBASEKARTRESULTS}, + {"KAITEM_EGGMINE", KAITEM_EGGMINE}, + {"KAITEM_ALTERNSHRINK", KAITEM_ALTERNSHRINK}, {"NUMKARTRESULTS",NUMKARTRESULTS}, // kartshields_t @@ -1682,11 +1685,7 @@ struct int_const_s const INT_CONST[] = { {"FACE_WANTED", FACE_WANTED}, {"FACE_MINIMAP", FACE_MINIMAP}, {"NUMFACES", NUMFACES}, - - // invintype - {"KARTINVIN_LEGACY", KARTINVIN_LEGACY}, - {"KARTINVIN_ALTERN", KARTINVIN_ALTERN}, - // alt item type (alias of invintype for now until refactor complete) + // alt item type {"KARTITEM_NORMAL", KARTITEM_NORMAL}, {"KARTITEM_ALTERN", KARTITEM_ALTERN}, diff --git a/src/k_altshrink.c b/src/k_altshrink.c index ff94a98ab..202cc47d3 100644 --- a/src/k_altshrink.c +++ b/src/k_altshrink.c @@ -10,13 +10,14 @@ #include "command.h" #include "console.h" #include "d_player.h" +#include "k_kart.h" #include "k_altshrink.h" #include "k_cluster.hpp" #include "m_fixed.h" #include "p_local.h" -UINT8 shrinktype; +UINT8 shrinktype; // How Shrink functions. 0 for Legacy/Vanilla, 1 for Alternative. void K_UpdateShrinkType(void) { @@ -35,10 +36,10 @@ void K_UpdateShrinkType(void) \return void */ -boolean K_IsShrunkMode(player_t *player, boolean legacy) +boolean K_IsShrunkMode(const player_t *player, boolean legacy) { boolean shrunk = K_IsShrunk(player); - boolean legacytype = (shrinktype == KARTINVIN_LEGACY); + boolean legacytype = (!K_IsKartItemAlternate(KITEM_SHRINK)); if (legacy) { diff --git a/src/k_altshrink.h b/src/k_altshrink.h index e535f94f1..a35640606 100644 --- a/src/k_altshrink.h +++ b/src/k_altshrink.h @@ -27,23 +27,13 @@ extern UINT8 shrinktype; #define SHRINKACCELBOOST CV_Get(&cv_kartstacking_altshrink_accelboost) #define SHRINKSTACKABLE CV_Get(&cv_kartstacking_altshrink_stackable) -/** \brief Returns true or false if Alt. Shrink is active. - - \return Are we using the alternative Shrink type? -*/ -FUNCINLINE static ATTRINLINE boolean K_AltShrinkActive(void) -{ - return (shrinktype == KARTINVIN_ALTERN); -} - - /** \brief Returns true or false if the player is shrunk. \param player (player_t) Player to test shrunken status for \return Is this player shrunk? */ -FUNCINLINE static ATTRINLINE boolean K_IsShrunk(player_t *player) +FUNCINLINE static ATTRINLINE boolean K_IsShrunk(const player_t *player) { return (player->growshrinktimer < 0); } @@ -58,7 +48,7 @@ FUNCINLINE static ATTRINLINE fixed_t K_AccomodateShrinkScaling(fixed_t x) return FixedDiv(x, SHRINK_SCALE); } -boolean K_IsShrunkMode(player_t *player, boolean legacy); +boolean K_IsShrunkMode(const player_t *player, boolean legacy); #define K_IsLegacyShrunk(player) (K_IsShrunkMode(player, true)) #define K_IsAltShrunk(player) (K_IsShrunkMode(player, false)) diff --git a/src/k_hud.c b/src/k_hud.c index 795639634..30d5252e0 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5833,7 +5833,7 @@ static void K_drawDistributionDebugger(void) } else { - for (item = 1; item < NUMKARTRESULTS; item++) + for (item = 1; item < NUMBASEKARTRESULTS; item++) { INT32 itemodds; INT32 amount; diff --git a/src/k_kart.c b/src/k_kart.c index 6dcb0fb83..04f3316af 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -252,7 +252,7 @@ void K_RegisterKartStuff(void) // Nu-ITEMS CV_RegisterVar(&cv_superring); - // CV_RegisterVar(&cv_landmine); + CV_RegisterVar(&cv_landmine); // FIXME: Kill it. CV_RegisterVar(&cv_bubbleshield); CV_RegisterVar(&cv_flameshield); @@ -5538,7 +5538,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (type == 0) { UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; + INT32 spawnchance[NUMBASEKARTRESULTS]; INT32 totalspawnchance = 0; INT32 i; @@ -5546,7 +5546,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 useodds = amount; - for (i = 1; i < NUMKARTRESULTS; i++) + for (i = 1; i < NUMBASEKARTRESULTS; i++) { spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( useodds, i, @@ -5561,7 +5561,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (totalspawnchance > 0) { totalspawnchance = P_RandomKey(totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + for (i = 0; i < NUMBASEKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); // TODO: this is bad! // K_KartGetItemResult requires a player @@ -9126,7 +9126,7 @@ static void K_SpawnDriftEFX(player_t *player,SINT8 level) // Sliptide conditions for Alternative Invincibility. static boolean K_AltInvinSliptideCondition(player_t *player) { - if (K_GetKartInvinType() != KARTINVIN_ALTERN) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { // Not in Alternative, hit the bricks! return false; @@ -10854,7 +10854,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) case KITEM_SHRINK: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { - if (K_AltShrinkActive()) + if (K_IsKartItemAlternate(KITEM_SHRINK)) { K_DoGrowShrink(player, true); K_AltShrinkIFrames(player); @@ -11522,6 +11522,9 @@ boolean K_IsKartItemAlternate(UINT8 itemtype) case KITEM_EGGMAN: return eggmantype == KARTITEM_ALTERN; break; + case KITEM_SHRINK: + return shrinktype == KARTITEM_ALTERN; + break; default: return false; diff --git a/src/k_odds.c b/src/k_odds.c index 63815661e..8afce132a 100644 --- a/src/k_odds.c +++ b/src/k_odds.c @@ -57,7 +57,7 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_pogospring, &cv_kitchensink, &cv_superring, - // &cv_landmine, + &cv_landmine, // FIXME: Kill it. &cv_bubbleshield, &cv_flameshield, &cv_dualsneaker, @@ -119,14 +119,10 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = { 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x3 { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x4 { 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Jawz x2 - { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Egg Mine + { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Egg Mine + { 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0} // Alt. Shrink }; -static UINT8 altshrinkodds[MAXODDS] = - { 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0}; - //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - //B C D E F G H I J K L M N O P Q - static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { //R S @@ -156,8 +152,9 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { 1, 1 }, // Banana x10 { 2, 0 }, // Orbinaut x3 { 1, 1 }, // Orbinaut x4 - { 5, 1 }, // Jawz x2 - { 2, 0 } // Egg Mine + { 5, 1 }, // Jawz x2 + { 2, 0 }, // Egg Mine + { 1, 0 } // Alt. Shrink }; /* @@ -190,8 +187,9 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 0, 0, 0 }, // Banana x10 { 0, 1, 1, 0 }, // Orbinaut x3 { 0, 0, 1, 1 }, // Orbinaut x4 - { 0, 0, 1, 1 }, // Jawz x2 - { 0, 0, 0, 0 } // Egg Mine + { 0, 0, 1, 1 }, // Jawz x2 + { 0, 0, 0, 0 }, // Egg Mine + { 0, 0, 0, 0 } // Alt. Shrink }; */ @@ -212,7 +210,7 @@ tic_t ItemBGone[NUMKARTRESULTS][2] = { 10, 0 }, // Ballhog { 20, 0 }, // Self-Propelled Bomb { 3, 0 }, // Grow - { 5, 0 }, // Shrink + { 20, 0 }, // Shrink { 0, 0 }, // Thunder Shield { 20, 0 }, // Hyudoro { 0, 0 }, // Pogo Spring @@ -228,12 +226,36 @@ tic_t ItemBGone[NUMKARTRESULTS][2] = { 10, 0 }, // Orbinaut x3 { 20, 0 }, // Orbinaut x4 { 10, 0 }, // Jawz x2 - { 0, 0 } // Egg Mine + { 0, 0 }, // Egg Mine + { 5, 0 } // Alt. Shrink }; // TODO: Vectorize all item tables and shove them into gamemode-uniqe pools (k_oddstable.cpp?). // Basically necessary for any hopes of easy SOC support in the near future. +static SINT8 K_GetAlternateVersion(SINT8 item) +{ + if (K_IsKartItemAlternate(item)) + { + switch(item) + { + /*case KITEM_INVINCIBILITY: + return KITEM_INVINCIBILITY;*/ + + case KITEM_EGGMAN: + return KAITEM_EGGMINE; + + case KITEM_SHRINK: + return KAITEM_ALTERNSHRINK; + + default: + break; + } + } + + return item; // Return ourselves; we don't have an alternative. +} + /** \brief Sets the cooldown timer for an item. When active, the item is removed * from all players' item pools for some time. @@ -431,13 +453,15 @@ static boolean K_DontDoubleMyItems(SINT8 item) */ static void K_KartGetItemResult(player_t *player, SINT8 getitem) { - if (getitem == KITEM_SPB || (getitem == KITEM_SHRINK && (!K_AltShrinkActive()))) // Indirect items + SINT8 trueitem = K_GetAlternateVersion(getitem); + + if (trueitem == KITEM_SPB || (trueitem == KITEM_SHRINK)) // Indirect items { indirectitemcooldown = 20*TICRATE; } - else if (K_GetBGone(getitem, true) > 0) // Item cooldowns + else if (K_GetBGone(trueitem, true) > 0) // Item cooldowns { - K_SetBGoneToBase(getitem); + K_SetBGoneToBase(trueitem); } K_BotResetItemConfirm(player, true); @@ -535,6 +559,8 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) // Odds value for Alt. Invin. to force itself on trailing players. #define INVFORCEODDS (MAXINVODDS / 2) +#define FRAC_3h (3 * FRACUNIT / 4) + static INT32 K_KartGetInvincibilityOdds(UINT32 dist) { UINT32 invindist = INVINDIST/2; @@ -553,10 +579,10 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist) } else { - if (fac <= FRACHALF) + if (fac <= FRAC_3h) { // Invincibility is practically useless at lower distances. - // At below half, remove it from the item pool. + // At below 75%, remove it from the item pool. return 0; } // Basic linear climb to "reasonable" odds. @@ -566,6 +592,8 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist) return min(MAXINVODDS, finodds); } +#undef FRAC_3h + // Assigns general cooldowns to shield items. void K_KartHandleShieldCooldown(SINT8 item) { @@ -639,7 +667,11 @@ INT32 K_KartGetItemOdds( INT32 shieldtype = KSHIELD_NONE; I_Assert(item > KITEM_NONE); // too many off by one scenarioes. - I_Assert(KartItemCVars[NUMKARTRESULTS-2] != NULL); // Make sure this exists + I_Assert(KartItemCVars[NUMBASEKARTRESULTS-2] != NULL); // Make sure this exists + + // Alt. item definitions are literally only for odds tables and cooldowns. + // Don't let the actual item listing step out of that boundary. + item = CLAMP(item, 0, NUMBASEKARTRESULTS); if (!KartItemCVars[item-1]->value && !modeattacking) return 0; @@ -664,20 +696,19 @@ INT32 K_KartGetItemOdds( INT32 oddsmul = BASEODDSMUL; + // Item type used for actual odds retrieval and cooldown assignments. + SINT8 useitem = K_GetAlternateVersion(item); + if (gametyperules & GTR_BATTLEODDS) { I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table - newodds = K_KartItemOddsBattle[item-1][pos]; + newodds = K_KartItemOddsBattle[useitem-1][pos]; oddsmul = BATTLEODDSMUL; } else if (gametyperules & GTR_RACEODDS) { I_Assert(pos < MAXODDS); // Ditto - - if (item == KITEM_SHRINK && K_AltShrinkActive()) - newodds = altshrinkodds[pos]; // Alt. Shrink has unique odds, since it's a power item. - else - newodds = K_KartItemOddsRace[item-1][pos]; + newodds = K_KartItemOddsRace[useitem-1][pos]; } else { @@ -760,14 +791,6 @@ INT32 K_KartGetItemOdds( if (K_IsKartItemAlternate(KITEM_EGGMAN)) { notForBottom = true; - if (gametyperules & GTR_BATTLEODDS) - { - newodds = K_KartItemOddsBattle[KAITEM_EGGMINE-1][pos]; - } - else if (gametyperules & GTR_RACEODDS) - { - newodds = K_KartItemOddsRace[KAITEM_EGGMINE-1][pos]; - } } else { @@ -789,7 +812,8 @@ INT32 K_KartGetItemOdds( } break; - case KITEM_LANDMINE: // au revoir + case KITEM_LANDMINE: + // au revoir newodds = 0; break; case KITEM_ROCKETSNEAKER: @@ -877,14 +901,25 @@ INT32 K_KartGetItemOdds( cooldownOnStart = true; powerItem = true; - if (!K_AltShrinkActive()) - indirectItem = true; - // "Why keep this for Alt. Shrink?" Bag. Ging. + // ESPECIALLY with double items. notNearEnd = true; - if (pingame-1 <= pexiting) - newodds = 0; + if (!K_IsKartItemAlternate(KITEM_SHRINK)) + { + indirectItem = true; + + if (pingame-1 <= pexiting) + newodds = 0; + } + else + { + if (rival) + { + // Rival bot or already shrunk. DON'T roll another. + newodds = 0; + } + } break; case KITEM_THUNDERSHIELD: cooldownOnStart = true; @@ -912,7 +947,7 @@ INT32 K_KartGetItemOdds( return newodds; } - if (K_GetBGone(item, false) > 0) + if (K_GetBGone(useitem, false) > 0) { // (Replaces hyubgone) This item is on cooldown; don't let it get rolled. newodds = 0; @@ -1001,7 +1036,7 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b break; } - for (j = 1; j < NUMKARTRESULTS; j++) + for (j = 1; j < NUMBASEKARTRESULTS; j++) { if (K_KartGetItemOdds( i, j, @@ -1388,7 +1423,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) UINT8 roulettestop; UINT32 pdis = 0; UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; + INT32 spawnchance[NUMBASEKARTRESULTS]; INT64 totalspawnchance = 0; // 75-scale numbers are going to get BIG. This is for paranoia's sake. UINT8 bestbumper = 0; fixed_t mashed = 0; @@ -1582,13 +1617,13 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // NOW that we're done with all of those specialized cases, we can move onto the REAL item roulette tables. // Initializes existing spawnchance values - for (i = 0; i < NUMKARTRESULTS; i++) + for (i = 0; i < NUMBASEKARTRESULTS; i++) spawnchance[i] = 0; // Split into another function for a debug function below useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); - for (i = 1; i < NUMKARTRESULTS; i++) + for (i = 1; i < NUMBASEKARTRESULTS; i++) { spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( @@ -1607,7 +1642,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) if (totalspawnchance > 0) { totalspawnchance = P_RandomKey(totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + for (i = 0; i < NUMBASEKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); K_KartGetItemResult(player, i); } diff --git a/src/m_menu.c b/src/m_menu.c index 1413bd751..baa98c7df 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -8558,7 +8558,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) { INT32 v = cv_sneaker.value; S_StartSound(NULL, sfx_s1b4); - for (i = 0; i < NUMKARTRESULTS-1; i++) + for (i = 0; i < NUMBASEKARTRESULTS-1; i++) { if (KartItemCVars[i]->value == v) CV_AddValue(KartItemCVars[i], 1); diff --git a/src/p_user.c b/src/p_user.c index 83ef01156..fb45513a9 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -832,7 +832,8 @@ void P_RestoreMusic(player_t *player) { wantedmus = 1; bestlocaltimer = players[p].growshrinktimer; } \ else if (players[p].invincibilitytimer > bestlocaltimer) \ { wantedmus = 2; bestlocaltimer = players[p].invincibilitytimer; } \ - else if ((K_AltShrinkActive()) && (K_GetShrinkTime(&players[p]) > bestlocaltimer)) \ + else if ((K_IsKartItemAlternate(KITEM_SHRINK)) && \ + (K_GetShrinkTime(&players[p]) > bestlocaltimer)) \ { wantedmus = 3; bestlocaltimer = K_GetShrinkTime(&players[p]); } \ } setbests(localplayertable[0]); @@ -851,7 +852,8 @@ void P_RestoreMusic(player_t *player) wantedmus = 1; else if (player->invincibilitytimer > 1) wantedmus = 2; - else if ((K_AltShrinkActive()) && (K_GetShrinkTime(player) > 1)) + else if ((K_IsKartItemAlternate(KITEM_SHRINK)) && + (K_GetShrinkTime(player) > 1)) wantedmus = 3; } From 673054196f51f9572e1fb4f353992db3b6669e33 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 5 Nov 2025 08:54:57 -0500 Subject: [PATCH 06/76] Actually slow down players using Alt. Shrink if they get bump-flipped Should hopefully make it less OP in regards to its catchup --- src/k_collide.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/k_collide.c b/src/k_collide.c index 83f34bab9..3a2842817 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -868,6 +868,9 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) { K_AltShrinkPityIncrease(t2->player); t2->player->altshrinktimeshit++; + + P_InstaThrust(t2, K_MomentumAngle(t2), K_Momentum2D(t2) / 3); + K_PlayPainSound(t2, NULL); } } @@ -908,6 +911,9 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) { K_AltShrinkPityIncrease(t1->player); t1->player->altshrinktimeshit++; + + P_InstaThrust(t1, K_MomentumAngle(t1), K_Momentum2D(t1) / 3); + K_PlayPainSound(t1, NULL); } } From b562bd057582c1a583c58946a2c03eb40e5b20ae Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 5 Nov 2025 11:27:26 -0500 Subject: [PATCH 07/76] Kartitem_t Struct implementation of an item's odds and relevant functions: * Stored in an unordered map for non-static item allocation; you can theoretically have millions of items in the game now * All item odds are dynamic C++ vectors that are loaded in a per-gametype basis. * Extremely boilerplate; any and every system in place can be used by any and every item --- src/Sourcefile | 1 + src/console.c | 1 + src/console.h | 1 + src/d_main.cpp | 5 ++ src/k_kartitem.cpp | 130 +++++++++++++++++++++++++++++++++++++++++++++ src/k_kartitem.hpp | 73 +++++++++++++++++++++++++ src/typedef.h | 3 ++ 7 files changed, 214 insertions(+) create mode 100644 src/k_kartitem.cpp create mode 100644 src/k_kartitem.hpp diff --git a/src/Sourcefile b/src/Sourcefile index 87fa585b3..8a94d2189 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -133,6 +133,7 @@ k_bot.cpp k_botitem.cpp k_botsearch.cpp k_cluster.cpp +k_kartitem.cpp k_odds.c k_altshrink.c k_grandprix.c diff --git a/src/console.c b/src/console.c index 9c30ee78c..f39804822 100644 --- a/src/console.c +++ b/src/console.c @@ -1624,6 +1624,7 @@ static const char *CON_LoadingStrings[LOADED_ALLDONE+1] = "Init audio subsystem...", //LOADED_SINITSFXCHANNELS "Cache HUD...", //LOADED_STINIT "Test fixed-point arithmetic...", //LOADED_MATHINIT + "Init kart items...", //LOADED_KITEMSINIT "Init ACSVM...", //LOADED_ACSINIT "Check game status...", //LOADED_DCHECKNETGAME "Now starting..." diff --git a/src/console.h b/src/console.h index bcc503a21..c68be9f74 100644 --- a/src/console.h +++ b/src/console.h @@ -53,6 +53,7 @@ typedef enum LOADED_SINITSFXCHANNELS, LOADED_STINIT, LOADED_MATHINIT, + LOADED_KITEMSINIT, LOADED_ACSINIT, LOADED_DCHECKNETGAME, LOADED_ALLDONE = LOADED_DCHECKNETGAME, diff --git a/src/d_main.cpp b/src/d_main.cpp index 3218325af..6d9965459 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -82,6 +82,7 @@ #include "m_random.h" // P_ClearRandom #include "acs/interface.h" #include "k_specialstage.h" +#include "k_kartitem.hpp" #define __STDC_FORMAT_MACROS #include @@ -1840,6 +1841,10 @@ void D_SRB2Main(void) CON_SetLoadingProgress(LOADED_MATHINIT); + CONS_Printf("K_InitializeKartItems(): Init necessary kart items.\n"); + K_InitializeKartItems(); + CON_SetLoadingProgress(LOADED_KITEMSINIT); + CONS_Printf("ACS_Init(): Init Action Code Script VM.\n"); ACS_Init(); CON_SetLoadingProgress(LOADED_ACSINIT); diff --git a/src/k_kartitem.cpp b/src/k_kartitem.cpp new file mode 100644 index 000000000..ffc6fdec7 --- /dev/null +++ b/src/k_kartitem.cpp @@ -0,0 +1,130 @@ +// BLANKART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2025 by Kart Krew. +// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 Blankart Team. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_kartitem.cpp +/// \brief Kart item structure and systems. + +#include +#include // kartitemtable + +#include "command.h" // Need for player_t +#include "doomdef.h" +#include "d_player.h" // Need for player_t +#include "k_kartitem.hpp" + +std::unordered_map kartitemtable; + +extern "C" +{ +kartitem_t *K_AddItemOdds(UINT32 id, kartitemflags_t flags) +{ + kartitem_t *newitem = new kartitem_t(); + + newitem->_id = id; + newitem->_flags = flags; + + newitem->alternate = false; + newitem->forceme = 0; + + // Initialize race and battle odds. + newitem->odds[GT_RACE][KARTITEM_NORMAL] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + newitem->odds[GT_RACE][KARTITEM_ALTERN] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + newitem->odds[GT_BATTLE][KARTITEM_NORMAL] = { 0, 0 }; + newitem->odds[GT_BATTLE][KARTITEM_ALTERN] = { 0, 0 }; + + kartitemtable.insert({ id, newitem }); + + return newitem; +} + +#define AssertItemExists(kitem, id) { \ + if (auto search = kartitemtable.find(id); search != kartitemtable.end()) \ + { \ + const kartitem_t *tempitem = search->second; \ + I_Assert(tempitem != nullptr); \ + I_Assert(tempitem->_id == kitem->_id); \ + I_Assert(tempitem->_id == id); \ + CONS_Printf("Item no. %d most certainly exists.\n", id); \ + } \ +} + +void K_InitializeKartItems(void) +{ + kartitem_t *kitem; + + // FIXME: Handle all this by SOC at some point! + + kitem = K_AddItemOdds(KITEM_SNEAKER, static_cast(IF_NONE)); + + if (kitem) + { + CONS_Printf("Allocation OK; setting up KITEM_SNEAKER...\n"); + AssertItemExists(kitem, KITEM_SNEAKER); + + // Sneaker + kitem->odds[GT_RACE][KARTITEM_NORMAL] = + { 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + //B C D E F G H I J K L M N O P Q + + kitem->odds[GT_BATTLE][KARTITEM_NORMAL] = { 2, 1 }; + //R S + + //CONS_Printf("%d\n", kitem->odds[GT_RACE][KARTITEM_NORMAL][5]); // Should print 24 + } + else + { + // Oh no. + I_Error("Failed to allocate required item KITEM_SNEAKER!"); + } + + kitem = K_AddItemOdds(KITEM_ROCKETSNEAKER, static_cast(IF_NONE)); + + if (kitem) + { + CONS_Printf("Allocation OK; setting up KITEM_ROCKETSNEAKER...\n"); + AssertItemExists(kitem, KITEM_ROCKETSNEAKER); + + // Rocket Sneaker + kitem->odds[GT_RACE][KARTITEM_NORMAL] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37}; + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + //B C D E F G H I J K L M N O P Q + + //CONS_Printf("%d\n", kitem->odds[GT_RACE][KARTITEM_NORMAL][14]); // Should print 37 + } + else + { + // Oh no. + I_Error("Failed to allocate required item KITEM_ROCKETSNEAKER!"); + } + + // Paranoia ID test for items allocated with this function. + CONS_Printf("ID test\n"); + + UINT32 i = 0; + for (auto& iterator : kartitemtable) + { + const kartitem_t * _item = iterator.second; + + if (_item) + { + CONS_Printf("Item no. %d exists.\n", _item->_id); + } + else + { + I_Error("An allocated item is NULL or invalid!"); + } + + i++; + } +} +} \ No newline at end of file diff --git a/src/k_kartitem.hpp b/src/k_kartitem.hpp new file mode 100644 index 000000000..628174331 --- /dev/null +++ b/src/k_kartitem.hpp @@ -0,0 +1,73 @@ +// BLANKART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2025 by Kart Krew. +// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 Blankart Team. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_kartitem.hpp +/// \brief Kart item structure and systems. + + +#ifndef __K_KARTITEM__ +#define __K_KARTITEM__ + +#include +#include // kartitemtable + +#include "command.h" // Need for player_t +#include "doomstat.h" +#include "doomdef.h" +#include "d_player.h" // Need for player_t + + +typedef enum kartitemflags_e +{ + IF_NONE = 0, + IF_POWERITEM = (1 << 0), // This item is a "power item". This activates "frantic item" toggle related functionality. + IF_STARTCOOLDOWN = (1 << 1), // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + IF_INDIRECTITEM = (1 << 2), // This item affects others indirectly. A 20 second cooldown is applied to all other items with this flag when an indirect item is rolled. + IF_NOTNEAREND = (1 << 3), // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) + IF_NOTFORBOTTOM = (1 << 4), // After the first 30 seconds, this item stops appearing for losing players. (Usually items that feel less effective at these positions) + IF_RUNNERAUGMENT = (1 << 5) // This item's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) +} kartitemflags_t; + +typedef enum kartitemfuncs_e +{ + IFUNC_UNIQUEODDS, // This item isn't bound to the odds table, and has unique odds based on certain conditions. + IFUNC_ALTERNODDS, // Same as IFUNC_UNIQUEODDS, but strictly for the alternative version of an item. + IFUNC_SETFORCEME, // This function, should it exist, controls the "forceme" variable for the given item. + IFUNC_CALLONROLL, // When this item is rolled in a roulette, this function is called. + IFUNC_USEITEM, // Function to call on an item's use. + NUMKARTITEMFUNCS +} kartitemfuncs_t; + +struct kartitem_t +{ + UINT32 _id; // The item's ID in game memory. + + kartitemflags_t _flags; // Various flags that control odds behavior. + + boolean alternate; // Is this an alternative version of the same item? + + UINT8 forceme; // This item is being forced into a player's item slot for one reason or another. Higher value = higher priority. + + void *functions[NUMKARTITEMFUNCS]; // Item functions. + + // Per-gametype item odds, for both standard and alternative modes. + std::vector odds[NUMGAMETYPES][2]; +}; + +// Dynamic table containing all items. +extern std::unordered_map kartitemtable; + +extern "C" +{ + kartitem_t *K_AddItemOdds(UINT32 id, kartitemflags_t flags); + void K_InitializeKartItems(void); +} + +#endif // __K_KARTITEM__ \ No newline at end of file diff --git a/src/typedef.h b/src/typedef.h index 9db777746..dc9989b6e 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -208,6 +208,9 @@ TYPEDEF (waypoint_t); TYPEDEF (mapUserProperty_t); TYPEDEF (mapUserProperties_t); +// k_kartitem.hpp +TYPEDEF (kartitem_t); + // lua_hudlib_drawlist.h typedef struct huddrawlist_s *huddrawlist_h; From ca762e26de29bac34dfc72ab21a4f53a07d31302 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Wed, 5 Nov 2025 17:33:00 +0100 Subject: [PATCH 08/76] Revert "Kartitem_t" This reverts commit b562bd057582c1a583c58946a2c03eb40e5b20ae. --- src/Sourcefile | 1 - src/console.c | 1 - src/console.h | 1 - src/d_main.cpp | 5 -- src/k_kartitem.cpp | 130 --------------------------------------------- src/k_kartitem.hpp | 73 ------------------------- src/typedef.h | 3 -- 7 files changed, 214 deletions(-) delete mode 100644 src/k_kartitem.cpp delete mode 100644 src/k_kartitem.hpp diff --git a/src/Sourcefile b/src/Sourcefile index 8a94d2189..87fa585b3 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -133,7 +133,6 @@ k_bot.cpp k_botitem.cpp k_botsearch.cpp k_cluster.cpp -k_kartitem.cpp k_odds.c k_altshrink.c k_grandprix.c diff --git a/src/console.c b/src/console.c index f39804822..9c30ee78c 100644 --- a/src/console.c +++ b/src/console.c @@ -1624,7 +1624,6 @@ static const char *CON_LoadingStrings[LOADED_ALLDONE+1] = "Init audio subsystem...", //LOADED_SINITSFXCHANNELS "Cache HUD...", //LOADED_STINIT "Test fixed-point arithmetic...", //LOADED_MATHINIT - "Init kart items...", //LOADED_KITEMSINIT "Init ACSVM...", //LOADED_ACSINIT "Check game status...", //LOADED_DCHECKNETGAME "Now starting..." diff --git a/src/console.h b/src/console.h index c68be9f74..bcc503a21 100644 --- a/src/console.h +++ b/src/console.h @@ -53,7 +53,6 @@ typedef enum LOADED_SINITSFXCHANNELS, LOADED_STINIT, LOADED_MATHINIT, - LOADED_KITEMSINIT, LOADED_ACSINIT, LOADED_DCHECKNETGAME, LOADED_ALLDONE = LOADED_DCHECKNETGAME, diff --git a/src/d_main.cpp b/src/d_main.cpp index 6d9965459..3218325af 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -82,7 +82,6 @@ #include "m_random.h" // P_ClearRandom #include "acs/interface.h" #include "k_specialstage.h" -#include "k_kartitem.hpp" #define __STDC_FORMAT_MACROS #include @@ -1841,10 +1840,6 @@ void D_SRB2Main(void) CON_SetLoadingProgress(LOADED_MATHINIT); - CONS_Printf("K_InitializeKartItems(): Init necessary kart items.\n"); - K_InitializeKartItems(); - CON_SetLoadingProgress(LOADED_KITEMSINIT); - CONS_Printf("ACS_Init(): Init Action Code Script VM.\n"); ACS_Init(); CON_SetLoadingProgress(LOADED_ACSINIT); diff --git a/src/k_kartitem.cpp b/src/k_kartitem.cpp deleted file mode 100644 index ffc6fdec7..000000000 --- a/src/k_kartitem.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// BLANKART -//----------------------------------------------------------------------------- -// Copyright (C) 2018-2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". -// Copyright (C) 2025 Blankart Team. -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- -/// \file k_kartitem.cpp -/// \brief Kart item structure and systems. - -#include -#include // kartitemtable - -#include "command.h" // Need for player_t -#include "doomdef.h" -#include "d_player.h" // Need for player_t -#include "k_kartitem.hpp" - -std::unordered_map kartitemtable; - -extern "C" -{ -kartitem_t *K_AddItemOdds(UINT32 id, kartitemflags_t flags) -{ - kartitem_t *newitem = new kartitem_t(); - - newitem->_id = id; - newitem->_flags = flags; - - newitem->alternate = false; - newitem->forceme = 0; - - // Initialize race and battle odds. - newitem->odds[GT_RACE][KARTITEM_NORMAL] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - newitem->odds[GT_RACE][KARTITEM_ALTERN] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - newitem->odds[GT_BATTLE][KARTITEM_NORMAL] = { 0, 0 }; - newitem->odds[GT_BATTLE][KARTITEM_ALTERN] = { 0, 0 }; - - kartitemtable.insert({ id, newitem }); - - return newitem; -} - -#define AssertItemExists(kitem, id) { \ - if (auto search = kartitemtable.find(id); search != kartitemtable.end()) \ - { \ - const kartitem_t *tempitem = search->second; \ - I_Assert(tempitem != nullptr); \ - I_Assert(tempitem->_id == kitem->_id); \ - I_Assert(tempitem->_id == id); \ - CONS_Printf("Item no. %d most certainly exists.\n", id); \ - } \ -} - -void K_InitializeKartItems(void) -{ - kartitem_t *kitem; - - // FIXME: Handle all this by SOC at some point! - - kitem = K_AddItemOdds(KITEM_SNEAKER, static_cast(IF_NONE)); - - if (kitem) - { - CONS_Printf("Allocation OK; setting up KITEM_SNEAKER...\n"); - AssertItemExists(kitem, KITEM_SNEAKER); - - // Sneaker - kitem->odds[GT_RACE][KARTITEM_NORMAL] = - { 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - //B C D E F G H I J K L M N O P Q - - kitem->odds[GT_BATTLE][KARTITEM_NORMAL] = { 2, 1 }; - //R S - - //CONS_Printf("%d\n", kitem->odds[GT_RACE][KARTITEM_NORMAL][5]); // Should print 24 - } - else - { - // Oh no. - I_Error("Failed to allocate required item KITEM_SNEAKER!"); - } - - kitem = K_AddItemOdds(KITEM_ROCKETSNEAKER, static_cast(IF_NONE)); - - if (kitem) - { - CONS_Printf("Allocation OK; setting up KITEM_ROCKETSNEAKER...\n"); - AssertItemExists(kitem, KITEM_ROCKETSNEAKER); - - // Rocket Sneaker - kitem->odds[GT_RACE][KARTITEM_NORMAL] = - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37}; - //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - //B C D E F G H I J K L M N O P Q - - //CONS_Printf("%d\n", kitem->odds[GT_RACE][KARTITEM_NORMAL][14]); // Should print 37 - } - else - { - // Oh no. - I_Error("Failed to allocate required item KITEM_ROCKETSNEAKER!"); - } - - // Paranoia ID test for items allocated with this function. - CONS_Printf("ID test\n"); - - UINT32 i = 0; - for (auto& iterator : kartitemtable) - { - const kartitem_t * _item = iterator.second; - - if (_item) - { - CONS_Printf("Item no. %d exists.\n", _item->_id); - } - else - { - I_Error("An allocated item is NULL or invalid!"); - } - - i++; - } -} -} \ No newline at end of file diff --git a/src/k_kartitem.hpp b/src/k_kartitem.hpp deleted file mode 100644 index 628174331..000000000 --- a/src/k_kartitem.hpp +++ /dev/null @@ -1,73 +0,0 @@ -// BLANKART -//----------------------------------------------------------------------------- -// Copyright (C) 2018-2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". -// Copyright (C) 2025 Blankart Team. -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- -/// \file k_kartitem.hpp -/// \brief Kart item structure and systems. - - -#ifndef __K_KARTITEM__ -#define __K_KARTITEM__ - -#include -#include // kartitemtable - -#include "command.h" // Need for player_t -#include "doomstat.h" -#include "doomdef.h" -#include "d_player.h" // Need for player_t - - -typedef enum kartitemflags_e -{ - IF_NONE = 0, - IF_POWERITEM = (1 << 0), // This item is a "power item". This activates "frantic item" toggle related functionality. - IF_STARTCOOLDOWN = (1 << 1), // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) - IF_INDIRECTITEM = (1 << 2), // This item affects others indirectly. A 20 second cooldown is applied to all other items with this flag when an indirect item is rolled. - IF_NOTNEAREND = (1 << 3), // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) - IF_NOTFORBOTTOM = (1 << 4), // After the first 30 seconds, this item stops appearing for losing players. (Usually items that feel less effective at these positions) - IF_RUNNERAUGMENT = (1 << 5) // This item's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) -} kartitemflags_t; - -typedef enum kartitemfuncs_e -{ - IFUNC_UNIQUEODDS, // This item isn't bound to the odds table, and has unique odds based on certain conditions. - IFUNC_ALTERNODDS, // Same as IFUNC_UNIQUEODDS, but strictly for the alternative version of an item. - IFUNC_SETFORCEME, // This function, should it exist, controls the "forceme" variable for the given item. - IFUNC_CALLONROLL, // When this item is rolled in a roulette, this function is called. - IFUNC_USEITEM, // Function to call on an item's use. - NUMKARTITEMFUNCS -} kartitemfuncs_t; - -struct kartitem_t -{ - UINT32 _id; // The item's ID in game memory. - - kartitemflags_t _flags; // Various flags that control odds behavior. - - boolean alternate; // Is this an alternative version of the same item? - - UINT8 forceme; // This item is being forced into a player's item slot for one reason or another. Higher value = higher priority. - - void *functions[NUMKARTITEMFUNCS]; // Item functions. - - // Per-gametype item odds, for both standard and alternative modes. - std::vector odds[NUMGAMETYPES][2]; -}; - -// Dynamic table containing all items. -extern std::unordered_map kartitemtable; - -extern "C" -{ - kartitem_t *K_AddItemOdds(UINT32 id, kartitemflags_t flags); - void K_InitializeKartItems(void); -} - -#endif // __K_KARTITEM__ \ No newline at end of file diff --git a/src/typedef.h b/src/typedef.h index dc9989b6e..9db777746 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -208,9 +208,6 @@ TYPEDEF (waypoint_t); TYPEDEF (mapUserProperty_t); TYPEDEF (mapUserProperties_t); -// k_kartitem.hpp -TYPEDEF (kartitem_t); - // lua_hudlib_drawlist.h typedef struct huddrawlist_s *huddrawlist_h; From c28769afc89e878b4c2f5465eeff7b55fbd03589 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 5 Nov 2025 19:37:18 -0500 Subject: [PATCH 09/76] Rebalance Alt. Shrink a bit * Nerf Alt. Shrink's speedboost * Bump it up some odds tiers * Increase the timer Slingshotting like it's fucking Angry Birds --- src/d_netcmd.c | 4 ++-- src/k_odds.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 92230cb74..20209ef7e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -502,7 +502,7 @@ consvar_t cv_kartstacking_grow_speedboost = CVAR_INIT ("vanillaboost_grow_speedb consvar_t cv_kartstacking_grow_accelboost = CVAR_INIT ("vanillaboost_grow_accelboost", "0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_grow_stackable = CVAR_INIT ("vanillaboost_grow_stackable", "Off", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); -consvar_t cv_kartstacking_altshrink_speedboost = CVAR_INIT ("vanillaboost_altshrink_speedboost", "0.56", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); +consvar_t cv_kartstacking_altshrink_speedboost = CVAR_INIT ("vanillaboost_altshrink_speedboost", "0.40", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_altshrink_accelboost = CVAR_INIT ("vanillaboost_altshrink_accelboost", "0.15", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_altshrink_stackable = CVAR_INIT ("vanillaboost_altshrink_stackable", "On", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); @@ -609,7 +609,7 @@ consvar_t cv_karteggmantype = CVAR_INIT ("karteggmantype", "Legacy", CV_NETVAR|C // Time limit for Alt. Shrink static CV_PossibleValue_t altshrinktime_cons_t[] = {{0, "MIN"}, {(INT16_MAX / TICRATE), "MAX"}, {0, NULL}}; -consvar_t cv_kartaltshrinktime = CVAR_INIT ("kartaltshrinktime", "13", CV_NETVAR|CV_CHEAT|CV_GUARD, altshrinktime_cons_t, NULL); +consvar_t cv_kartaltshrinktime = CVAR_INIT ("kartaltshrinktime", "14", CV_NETVAR|CV_CHEAT|CV_GUARD, altshrinktime_cons_t, NULL); // How far the player must be from the cluster to begin frequently rolling Invincibility. static CV_PossibleValue_t invindist_cons_t[] = {{1, "MIN"}, {32000, "MAX"}, {0, NULL}}; diff --git a/src/k_odds.c b/src/k_odds.c index 8afce132a..9fe28fcbc 100644 --- a/src/k_odds.c +++ b/src/k_odds.c @@ -120,7 +120,7 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x4 { 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Jawz x2 { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Egg Mine - { 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0} // Alt. Shrink + { 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Alt. Shrink }; static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = From 8bae1b3c91bfd576ea9042b6e0ce4e6d72e46edb Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 5 Nov 2025 22:05:51 -0500 Subject: [PATCH 10/76] Add items readme --- extras/Items.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 extras/Items.md diff --git a/extras/Items.md b/extras/Items.md new file mode 100644 index 000000000..5adaa3e8c --- /dev/null +++ b/extras/Items.md @@ -0,0 +1,64 @@ +# Items + +BlanKart features its own collection of both new items and toggleable alternate takes on existing SRB2Kart v1 items. Most new items also appear in Ring Racers, but function significantly differently. This wiki page will cover all necessary information for these items. + +## Alternate Items + +Alternative items are secondary versions of existing SRB2Kart items that **reinterpret them in (mostly) unique ways**. As BlanKart develops further, more alternate items may appear. + +To toggle on/off these alternate versions, you can do so with the following commands: + +- ``kartinvintype`` (for Invincibility) +- ``kartshrinktype`` (for Shrink) + +### Alt. Invincibility (Power: Occupies Slot) +Invincibility, in its normal form, is a power item that appears in the lower ranks, letting players cut through offroad and spin out opponents with prejudice. **Alt. Invincibility** works significantly different, trading its aggression for speed and optimized recovery. + +To begin, Alt. Invincibility's item odds work as an inversion of the Self-Propelled Bomb's: Like how the S.P.B. only appears when first is extremely far ahead of the pack, Alt. Invincibility only reveals itself to players who are **extremely far behind the pack**. In some extreme cases, Alt. Invincibilty will override the race-start cooldown to power-up players already left in the dust. + +Unlike the standard Invincibility, **you can't damage players with Alt. Invincibility**, instead bumping them like you would normally. + +Its time limit (and power) is directly tied to your distance from the "cluster player", the player closest to the largest collection on the map of (losing) players. If you notice yourself falling super far behind on the minimap, you're likely to roll Alt. Invincibility. **The further the distance, the stronger your invincibility**, and the faster you'll go. Think of it like a non-autopilot version of Mario Kart's Bullet Bill, or Sonic Racing's Drill Wisp. _Make huge comebacks, and don't lose hope!_ + +Do note, though: Alt. Invincibility is designed as a "gap closer", and not traditional catchup. Once you pass the cluster player, the timer **decreases at an exponentially fast rate**; don't count on it to let you steal wins from the leader! + +As you run out of power (your Invincibility drops below 5 seconds), **offroad will gradually begin to affect you again**. A warning signal usually sounds as your invincibility is about to run out, as well. Be careful when you see the rainbow color begin to fade! + +If you want something else, or your invincibility is running low, hold ITEM to cancel your Invincibility; the same as you would Grow. + +### Alt. Shrink (Power) +Shrink, in its normal form, functions much like Mario Kart's Lightning item. Most servers keep it off due to how frequent and disruptive it is to races. + +**Alt. Shrink** forgoes that and turns it into an inversion of Grow! In its alternate form, Shrink appears as a **midpack power item**, shrinking you to a dimunitive size and increasing your acceleration and top speed, whilist **doubling the amount of nearly every item** you roll while under its effects. With some luck, you can roll impressive items and put pressure on first! + +Be careful, though: running into anyone while in this state **will flip you over** and significantly cut your speed. Nimble dodging is required to make the most of this item! + +## New Items + +### Bubble Shield (Defense: Occupies Slot) +This is an item that appears around the upper-midpack of any race. This item's focus is **defense**. + +When a player holds onto this shield, they can inflate the shield to protect themselves from items. +As the shield is used more, and takes damage from items and player bumps, it **cracks more and more until finally shattering**. Some item interactions, like a snipe from a Banana or Orbinaut, make it **shatter right away**! + +Hold down ITEM to inflate the shield. Once you let go, it slowly deflates. As long as it's in an inflated state--be it inflating or deflating--you can **protect yourself from items** without spinning out. +While in an _uninflated_ state, however, the Bubble Shield can let you **nullify only one item hit**, shattering itself in the process. + +If you hold ITEM for too long, you'll **overinflate the shield**, causing it to shatter, no matter the amount of health it still had. That's not all bad, though; you'll get a nice speed boost, and be temporarily invulnerable to hazards and items; proximity mines are no match! + +The Bubble Shield also makes your bumps stronger: if you bump into another player, you won't rebound, but they will. If your shield isn't inflated while bumping other players, it'll **take severe damage** from the bump. Careful play is key with this shield! + +### Flame Shield (Offense+Boost: Occupies Slot) +This is an item that appears around the lower-midpack of any race. + +The Flame Shield can be used by holding down ITEM. + +When in use, a **Flamometer** appears next to your character. The Flamometer shows how much you're charging the shield, and how much fuel you have left. +The fuel gauge is constantly depleting once the Flame Shield is first used. As you hold ITEM; you'll increase the Flame Shield's boost power, but it'll **use up whatever fuel you have** at the moment. + +If you overcharge the Flame Shield, the Flamometer **catches fire**, and you'll gain no extra boost power, but can more easily cut through offroad. If you pace yourself, you can make this item last very long. + +If you run into players while charging your Flame Shield, **you'll flip them over**, making them lose a significant amount of speed. You however, rush right through them. + +### Land Mine (Drop Behind) +This is an item exclusive to first. Drop a discreet mine onto the track; anyone who runs into it **flips over and loses speed**. From efac1d27c2e74d5ef11b05d912e36c9376ecaad1 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Thu, 6 Nov 2025 22:57:21 +0100 Subject: [PATCH 11/76] Item refactor, part 1 Too much shit to explain, read the diff (you should anyway!) Very sloppy, expect lots of fixes TODO: SOC it all, separate "active odds" table, how to handle alt items --- src/Sourcefile | 3 +- src/command.h | 4 - src/d_netcmd.c | 132 +---- src/d_netcmd.h | 38 +- src/d_player.h | 55 -- src/deh_lua.c | 14 + src/deh_lua.h | 1 + src/deh_tables.c | 24 - src/doomstat.h | 3 - src/g_game.c | 8 +- src/info.c | 10 + src/k_altshrink.c | 82 --- src/k_altshrink.h | 60 --- src/k_bot.cpp | 1 + src/k_botitem.cpp | 4 +- src/k_botsearch.cpp | 1 + src/k_collide.c | 3 +- src/k_hud.c | 367 +++---------- src/k_hud.h | 2 +- src/{k_odds.c => k_items.c} | 1008 +++++++++++++++++++---------------- src/k_items.h | 147 +++++ src/k_kart.c | 83 +-- src/k_kart.h | 6 +- src/k_odds.h | 72 --- src/lua_baselib.c | 67 +-- src/lua_script.c | 11 +- src/m_menu.c | 133 ++--- src/p_enemy.c | 1 + src/p_inter.c | 4 +- src/p_mobj.c | 10 +- src/p_saveg.c | 11 +- src/p_setup.c | 12 +- src/p_spec.c | 2 +- src/p_tick.c | 10 +- src/p_user.c | 3 +- src/r_patchrotation.c | 1 + src/typedef.h | 5 + 37 files changed, 917 insertions(+), 1481 deletions(-) delete mode 100644 src/k_altshrink.c delete mode 100644 src/k_altshrink.h rename src/{k_odds.c => k_items.c} (56%) create mode 100644 src/k_items.h delete mode 100644 src/k_odds.h diff --git a/src/Sourcefile b/src/Sourcefile index 87fa585b3..311753777 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -133,8 +133,7 @@ k_bot.cpp k_botitem.cpp k_botsearch.cpp k_cluster.cpp -k_odds.c -k_altshrink.c +k_items.c k_grandprix.c k_boss.c k_hud.c diff --git a/src/command.h b/src/command.h index 5c0d81d8d..345be77df 100644 --- a/src/command.h +++ b/src/command.h @@ -189,10 +189,6 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots extern CV_PossibleValue_t kartspeed_cons_t[], gpdifficulty_cons_t[]; -// Alternative item types. -#define KARTITEM_NORMAL 0 -#define KARTITEM_ALTERN 1 - extern consvar_t cv_execversion; extern consvar_t cv_resetnetvars; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 20209ef7e..974e844d2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -68,7 +68,7 @@ #include "m_perfstats.h" #include "g_party.h" #include "k_specialstage.h" -#include "k_altshrink.h" +#include "k_items.h" #define CV_RESTRICT CV_NETVAR @@ -167,9 +167,6 @@ static void KartItemLitter_OnChange(void); static void KartItemPush_OnChange(void); static void KartAntiBump_OnChange(void); static void KartItemBreaker_OnChange(void); -static void KartInvinType_OnChange(void); -static void KartEggmanType_OnChange(void); -static void KartShrinkType_OnChange(void); static void KartBumpSpark_OnChange(void); static void Schedule_OnChange(void); @@ -386,35 +383,6 @@ consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = { //Alam: Dummy for save #endif // SRB2kart -consvar_t cv_sneaker = CVAR_INIT ("sneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_rocketsneaker = CVAR_INIT ("rocketsneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_invincibility = CVAR_INIT ("invincibility", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_banana = CVAR_INIT ("banana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_eggmanmonitor = CVAR_INIT ("eggmanmonitor", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_orbinaut = CVAR_INIT ("orbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_jawz = CVAR_INIT ("jawz", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_mine = CVAR_INIT ("mine", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_ballhog = CVAR_INIT ("ballhog", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_selfpropelledbomb = CVAR_INIT ("selfpropelledbomb", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_grow = CVAR_INIT ("grow", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_shrink = CVAR_INIT ("shrink", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_thundershield = CVAR_INIT ("thundershield", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_hyudoro = CVAR_INIT ("hyudoro", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_pogospring = CVAR_INIT ("pogospring", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_kitchensink = CVAR_INIT ("kitchensink", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_superring = CVAR_INIT ("superring", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_landmine = CVAR_INIT ("landmine", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_bubbleshield = CVAR_INIT ("bubbleshield", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_flameshield = CVAR_INIT ("flameshield", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); - -consvar_t cv_dualsneaker = CVAR_INIT ("dualsneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_triplesneaker = CVAR_INIT ("triplesneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_triplebanana = CVAR_INIT ("triplebanana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_decabanana = CVAR_INIT ("decabanana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_tripleorbinaut = CVAR_INIT ("tripleorbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_quadorbinaut = CVAR_INIT ("quadorbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_dualjawz = CVAR_INIT ("dualjawz", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); - static CV_PossibleValue_t kartminimap_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}}; consvar_t cv_kartminimap = CVAR_INIT ("kartminimap", "4", CV_SAVE, kartminimap_cons_t, NULL); consvar_t cv_kartcheck = CVAR_INIT ("kartcheck", "Yes", CV_SAVE, CV_YesNo, NULL); @@ -601,12 +569,6 @@ consvar_t cv_kartspbdist = CVAR_INIT ("kartspbdist", "4432", CV_NETVAR|CV_CHEAT| consvar_t cv_kartlegacyspbdist = CVAR_INIT ("kartlegacyspbdist", "2216", CV_NETVAR|CV_CHEAT|CV_GUARD, spbdist_cons_t, NULL); #undef MAXODDSDIST -// Items with alternative types -static CV_PossibleValue_t kartitemtype_cons_t[] = {{KARTITEM_NORMAL, "Legacy"}, {KARTITEM_ALTERN, "Alternative"}, {0, NULL}}; -consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, kartitemtype_cons_t, KartInvinType_OnChange); -consvar_t cv_kartshrinktype = CVAR_INIT ("kartshrinktype", "Legacy", CV_NETVAR|CV_CALL, kartitemtype_cons_t, KartShrinkType_OnChange); -consvar_t cv_karteggmantype = CVAR_INIT ("karteggmantype", "Legacy", CV_NETVAR|CV_CALL, kartitemtype_cons_t, KartEggmanType_OnChange); - // Time limit for Alt. Shrink static CV_PossibleValue_t altshrinktime_cons_t[] = {{0, "MIN"}, {(INT16_MAX / TICRATE), "MAX"}, {0, NULL}}; consvar_t cv_kartaltshrinktime = CVAR_INIT ("kartaltshrinktime", "14", CV_NETVAR|CV_CHEAT|CV_GUARD, altshrinktime_cons_t, NULL); @@ -631,23 +593,7 @@ consvar_t cv_kartflame_offroadburn = CVAR_INIT ("kartflame_offroadburn", "Off", static CV_PossibleValue_t kartairsquish_cons_t[] = {{1, "Squish"}, {2, "Flip-over"}, {0, "None"}, {0, NULL}}; consvar_t cv_kartairsquish = CVAR_INIT ("kartairsquish", "None", CV_NETVAR, kartairsquish_cons_t, NULL); -static CV_PossibleValue_t kartdebugitem_cons_t[] = -{ -#define FOREACH( name, n ) { n, #name } - KART_ITEM_ITERATOR, -#undef FOREACH - {0} -}; -consvar_t cv_kartdebugitem = CVAR_INIT ("kartdebugitem", "NONE", CV_NETVAR|CV_CHEAT, kartdebugitem_cons_t, NULL); -static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}}; -consvar_t cv_kartdebugamount = CVAR_INIT ("kartdebugamount", "1", CV_NETVAR|CV_CHEAT, kartdebugamount_cons_t, NULL); consvar_t cv_kartdebugshrink = CVAR_INIT ("kartdebugshrink", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -#ifdef DEVELOP -#define VALUE "Yes" -#else -#define VALUE "No" -#endif -#undef VALUE consvar_t cv_kartdebugdistribution = CVAR_INIT ("kartdebugdistribution", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); consvar_t cv_kartdebughuddrop = CVAR_INIT ("kartdebughuddrop", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); @@ -6235,8 +6181,7 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) SINT8 item = READSINT8(*cp); UINT8 amt = READUINT8(*cp); - item = max(item, KITEM_SAD); - item = min(item, NUMKARTITEMS - 1); + item = CLAMP(item, KITEM_SAD, numkartitems - 1); K_StripItems(player); @@ -6252,10 +6197,7 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) } else { - // FIXME: we should have actual KITEM_ name array - const char *itemname = cv_kartdebugitem.PossibleValue[1 + item].strvalue; - - CV_CheaterWarning(playernum, va("give item %s x%d", itemname, amt)); + CV_CheaterWarning(playernum, va("give item %s x%d", item < 0 ? "SAD" : kartitems[item].name, amt)); } break; } @@ -6540,8 +6482,6 @@ static void Command_KartGiveItem_f(void) const char *name; INT32 item; - const char * str; - int i; ac = COM_Argc(); @@ -6553,7 +6493,7 @@ static void Command_KartGiveItem_f(void) } else { - item = NUMKARTITEMS; + item = numkartitems; name = COM_Argv(1); @@ -6569,18 +6509,18 @@ static void Command_KartGiveItem_f(void) CONS_Printf("\x83" "Autocomplete:\n"); /* then do very loose partial matching */ - for (i = 0; ( str = kartdebugitem_cons_t[i].strvalue ); ++i) + for (i = 0; i < numkartitems; i++) { - if (strcasestr(str, name) != NULL) + if (strcasestr(kartitems[i].name, name) != NULL) { - CONS_Printf("\x83\t%s\n", str); - item = kartdebugitem_cons_t[i].value; + CONS_Printf("\x83\t%s\n", kartitems[i].name); + item = i; } } } } - if (item < NUMKARTITEMS) + if (item < numkartitems) { INT32 amt; @@ -7828,60 +7768,6 @@ static void KartItemBreaker_OnChange(void) } } -static void KartInvinType_OnChange(void) -{ - if (K_CanChangeRules(false) == false) - { - return; - } - - if (leveltime < starttime) - { - CONS_Printf(M_GetText("Invincibility type has been changed to \"%s\".\n"), cv_kartinvintype.string); - invintype = (UINT8)cv_kartinvintype.value; - } - else - { - CONS_Printf(M_GetText("Invincibility type will be changed to \"%s\" next round.\n"), cv_kartinvintype.string); - } -} - -static void KartShrinkType_OnChange(void) -{ - if (K_CanChangeRules(false) == false) - { - return; - } - - if (leveltime < starttime) - { - CONS_Printf(M_GetText("Shrink type has been changed to \"%s\".\n"), cv_kartshrinktype.string); - shrinktype = (UINT8)cv_kartshrinktype.value; - } - else - { - CONS_Printf(M_GetText("Shrink type will be changed to \"%s\" next round.\n"), cv_kartshrinktype.string); - } -} - -static void KartEggmanType_OnChange(void) -{ - if (K_CanChangeRules(false) == false) - { - return; - } - - if (leveltime < starttime) - { - CONS_Printf(M_GetText("Eggman Monitor type has been changed to \"%s\".\n"), cv_karteggmantype.string); - eggmantype = (UINT8)cv_karteggmantype.value; - } - else - { - CONS_Printf(M_GetText("Eggman Monitor type will be changed to \"%s\" next round.\n"), cv_karteggmantype.string); - } -} - static void KartBumpSpark_OnChange(void) { if (K_CanChangeRules(false) == false) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 6d188f04d..f4d83f978 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -72,38 +72,6 @@ extern consvar_t cv_spectatorreentry, cv_antigrief; extern consvar_t cv_debugtraversemax; #endif -// SRB2kart items -extern consvar_t - cv_sneaker, - cv_rocketsneaker, - cv_invincibility, - cv_banana, - cv_eggmanmonitor, - cv_orbinaut, - cv_jawz, - cv_mine, - cv_ballhog, - cv_selfpropelledbomb, - cv_grow, - cv_shrink, - cv_thundershield, - cv_hyudoro, - cv_pogospring, - cv_kitchensink, - cv_superring, - cv_landmine, - cv_bubbleshield, - cv_flameshield; - -extern consvar_t - cv_dualsneaker, - cv_triplesneaker, - cv_triplebanana, - cv_decabanana, - cv_tripleorbinaut, - cv_quadorbinaut, - cv_dualjawz; - extern consvar_t cv_kartminimap; extern consvar_t cv_kartcheck; extern consvar_t cv_kartinvinsfx; @@ -160,6 +128,11 @@ extern consvar_t cv_kartstacking_bubble_speedboost; extern consvar_t cv_kartstacking_bubble_accelboost; extern consvar_t cv_kartstacking_bubble_stackable; +extern consvar_t cv_kartaltshrinktime; +extern consvar_t cv_kartstacking_altshrink_speedboost; +extern consvar_t cv_kartstacking_altshrink_accelboost; +extern consvar_t cv_kartstacking_altshrink_stackable; + extern consvar_t cv_kartstacking_start_speedboost; extern consvar_t cv_kartstacking_start_accelboost; extern consvar_t cv_kartstacking_start_stackable; @@ -200,7 +173,6 @@ extern consvar_t cv_kartbumpspark; extern consvar_t cv_kartbumpspring; extern consvar_t cv_kartslipdash; extern consvar_t cv_kartslopeboost; -extern consvar_t cv_kartinvintype; extern consvar_t cv_kartinvindist; extern consvar_t cv_kartinvindistmul; extern consvar_t cv_kartinvin_maxtime; diff --git a/src/d_player.h b/src/d_player.h index 979f1165f..a914f59da 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -137,61 +137,6 @@ typedef enum CR_ZOOMTUBE, } carrytype_t; // carry -/* -To use: #define FOREACH( name, number ) -Do with it whatever you want. -Run this macro, then #undef FOREACH afterward -*/ -#define KART_ITEM_ITERATOR \ - FOREACH (SAD, -1),\ - FOREACH (NONE, 0),\ - FOREACH (SNEAKER, 1),\ - FOREACH (ROCKETSNEAKER, 2),\ - FOREACH (INVINCIBILITY, 3),\ - FOREACH (BANANA, 4),\ - FOREACH (EGGMAN, 5),\ - FOREACH (ORBINAUT, 6),\ - FOREACH (JAWZ, 7),\ - FOREACH (MINE, 8),\ - FOREACH (BALLHOG, 9),\ - FOREACH (SPB, 10),\ - FOREACH (GROW, 11),\ - FOREACH (SHRINK, 12),\ - FOREACH (THUNDERSHIELD, 13),\ - FOREACH (HYUDORO, 14),\ - FOREACH (POGOSPRING, 15),\ - FOREACH (KITCHENSINK, 16),\ - FOREACH (SUPERRING, 17),\ - FOREACH (LANDMINE, 18),\ - FOREACH (BUBBLESHIELD, 19),\ - FOREACH (FLAMESHIELD, 20) - -typedef enum -{ -#define FOREACH( name, n ) KITEM_ ## name = n - KART_ITEM_ITERATOR, -#undef FOREACH - - NUMKARTITEMS, - - // Additional roulette numbers, only used for K_KartGetItemResult - KRITEM_DUALSNEAKER = NUMKARTITEMS, - KRITEM_TRIPLESNEAKER, - KRITEM_TRIPLEBANANA, - KRITEM_TENFOLDBANANA, - KRITEM_TRIPLEORBINAUT, - KRITEM_QUADORBINAUT, - KRITEM_DUALJAWZ, - - NUMBASEKARTRESULTS, - - // alt items implemented as separate things - KAITEM_EGGMINE = NUMBASEKARTRESULTS, // also landmine - KAITEM_ALTERNSHRINK, - - NUMKARTRESULTS -} kartitems_t; - typedef enum { KROULETTE_DISABLED, KROULETTE_ACTIVE, diff --git a/src/deh_lua.c b/src/deh_lua.c index 790fc9777..7b7b279be 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -439,6 +439,20 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) } return luaL_error(L, "karthud '%s' could not be found.\n", word); } + else if (mathlib && fastncmp("KITEM_",word,6)) { + p = word+6; + if (fastcmp(p, "LIGHTNINGSHIELD")) + { + lua_pushinteger(L, KITEM_THUNDERSHIELD); + return 1; + } + for (i = 0; i < numkartitems; i++) + if (fastcmp(p, kartitems[i].name)) { + lua_pushinteger(L, i); + return 1; + } + return luaL_error(L, "kartitem '%s' could not be found.\n", word); + } else if (fastncmp("SKINCOLOR_",word,10)) { p = word+10; i = DEH_FindSkincolor(p); diff --git a/src/deh_lua.h b/src/deh_lua.h index 7b767f017..ed97a1418 100644 --- a/src/deh_lua.h +++ b/src/deh_lua.h @@ -27,6 +27,7 @@ extern "C" { #include "fastcmp.h" #include "lua_script.h" #include "lua_libs.h" +#include "k_items.h" #include "dehacked.h" #include "deh_tables.h" diff --git a/src/deh_tables.c b/src/deh_tables.c index c6b7c6b29..f714214c2 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1511,24 +1511,6 @@ struct int_const_s const INT_CONST[] = { {"BASEVIDWIDTH", BASEVIDWIDTH}, {"BASEVIDHEIGHT", BASEVIDHEIGHT}, - // SRB2Kart - // kartitems_t -#define FOREACH( name, n ) { TOSTR (KITEM_ ## name), KITEM_ ## name } - KART_ITEM_ITERATOR, // Actual items (can be set for k_itemtype) -#undef FOREACH - {"NUMKARTITEMS",NUMKARTITEMS}, - {"KRITEM_DUALSNEAKER",KRITEM_DUALSNEAKER}, // Additional roulette IDs (not usable for much in Lua besides K_GetItemPatch) - {"KRITEM_TRIPLESNEAKER",KRITEM_TRIPLESNEAKER}, - {"KRITEM_TRIPLEBANANA",KRITEM_TRIPLEBANANA}, - {"KRITEM_TENFOLDBANANA",KRITEM_TENFOLDBANANA}, - {"KRITEM_TRIPLEORBINAUT",KRITEM_TRIPLEORBINAUT}, - {"KRITEM_QUADORBINAUT",KRITEM_QUADORBINAUT}, - {"KRITEM_DUALJAWZ",KRITEM_DUALJAWZ}, - {"NUMBASEKARTRESULTS",NUMBASEKARTRESULTS}, - {"KAITEM_EGGMINE", KAITEM_EGGMINE}, - {"KAITEM_ALTERNSHRINK", KAITEM_ALTERNSHRINK}, - {"NUMKARTRESULTS",NUMKARTRESULTS}, - // kartshields_t {"KSHIELD_NONE",KSHIELD_NONE}, {"KSHIELD_THUNDER",KSHIELD_THUNDER}, @@ -1537,9 +1519,6 @@ struct int_const_s const INT_CONST[] = { {"KSHIELD_FLAME",KSHIELD_FLAME}, {"NUMKARTSHIELDS",NUMKARTSHIELDS}, - // kartitems_t - {"KITEM_LIGHTNINGSHIELD",KITEM_THUNDERSHIELD}, - // kartroulette_t {"KROULETTE_DISABLED",KROULETTE_DISABLED}, {"KROULETTE_ACTIVE",KROULETTE_ACTIVE}, @@ -1685,9 +1664,6 @@ struct int_const_s const INT_CONST[] = { {"FACE_WANTED", FACE_WANTED}, {"FACE_MINIMAP", FACE_MINIMAP}, {"NUMFACES", NUMFACES}, - // alt item type - {"KARTITEM_NORMAL", KARTITEM_NORMAL}, - {"KARTITEM_ALTERN", KARTITEM_ALTERN}, // k_waypoint.h values {"DEFAULT_WAYPOINT_RADIUS",DEFAULT_WAYPOINT_RADIUS}, diff --git a/src/doomstat.h b/src/doomstat.h index 41173c77d..4c350d307 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -669,9 +669,6 @@ extern boolean franticitems; extern boolean encoremode, prevencoremode; extern boolean comeback; -extern UINT8 invintype; -extern UINT8 eggmantype; - extern SINT8 mostwanted; extern SINT8 battlewanted[4]; extern tic_t wantedcalcdelay; diff --git a/src/g_game.c b/src/g_game.c index b607d9cb0..3430f4113 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -61,8 +61,7 @@ #include "k_boss.h" #include "k_specialstage.h" #include "k_bot.h" -#include "k_odds.h" -#include "k_altshrink.h" +#include "k_items.h" #include "doomstat.h" #include "acs/interface.h" #include "k_director.h" @@ -300,9 +299,6 @@ boolean encoremode = false; // Encore Mode currently enabled? boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? boolean comeback; // Battle Mode's karma comeback is on/off -// alt item values -UINT8 invintype; // How Invincibility functions. 0 for Legacy/Vanilla, 1 for Alternative. -UINT8 eggmantype; // How Eggman Monitor functions. 0 for Legacy/Vanilla, 1 for Alternative (Egg Mine). // Voting system INT16 votelevels[12][2]; // Levels that were rolled by the host @@ -314,7 +310,7 @@ SINT8 battlewanted[4]; // WANTED players in battle, worth x2 points SINT8 mostwanted; // The "most wanted" (first in line) player. tic_t wantedcalcdelay; // Time before it recalculates WANTED tic_t indirectitemcooldown; // Cooldown before any more Shrink, SPB, or any other item that works indirectly is awarded -// hyubgone was taken out back. See k_odds.c +// hyubgone was taken out back. See k_items.c tic_t mapreset; // Map reset delay when enough players have joined an empty game boolean thwompsactive; // Thwomps activate on lap 2 UINT8 lastLowestLap; // Last lowest lap, for activating race lap executors diff --git a/src/info.c b/src/info.c index 9cef1eef2..9f25274a6 100644 --- a/src/info.c +++ b/src/info.c @@ -20,6 +20,7 @@ #include "d_main.h" #include "m_menu.h" #include "deh_tables.h" +#include "k_items.h" // Hey, moron! If you wanna change this table, you can just change the sprite enum in info/sprites.h, // so you don't have to copy and paste the list of sprite names back in here :^) @@ -233,4 +234,13 @@ void P_ResetData(INT32 flags) for (i = 0; i < MN_FIRSTFREESLOT; i++, name += strlen(name)+1) DEH_Link(name, &menudefs[i].info, &menunames); } + + // kartitems + if (init) + { + memset(kartitems, 0, sizeof(kartitems)); + K_InitializeItems(); + + numkartitems = KITEM_FIRSTFREESLOT; + } } diff --git a/src/k_altshrink.c b/src/k_altshrink.c deleted file mode 100644 index 202cc47d3..000000000 --- a/src/k_altshrink.c +++ /dev/null @@ -1,82 +0,0 @@ -// BLANKART -//----------------------------------------------------------------------------- -/// \file k_altshrink.c -/// \brief Alternative Shrink item type functions. - -#include "doomdef.h" -#include "doomtype.h" -#include "doomstat.h" - -#include "command.h" -#include "console.h" -#include "d_player.h" -#include "k_kart.h" -#include "k_altshrink.h" -#include "k_cluster.hpp" -#include "m_fixed.h" -#include "p_local.h" - - -UINT8 shrinktype; // How Shrink functions. 0 for Legacy/Vanilla, 1 for Alternative. - -void K_UpdateShrinkType(void) -{ - if (leveltime < starttime) - { - CONS_Printf(M_GetText("Shrink type has been changed to \"%s\".\n"), cv_kartshrinktype.string); - } - - shrinktype = (UINT8)cv_kartshrinktype.value; -} - -/** \brief Depending on the Shrink type set, this returns true or false if the player is shrunk. - - \param player (player_t) Player to test shrunken status for - \param legacy (boolean) Legacy shrink? - - \return void -*/ -boolean K_IsShrunkMode(const player_t *player, boolean legacy) -{ - boolean shrunk = K_IsShrunk(player); - boolean legacytype = (!K_IsKartItemAlternate(KITEM_SHRINK)); - - if (legacy) - { - return ((shrunk) && (legacytype)); - } - - return ((shrunk) && (!legacytype)); -} - -#define ALTSHRINK_EPSILON (320 * FRACUNIT) -#define NEIGHBOR_IFRAMES (TICRATE / 2) -#define BASE_IFRAMES (2 * TICRATE) - -void K_AltShrinkIFrames(player_t *player) -{ - vector3_t tempclusterpoint; - - // Find neighbors - INT32 N = K_CountNeighboringPlayers(player, ALTSHRINK_EPSILON, &tempclusterpoint); - - // For every neighbor, add some iframes for a clean breakaway. - player->flashing = (UINT16)min((INT32)(UINT16_MAX), BASE_IFRAMES + (NEIGHBOR_IFRAMES * N)); -} - -#define PITY_SHRINKINCREASE_BASE (3) -#define PITY_SHRINKINCREASE (TICRATE / 2) - -void K_AltShrinkPityIncrease(player_t *player) -{ - // Increase your shrink timer by a little bit for every player you run into. - INT32 shrinktime = (UINT32)(K_GetShrinkTime(player)); - - fixed_t dimin = FRACUNIT; - - dimin = max(0, 5 - player->altshrinktimeshit) * FRACUNIT / 5; - - shrinktime = min(INT16_MAX, shrinktime + PITY_SHRINKINCREASE_BASE + FixedMul(PITY_SHRINKINCREASE, dimin)); - - player->growshrinktimer = ((INT16)shrinktime) * -1; -} \ No newline at end of file diff --git a/src/k_altshrink.h b/src/k_altshrink.h deleted file mode 100644 index a35640606..000000000 --- a/src/k_altshrink.h +++ /dev/null @@ -1,60 +0,0 @@ -// BLANKART -//----------------------------------------------------------------------------- -/// \file k_altshrink.h -/// \brief Alternative Shrink item type functions. - -#ifndef __K_ALTSHRINK__ -#define __K_ALTSHRINK__ - -#include "doomdef.h" -#include "d_player.h" // Need for player_t -#include "command.h" // Need for player_t -#include "m_fixed.h" -#include "k_kart.h" - -extern consvar_t cv_kartaltshrinktime; -extern consvar_t cv_kartshrinktype; - -extern consvar_t cv_kartstacking_altshrink_speedboost; -extern consvar_t cv_kartstacking_altshrink_accelboost; -extern consvar_t cv_kartstacking_altshrink_stackable; - -extern UINT8 shrinktype; - -#define ALTSHRINKTIME CV_Get(&cv_kartaltshrinktime) - -#define SHRINKSPEEDBOOST CV_Get(&cv_kartstacking_altshrink_speedboost) -#define SHRINKACCELBOOST CV_Get(&cv_kartstacking_altshrink_accelboost) -#define SHRINKSTACKABLE CV_Get(&cv_kartstacking_altshrink_stackable) - -/** \brief Returns true or false if the player is shrunk. - - \param player (player_t) Player to test shrunken status for - - \return Is this player shrunk? -*/ -FUNCINLINE static ATTRINLINE boolean K_IsShrunk(const player_t *player) -{ - return (player->growshrinktimer < 0); -} - -FUNCINLINE static ATTRINLINE INT16 K_GetShrinkTime(player_t *player) -{ - return (player->growshrinktimer * -1); -} - -FUNCINLINE static ATTRINLINE fixed_t K_AccomodateShrinkScaling(fixed_t x) -{ - return FixedDiv(x, SHRINK_SCALE); -} - -boolean K_IsShrunkMode(const player_t *player, boolean legacy); - -#define K_IsLegacyShrunk(player) (K_IsShrunkMode(player, true)) -#define K_IsAltShrunk(player) (K_IsShrunkMode(player, false)) - -void K_UpdateShrinkType(void); -void K_AltShrinkIFrames(player_t *player); -void K_AltShrinkPityIncrease(player_t *player); - -#endif // __K_ALTSHRINK__ \ No newline at end of file diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 2a4d02491..75fa915f2 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -44,6 +44,7 @@ #include "blan/b_soc.h" #include "v_video.h" // for debugging #include "k_waypoint.h" +#include "k_items.h" consvar_t cv_forcebots = CVAR_INIT ("kartforcebots", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); consvar_t cv_botcontrol = CVAR_INIT ("kartbotcontrol", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index 4ff9197cd..277d64552 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -29,7 +29,7 @@ #include "m_random.h" #include "r_things.h" // numskins #include "m_easing.h" -#include "k_odds.h" +#include "k_items.h" #include "k_waypoint.h" // Looks for players around the bot, and presses the item button @@ -1155,7 +1155,7 @@ static void K_BotItemRouletteMash(botdata_t *bd, const player_t *player) } } - if (player->rings < 0 && cv_superring.value && K_RingsActive()) + if (player->rings < 0 && K_ItemResultEnabled("superring") && K_RingsActive()) { // Uh oh, we need a loan! // It'll be better in the long run for bots to lose an item set for 5-15 free rings. diff --git a/src/k_botsearch.cpp b/src/k_botsearch.cpp index 1597596c4..0d44462d1 100644 --- a/src/k_botsearch.cpp +++ b/src/k_botsearch.cpp @@ -33,6 +33,7 @@ #include "p_slopes.h" // P_GetZAt #include "m_perfstats.h" #include "k_objects.h" +#include "k_items.h" #include "blan/b_soc.h" diff --git a/src/k_collide.c b/src/k_collide.c index 3a2842817..234d05528 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -2,13 +2,12 @@ /// \brief SRB2Kart item collision hooks #include "k_collide.h" -#include "k_altshrink.h" #include "k_stats.h" #include "doomstat.h" #include "doomtype.h" #include "p_mobj.h" #include "k_kart.h" -#include "k_odds.h" +#include "k_items.h" #include "p_local.h" #include "s_sound.h" #include "r_main.h" // R_PointToAngle2, R_PointToDist2 diff --git a/src/k_hud.c b/src/k_hud.c index 30d5252e0..a807dbdd6 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -17,7 +17,7 @@ #include "k_boss.h" #include "k_color.h" #include "k_director.h" -#include "k_odds.h" +#include "k_items.h" #include "p_mobj.h" #include "screen.h" #include "doomtype.h" @@ -186,26 +186,6 @@ static patch_t *kp_itemmulsticker[4]; static patch_t *kp_itemx; static patch_t *kp_sadface[2]; -static patch_t *kp_sneaker[4]; -static patch_t *kp_rocketsneaker[2]; -static patch_t *kp_invincibility[13]; -static patch_t *kp_banana[5]; -static patch_t *kp_eggman[2]; -static patch_t *kp_orbinaut[5]; -static patch_t *kp_jawz[3]; -static patch_t *kp_mine[2]; -static patch_t *kp_ballhog[2]; -static patch_t *kp_selfpropelledbomb[2]; -static patch_t *kp_grow[2]; -static patch_t *kp_shrink[2]; -static patch_t *kp_thundershield[2]; -static patch_t *kp_hyudoro[2]; -static patch_t *kp_pogospring[2]; -static patch_t *kp_kitchensink[2]; -static patch_t *kp_superring[2]; -static patch_t *kp_landmine[2]; -static patch_t *kp_bubbleshield[2]; -static patch_t *kp_flameshield[2]; static patch_t *kp_check[6]; @@ -540,87 +520,31 @@ void K_LoadKartHUDGraphics(void) // Kart Item Windows HU_UpdatePatch(&kp_itembg[0], "K_ITBG"); HU_UpdatePatch(&kp_itembg[1], "K_ITBGD"); + HU_UpdatePatch(&kp_itembg[2], "K_ISBG"); + HU_UpdatePatch(&kp_itembg[3], "K_ISBGD"); HU_UpdatePatch(&kp_itembg[4], "K_ITBC"); HU_UpdatePatch(&kp_itembg[5], "K_ITBCD"); + HU_UpdatePatch(&kp_itembg[6], "K_ISBC"); + HU_UpdatePatch(&kp_itembg[7], "K_ISBCD"); HU_UpdatePatch(&kp_itemtimer[0], "K_ITIMER"); + HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); + HU_UpdatePatch(&kp_itemmulsticker[1], "K_ISMUL"); HU_UpdatePatch(&kp_itemmulsticker[2], "K_ITMULC"); + HU_UpdatePatch(&kp_itemmulsticker[3], "K_ISMULC"); HU_UpdatePatch(&kp_itemx, "K_ITX"); HU_UpdatePatch(&kp_sadface[0], "K_ITSAD"); - HU_UpdatePatch(&kp_sneaker[0], "K_ITSHOE"); - HU_UpdatePatch(&kp_sneaker[1], "K_ITSHO2"); - HU_UpdatePatch(&kp_sneaker[2], "K_ITSHO3"); - HU_UpdatePatch(&kp_rocketsneaker[0], "K_ITRSHE"); - - sprintf(buffer, "K_ITINVx"); - for (i = 0; i < 7; i++) - { - buffer[7] = '1'+i; - HU_UpdatePatch(&kp_invincibility[i], "%s", buffer); - } - HU_UpdatePatch(&kp_banana[0], "K_ITBANA"); - HU_UpdatePatch(&kp_banana[1], "K_ITBAN2"); - HU_UpdatePatch(&kp_banana[2], "K_ITBAN3"); - HU_UpdatePatch(&kp_banana[3], "K_ITBAN4"); - HU_UpdatePatch(&kp_eggman[0], "K_ITEGGM"); - sprintf(buffer, "K_ITORBx"); - for (i = 0; i < 4; i++) - { - buffer[7] = '1'+i; - HU_UpdatePatch(&kp_orbinaut[i], "%s", buffer); - } - HU_UpdatePatch(&kp_jawz[0], "K_ITJAWZ"); - HU_UpdatePatch(&kp_jawz[1], "K_ITJAW2"); - HU_UpdatePatch(&kp_mine[0], "K_ITMINE"); - HU_UpdatePatch(&kp_ballhog[0], "K_ITBHOG"); - HU_UpdatePatch(&kp_selfpropelledbomb[0], "K_ITSPB"); - HU_UpdatePatch(&kp_grow[0], "K_ITGROW"); - HU_UpdatePatch(&kp_shrink[0], "K_ITSHRK"); - HU_UpdatePatch(&kp_thundershield[0], "K_ITTHNS"); - HU_UpdatePatch(&kp_hyudoro[0], "K_ITHYUD"); - HU_UpdatePatch(&kp_pogospring[0], "K_ITPOGO"); - HU_UpdatePatch(&kp_kitchensink[0], "K_ITSINK"); - HU_UpdatePatch(&kp_superring[0], "K_ITRING"); - HU_UpdatePatch(&kp_landmine[0], "K_ITLNDM"); - HU_UpdatePatch(&kp_bubbleshield[0], "K_ITBUBS"); - HU_UpdatePatch(&kp_flameshield[0], "K_ITFLMS"); - - // Splitscreen - HU_UpdatePatch(&kp_itembg[2], "K_ISBG"); - HU_UpdatePatch(&kp_itembg[3], "K_ISBGD"); - HU_UpdatePatch(&kp_itembg[6], "K_ISBC"); - HU_UpdatePatch(&kp_itembg[7], "K_ISBCD"); - HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); - HU_UpdatePatch(&kp_itemmulsticker[1], "K_ISMUL"); - HU_UpdatePatch(&kp_itemmulsticker[3], "K_ISMULC"); - HU_UpdatePatch(&kp_sadface[1], "K_ISSAD"); - HU_UpdatePatch(&kp_sneaker[3], "K_ISSHOE"); - HU_UpdatePatch(&kp_rocketsneaker[1], "K_ISRSHE"); - sprintf(buffer, "K_ISINVx"); - for (i = 0; i < 6; i++) + + for (i = 0; i < numkartitems; i++) { - buffer[7] = '1'+i; - HU_UpdatePatch(&kp_invincibility[i+7], "%s", buffer); + kartitem_t *item = &kartitems[i]; + for (j = 0; j < item->graphics[0].numpatches; j++) + HU_UpdatePatch(&item->graphics[0].patches[j], item->graphics[0].patchnames[j]); + for (j = 0; j < item->graphics[1].numpatches; j++) + HU_UpdatePatch(&item->graphics[1].patches[j], item->graphics[1].patchnames[j]); } - HU_UpdatePatch(&kp_banana[4], "K_ISBANA"); - HU_UpdatePatch(&kp_eggman[1], "K_ISEGGM"); - HU_UpdatePatch(&kp_orbinaut[4], "K_ISORBN"); - HU_UpdatePatch(&kp_jawz[2], "K_ISJAWZ"); - HU_UpdatePatch(&kp_mine[1], "K_ISMINE"); - HU_UpdatePatch(&kp_ballhog[1], "K_ISBHOG"); - HU_UpdatePatch(&kp_selfpropelledbomb[1], "K_ISSPB"); - HU_UpdatePatch(&kp_grow[1], "K_ISGROW"); - HU_UpdatePatch(&kp_shrink[1], "K_ISSHRK"); - HU_UpdatePatch(&kp_thundershield[1], "K_ISTHNS"); - HU_UpdatePatch(&kp_hyudoro[1], "K_ISHYUD"); - HU_UpdatePatch(&kp_pogospring[1], "K_ISPOGO"); - HU_UpdatePatch(&kp_kitchensink[1], "K_ISSINK"); - HU_UpdatePatch(&kp_superring[1], "K_ISRING"); - HU_UpdatePatch(&kp_landmine[1], "K_ISLNDM"); - HU_UpdatePatch(&kp_bubbleshield[1], "K_ISBUBS"); - HU_UpdatePatch(&kp_flameshield[1], "K_ISFLMS"); // CHECK indicators sprintf(buffer, "K_CHECKx"); @@ -723,101 +647,20 @@ void K_LoadKartHUDGraphics(void) } } -// For the item toggle menu -const char *K_GetItemPatch(UINT8 item, boolean tiny) +patch_t *K_GetCachedItemPatch(SINT8 type, boolean tiny, UINT8 amount) { - switch (item) - { - case KITEM_SNEAKER: - return (tiny ? "K_ISSHOE" : "K_ITSHOE"); - case KITEM_ROCKETSNEAKER: - return (tiny ? "K_ISRSHE" : "K_ITRSHE"); - case KITEM_INVINCIBILITY: - return (tiny ? "K_ISINV1" : "K_ITINV1"); - case KITEM_BANANA: - return (tiny ? "K_ISBANA" : "K_ITBANA"); - case KITEM_EGGMAN: - return (tiny ? "K_ISEGGM" : "K_ITEGGM"); - case KITEM_ORBINAUT: - return (tiny ? "K_ISORBN" : "K_ITORB1"); - case KITEM_JAWZ: - return (tiny ? "K_ISJAWZ" : "K_ITJAWZ"); - case KITEM_MINE: - return (tiny ? "K_ISMINE" : "K_ITMINE"); - case KITEM_BALLHOG: - return (tiny ? "K_ISBHOG" : "K_ITBHOG"); - case KITEM_SPB: - return (tiny ? "K_ISSPB" : "K_ITSPB"); - case KITEM_GROW: - return (tiny ? "K_ISGROW" : "K_ITGROW"); - case KITEM_SHRINK: - return (tiny ? "K_ISSHRK" : "K_ITSHRK"); - case KITEM_THUNDERSHIELD: - return (tiny ? "K_ISTHNS" : "K_ITTHNS"); - case KITEM_HYUDORO: - return (tiny ? "K_ISHYUD" : "K_ITHYUD"); - case KITEM_POGOSPRING: - return (tiny ? "K_ISPOGO" : "K_ITPOGO"); - case KITEM_KITCHENSINK: - return (tiny ? "K_ISSINK" : "K_ITSINK"); - case KITEM_SUPERRING: - return (tiny ? "K_ISRING" : "K_ITRING"); - case KITEM_LANDMINE: - return (tiny ? "K_ISLNDM" : "K_ITLNDM"); - case KITEM_BUBBLESHIELD: - return (tiny ? "K_ISBUBS" : "K_ITBUBS"); - case KITEM_FLAMESHIELD: - return (tiny ? "K_ISFLMS" : "K_ITFLMS"); - case KRITEM_DUALSNEAKER: - return (tiny ? "K_ISSHOE" : "K_ITSHO2"); - case KRITEM_TRIPLESNEAKER: - return (tiny ? "K_ISSHOE" : "K_ITSHO3"); - case KRITEM_TRIPLEORBINAUT: - return (tiny ? "K_ISORBN" : "K_ITORB3"); - case KRITEM_DUALJAWZ: - return (tiny ? "K_ISJAWZ" : "K_ITJAW2"); - case KRITEM_TRIPLEBANANA: - return (tiny ? "K_ISBANA" : "K_ITBAN3"); - case KRITEM_TENFOLDBANANA: - return (tiny ? "K_ISBANA" : "K_ITBAN4"); - case KRITEM_QUADORBINAUT: - return (tiny ? "K_ISORBN" : "K_ITORB4"); - default: - return (tiny ? "K_ISSAD" : "K_ITSAD"); - } -} - -static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) -{ - patch_t **kp[1 + NUMKARTITEMS] = { - kp_sadface, - NULL, - kp_sneaker, - kp_rocketsneaker, - kp_invincibility, - kp_banana, - kp_eggman, - kp_orbinaut, - kp_jawz, - kp_mine, - kp_ballhog, - kp_selfpropelledbomb, - kp_grow, - kp_shrink, - kp_thundershield, - kp_hyudoro, - kp_pogospring, - kp_kitchensink, - kp_superring, - kp_landmine, - kp_bubbleshield, - kp_flameshield, - }; - - if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) - return kp[item - KITEM_SAD][offset]; - else + if (type == KITEM_SAD) + return tiny ? kp_sadface[1] : kp_sadface[0]; + else if (type <= KITEM_NONE || type >= numkartitems) return NULL; + + kartitem_t *item = &kartitems[type]; + kartitemgraphics_t *graphics = &item->graphics[tiny ? 1 : 0]; + + if (item->animated) + return graphics->patches[(leveltime % (graphics->numpatches*3)) / 3]; + else + return graphics->patches[CLAMP(amount - 1, 0, graphics->numpatches - 1)]; } //} @@ -1330,10 +1173,9 @@ static void K_drawKartItem(void) // Why write V_DrawScaledPatch calls over and over when they're all the same? // Set to 'no item' just in case. - const UINT8 offset = ((r_splitscreen > 1) ? 1 : 0); + const boolean tiny = r_splitscreen > 1; patch_t *localpatch = kp_nodraw; patch_t *localbg; - patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]); boolean dark = false; INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... INT32 numberdisplaymin = 2; @@ -1351,26 +1193,7 @@ static void K_drawKartItem(void) if (K_GetHudColor()) localcolor = K_GetHudColor(); - switch (item) - { - case KITEM_SNEAKER: - localpatch = kp_sneaker[offset ? 3: 0]; - break; - case KITEM_BANANA: - localpatch = kp_banana[offset ? 4: 0]; - break; - case KITEM_ORBINAUT: - localpatch = kp_orbinaut[3+offset]; - break; - case KITEM_JAWZ: - localpatch = kp_jawz[offset ? 2: 0]; - break; - case KITEM_INVINCIBILITY: - localpatch = localinv; - break; - default: - localpatch = K_GetCachedItemPatch(item, offset); - } + localpatch = K_GetCachedItemPatch(item, tiny, 0); } else { @@ -1381,18 +1204,18 @@ static void K_drawKartItem(void) if (stplyr->stolentimer > 0) { if (leveltime & 2) - localpatch = kp_hyudoro[offset]; + localpatch = K_GetCachedItemPatch(KITEM_HYUDORO, tiny, 0); else localpatch = kp_nodraw; } else if ((stplyr->stealingtimer > 0) && (leveltime & 2)) { - localpatch = kp_hyudoro[offset]; + localpatch = K_GetCachedItemPatch(KITEM_HYUDORO, tiny, 0); } else if (stplyr->eggmanexplode > 1) { if (leveltime & 1) - localpatch = kp_eggman[offset]; + localpatch = K_GetCachedItemPatch(KITEM_EGGMAN, tiny, 0); else localpatch = kp_nodraw; } @@ -1401,7 +1224,7 @@ static void K_drawKartItem(void) itembar = FixedDiv(stplyr->rocketsneakertimer, itemtime*3); if (leveltime & 1) - localpatch = kp_rocketsneaker[offset]; + localpatch = K_GetCachedItemPatch(KITEM_ROCKETSNEAKER, tiny, 0); else localpatch = kp_nodraw; } @@ -1409,7 +1232,6 @@ static void K_drawKartItem(void) { itembar = FixedDiv(stplyr->flametimer, itemtime*3); flamebar = FixedDiv(stplyr->flamestore, FLAMESTOREMAX); - localbg = kp_itembg[offset+1]; dark = true; if ((stplyr->flamestore >= FLAMESTOREMAX-1) && (leveltime & 1)) @@ -1419,7 +1241,7 @@ static void K_drawKartItem(void) } if (leveltime & 1) - localpatch = kp_flameshield[offset]; + localpatch = K_GetCachedItemPatch(KITEM_FLAMESHIELD, tiny, 0); else localpatch = kp_nodraw; } @@ -1429,7 +1251,7 @@ static void K_drawKartItem(void) itembar = FixedDiv(stplyr->growcancel, 26); if (leveltime & 1) - localpatch = kp_grow[offset]; + localpatch = K_GetCachedItemPatch(KITEM_GROW, tiny, 0); else localpatch = kp_nodraw; } @@ -1441,13 +1263,13 @@ static void K_drawKartItem(void) flamebar = FixedDiv(stplyr->invincibilitycancel, 26); if (leveltime & 1) - localpatch = localinv; + localpatch = K_GetCachedItemPatch(KITEM_INVINCIBILITY, tiny, 0); else localpatch = kp_nodraw; } else if (K_GetShieldFromPlayer(stplyr) == KSHIELD_BUBBLE) { - localpatch = kp_bubbleshield[offset]; + localpatch = K_GetCachedItemPatch(KITEM_BUBBLESHIELD, tiny, 0); dark = true; if (stplyr->bubbleblowup > 0 && leveltime & 1) @@ -1461,7 +1283,7 @@ static void K_drawKartItem(void) else if (stplyr->sadtimer > 0) { if (leveltime & 2) - localpatch = kp_sadface[offset]; + localpatch = kp_sadface[tiny ? 1 : 0]; else localpatch = kp_nodraw; } @@ -1470,43 +1292,15 @@ static void K_drawKartItem(void) if (stplyr->itemamount <= 0) return; - switch(stplyr->itemtype) + localpatch = K_GetCachedItemPatch(stplyr->itemtype, tiny, stplyr->itemamount); + if (localpatch == NULL) + localpatch = kp_nodraw; // diagnose underflows + + if (stplyr->itemtype > 0 && stplyr->itemtype < numkartitems) { - case KITEM_SNEAKER: - localpatch = kp_sneaker[(offset ? 3 : min(stplyr->itemamount-1, 2))]; - numberdisplaymin = 4; - numberdisplaymin = offset ? 2 : 4; - break; - case KITEM_INVINCIBILITY: - localpatch = localinv; - localbg = kp_itembg[offset+1]; - break; - case KITEM_BANANA: - localpatch = kp_banana[(offset ? 4 : min(stplyr->itemamount-1, 3))]; - numberdisplaymin = offset ? 2 : 5; - break; - case KITEM_ORBINAUT: - localpatch = kp_orbinaut[(offset ? 4 : min(stplyr->itemamount-1, 3))]; - numberdisplaymin = offset ? 2 : 5; - break; - case KITEM_JAWZ: - localpatch = kp_jawz[(offset ? 2 : min(stplyr->itemamount-1, 1))]; - numberdisplaymin = 3; - numberdisplaymin = offset ? 2 : 3; - break; - case KITEM_SPB: - case KITEM_THUNDERSHIELD: - case KITEM_BUBBLESHIELD: - case KITEM_FLAMESHIELD: - dark = true; - /*FALLTHRU*/ - - default: - localpatch = K_GetCachedItemPatch(stplyr->itemtype, offset); - - if (localpatch == NULL) - localpatch = kp_nodraw; // diagnose underflows - break; + kartitem_t *item = &kartitems[stplyr->itemtype]; + numberdisplaymin = item->animated ? 2 : item->graphics[tiny ? 1 : 0].numpatches + 1; + dark = item->darkbg; } if ((stplyr->itemflags & IF_ITEMOUT) && !(leveltime & 1)) @@ -1532,7 +1326,7 @@ static void K_drawKartItem(void) } } - localbg = K_getItemBoxPatch((boolean)offset, dark); + localbg = K_getItemBoxPatch(tiny, dark); drawinfo_t info; K_getItemBoxDrawinfo(&info); fx = info.x; @@ -1554,7 +1348,7 @@ static void K_drawKartItem(void) if (cv_fancyroulette.value && stplyr->itemroulette && !stplyr->deadtimer) { fixed_t frac = R_GetTimeFrac(RTF_LEVEL); - UINT8 fancystep = (offset ? 6 : 10); + UINT8 fancystep = tiny ? 6 : 10; fixed_t fancyoffset = (stplyr->itemroulette % 3)-1; if (fancyoffset != 0) @@ -1569,10 +1363,10 @@ static void K_drawKartItem(void) // Then, the numbers: if (stplyr->itemamount >= numberdisplaymin && !stplyr->itemroulette) { - localbg = K_getItemMulPatch((boolean)offset); + localbg = K_getItemMulPatch(tiny); V_DrawMappedPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), localbg, colormap); // flip this graphic for p2 and p4 in split and shift it. V_DrawFixedPatch(fx<itemamount)); else @@ -1598,7 +1392,7 @@ static void K_drawKartItem(void) // Quick Eggman numbers if (stplyr->eggmanexplode > 1) - V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->eggmanexplode))]); + V_DrawScaledPatch(fx+17, fy+13-(tiny ? 1 : 0), V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->eggmanexplode))]); } void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode) @@ -5758,48 +5552,12 @@ K_drawMiniPing (void) } } -static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item) -{ - UINT8 offset; - - item = K_ItemResultToType(item); - - switch (item) - { - case KITEM_INVINCIBILITY: - offset = 7; - break; - - case KITEM_BANANA: - offset = 4; - break; - - case KITEM_ORBINAUT: - offset = 4; - break; - - case KITEM_JAWZ: - offset = 2; - break; - - case KITEM_SNEAKER: - offset = 3; - break; - - default: - offset = 1; - } - - return K_GetCachedItemPatch(item, offset); -} - static void K_drawDistributionDebugger(void) { UINT8 useodds = 0; UINT8 pingame = 0, bestbumper = 0; UINT32 pdis = 0; INT32 i; - INT32 item; INT32 x = -9, y = -9; //boolean dontforcespb = false; boolean spbrush = false; @@ -5825,21 +5583,19 @@ static void K_drawDistributionDebugger(void) if (pingame == 1) { - if (stplyr->itemroulette && (stplyr->cmd.buttons & BT_ATTACK) && cv_superring.value && (K_RingsActive() == true)) - V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetSmallStaticCachedItemPatch(KITEM_SUPERRING)); + if (stplyr->itemroulette && stplyr->cmd.buttons & BT_ATTACK && K_ItemResultEnabled("superring") && K_RingsActive()) + V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(KITEM_SUPERRING, true, 0)); else - V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetSmallStaticCachedItemPatch(KITEM_SNEAKER)); + V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(KITEM_SNEAKER, true, 0)); V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, va("%d", 200)); } else { - for (item = 1; item < NUMBASEKARTRESULTS; item++) + for (i = 0; i < numkartresults; i++) { - INT32 itemodds; - INT32 amount; - - itemodds = K_KartGetItemOdds( - useodds, item, + kartresult_t *result = &kartresults[i]; + INT32 itemodds = K_KartGetItemOdds( + useodds, result, stplyr->distancetofinish, stplyr->distancefromcluster, 0, @@ -5850,14 +5606,13 @@ static void K_drawDistributionDebugger(void) if (itemodds <= 0) continue; - V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetSmallStaticCachedItemPatch(item)); + V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(result->type, true, 0)); V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds)); // Display amount for multi-items - amount = K_ItemResultToAmount(item); - if (amount > 1) + if (result->amount > 1) { - V_DrawString(x+24, y+31, V_SPLITSCREEN|V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", amount)); + V_DrawString(x+24, y+31, V_SPLITSCREEN|V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", result->amount)); } diff --git a/src/k_hud.h b/src/k_hud.h index f330d950f..119dcffcc 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -95,11 +95,11 @@ void K_getRingsDrawinfo(drawinfo_t *out); void K_getMinimapDrawinfo(drawinfo_t *out); void K_getSlipstreamDrawinfo(drawinfo_t *out); void K_getLivesnStatsDrawinfo(drawinfo_t *out); -const char *K_GetItemPatch(UINT8 item, boolean tiny); void K_ReloadHUDColorCvar(void); boolean K_UseColorHud(void); UINT8 K_GetHudColor(void); void K_LoadKartHUDGraphics(void); +patch_t *K_GetCachedItemPatch(SINT8 type, boolean tiny, UINT8 amount); void K_drawKartHUD(void); void K_drawKartFreePlay(void); void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode); diff --git a/src/k_odds.c b/src/k_items.c similarity index 56% rename from src/k_odds.c rename to src/k_items.c index 9fe28fcbc..679ebb273 100644 --- a/src/k_odds.c +++ b/src/k_items.c @@ -8,8 +8,8 @@ // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file k_odds.cpp -/// \brief Kart item odds systems. +/// \file k_items.c +/// \brief Kart item systems. // SRB2kart Roulette Code - Position Based @@ -33,41 +33,22 @@ #include "k_bot.h" #include "k_kart.h" #include "k_waypoint.h" -#include "k_altshrink.h" #include "k_cluster.hpp" -#include "k_odds.h" +#include "k_items.h" +#include "z_zone.h" -consvar_t *KartItemCVars[NUMKARTRESULTS-1] = -{ - &cv_sneaker, - &cv_rocketsneaker, - &cv_invincibility, - &cv_banana, - &cv_eggmanmonitor, - &cv_orbinaut, - &cv_jawz, - &cv_mine, - &cv_ballhog, - &cv_selfpropelledbomb, - &cv_grow, - &cv_shrink, - &cv_thundershield, - &cv_hyudoro, - &cv_pogospring, - &cv_kitchensink, - &cv_superring, - &cv_landmine, // FIXME: Kill it. - &cv_bubbleshield, - &cv_flameshield, - &cv_dualsneaker, - &cv_triplesneaker, - &cv_triplebanana, - &cv_decabanana, - &cv_tripleorbinaut, - &cv_quadorbinaut, - &cv_dualjawz -}; +kartitem_t kartitems[MAXKARTITEMS] = {0}; +kartresult_t kartresults[MAXKARTRESULTS] = {0}; +UINT8 numkartitems = 0; +UINT8 numkartresults = 0; + +static CV_PossibleValue_t kartdebugitem_cons_t[MAXKARTITEMS] = {0}; +consvar_t cv_kartdebugitem = CVAR_INIT ("kartdebugitem", "NONE", CV_NETVAR|CV_CHEAT, kartdebugitem_cons_t, NULL); +static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}}; +consvar_t cv_kartdebugamount = CVAR_INIT ("kartdebugamount", "1", CV_NETVAR|CV_CHEAT, kartdebugamount_cons_t, NULL); + +static CV_PossibleValue_t kartitemtype_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; #define NUMKARTODDS (MAXODDS*10) @@ -85,211 +66,354 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = #define REALMAXBATTLEPROB 10 #define MAXBATTLEPROBABILITY (REALMAXBATTLEPROB * BATTLEODDSMUL) -// Less ugly 2D arrays -// Expanded 16-tier useodds, for more flexible calculations. -// Item odds are now based around the max number being 75 as well. -static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = -{ - //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - //B C D E F G H I J K L M N O P Q - { 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Sneaker - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37}, // Rocket Sneaker - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 24, 50, 75, 75}, // Invincibility - {35, 28, 22, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Banana - {18, 15, 12, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Eggman Monitor - {45, 28, 24, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut - { 0, 11, 22, 9, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Jawz - { 0, 0, 5, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Mine - { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Ballhog - { 0, 0, 2, 3, 4, 5, 6, 9, 10, 12, 8, 5, 2, 2, 0, 0}, // Self-Propelled Bomb - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 12, 9, 0, 0, 0}, // Grow - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 3, 0, 0}, // Shrink - {12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Thunder Shield - { 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 0, 0, 0, 0, 0, 0}, // Hyudoro - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Pogo Spring - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Kitchen Sink - { 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Super Ring - { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Land Mine - { 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Bubble Shield - { 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0}, // Flame Shield - { 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0}, // Sneaker x2 - { 0, 0, 0, 0, 0, 0, 0, 7, 35, 45, 60, 56, 52, 4, 0, 0}, // Sneaker x3 - { 0, 7, 7, 7, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Banana x3 - { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Banana x10 - { 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x3 - { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x4 - { 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Jawz x2 - { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Egg Mine - { 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Alt. Shrink -}; +static UINT8 oddstablelen[MAXODDSTABLES] = { MAXODDS, 2, 4 }; -static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = +static void K_RegisterItem(const char *name, boolean hasalt) { - //R S - { 2, 1 }, // Sneaker - { 0, 0 }, // Rocket Sneaker - { 4, 1 }, // Invincibility - { 0, 0 }, // Banana - { 1, 0 }, // Eggman Monitor - { 8, 0 }, // Orbinaut - { 8, 1 }, // Jawz - { 6, 1 }, // Mine - { 2, 1 }, // Ballhog - { 0, 0 }, // Self-Propelled Bomb - { 2, 1 }, // Grow - { 0, 0 }, // Shrink - { 4, 0 }, // Thunder Shield - { 2, 0 }, // Hyudoro - { 3, 0 }, // Pogo Spring - { 0, 0 }, // Kitchen Sink - { 0, 0 }, // Super Ring - { 2, 0 }, // Land Mine - { 1, 0 }, // Bubble Shield - { 1, 0 }, // Flame Shield - { 0, 0 }, // Sneaker x2 - { 0, 1 }, // Sneaker x3 - { 0, 0 }, // Banana x3 - { 1, 1 }, // Banana x10 - { 2, 0 }, // Orbinaut x3 - { 1, 1 }, // Orbinaut x4 - { 5, 1 }, // Jawz x2 - { 2, 0 }, // Egg Mine - { 1, 0 } // Alt. Shrink -}; + kartitem_t *item = &kartitems[numkartitems++]; -/* -static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = + item->name = name; + + if (hasalt) + { + char *cvname = malloc(strlen(name)+1 + 8); + sprintf(cvname, "altitem_%s", name); + strlwr(cvname); + + consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &item->altcvar); + var->name = cvname; + var->defaultvalue = "Legacy"; + var->flags = CV_NETVAR/*|CV_CALL*/; + var->PossibleValue = kartitemtype_cons_t; + var->func = NULL; + CV_RegisterVar(var); + } + else + { + item->altcvar = NULL; + } + + kartdebugitem_cons_t[numkartitems - 1].strvalue = name; + kartdebugitem_cons_t[numkartitems - 1].value = numkartitems - 1; + kartdebugitem_cons_t[numkartitems].strvalue = NULL; + kartdebugitem_cons_t[numkartitems].value = 0; +} + +static void K_RegisterResult(const char *name, kartitemtype_e type, UINT8 amount, UINT8 flags) { - //M N O P - { 1, 1, 0, 0 }, // Sneaker - { 0, 0, 0, 0 }, // Rocket Sneaker - { 0, 0, 0, 0 }, // Invincibility - { 0, 0, 0, 0 }, // Banana - { 0, 0, 0, 0 }, // Eggman Monitor - { 1, 1, 0, 0 }, // Orbinaut - { 1, 1, 0, 0 }, // Jawz - { 0, 0, 0, 0 }, // Mine - { 0, 0, 0, 0 }, // Ballhog - { 0, 0, 0, 1 }, // Self-Propelled Bomb - { 0, 0, 0, 0 }, // Grow - { 0, 0, 0, 0 }, // Shrink - { 0, 0, 0, 0 }, // Thunder Shield - { 0, 0, 0, 0 }, // Hyudoro - { 0, 0, 0, 0 }, // Pogo Spring - { 0, 0, 0, 0 }, // Kitchen Sink - { 0, 0, 0, 0 }, // Super Ring - { 0, 0, 0, 0 }, // Land Mine - { 0, 0, 0, 0 }, // Bubble Shield - { 0, 0, 0, 0 }, // Flame Shield - { 0, 1, 1, 0 }, // Sneaker x2 - { 0, 0, 1, 1 }, // Sneaker x3 - { 0, 0, 0, 0 }, // Banana x3 - { 0, 0, 0, 0 }, // Banana x10 - { 0, 1, 1, 0 }, // Orbinaut x3 - { 0, 0, 1, 1 }, // Orbinaut x4 - { 0, 0, 1, 1 }, // Jawz x2 - { 0, 0, 0, 0 }, // Egg Mine - { 0, 0, 0, 0 } // Alt. Shrink -}; -*/ + kartresult_t *result = &kartresults[numkartresults++]; + result->name = name; + result->type = type; + result->amount = amount; + result->flags = flags; + + consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &result->cvar); + var->name = name; + var->defaultvalue = "On"; + var->flags = CV_NETVAR|CV_CHEAT; + var->PossibleValue = CV_OnOff; + var->func = NULL; + CV_RegisterVar(var); +} + +kartresult_t *K_GetKartResult(const char *name) +{ + for (UINT8 i = 0; i < numkartresults; i++) + { + if (!strcmp(name, kartresults[i].name)) + return &kartresults[i]; + } + + return NULL; +} + +void K_InitializeItems(void) +{ + numkartitems = 0; + K_RegisterItem("NONE", false); + K_RegisterItem("SNEAKER", false); + K_RegisterItem("ROCKETSNEAKER", false); + K_RegisterItem("INVINCIBILITY", true); + K_RegisterItem("BANANA", false); + K_RegisterItem("EGGMAN", true); + K_RegisterItem("ORBINAUT", false); + K_RegisterItem("JAWZ", false); + K_RegisterItem("MINE", false); + K_RegisterItem("BALLHOG", false); + K_RegisterItem("SPB", false); + K_RegisterItem("GROW", false); + K_RegisterItem("SHRINK", true); + K_RegisterItem("THUNDERSHIELD", false); + K_RegisterItem("HYUDORO", false); + K_RegisterItem("POGOSPRING", false); + K_RegisterItem("KITCHENSINK", false); + K_RegisterItem("SUPERRING", false); + K_RegisterItem("LANDMINE", false); + K_RegisterItem("BUBBLESHIELD", false); + K_RegisterItem("FLAMESHIELD", false); + +#define A(item, amount, samount) \ + kartitems[item].graphics[0].numpatches = amount; \ + kartitems[item].graphics[1].numpatches = samount; \ + Z_Malloc(sizeof(const char *) * amount, PU_STATIC, &kartitems[item].graphics[0].patchnames); \ + Z_Malloc(sizeof(const char *) * samount, PU_STATIC, &kartitems[item].graphics[1].patchnames); \ + Z_Calloc(sizeof(patch_t *) * amount, PU_STATIC, &kartitems[item].graphics[0].patches); \ + Z_Calloc(sizeof(patch_t *) * samount, PU_STATIC, &kartitems[item].graphics[1].patches) + + A(KITEM_SNEAKER, 3, 1); + kartitems[KITEM_SNEAKER].graphics[0].patchnames[0] = "K_ITSHOE"; + kartitems[KITEM_SNEAKER].graphics[0].patchnames[1] = "K_ITSHO2"; + kartitems[KITEM_SNEAKER].graphics[0].patchnames[2] = "K_ITSHO3"; + kartitems[KITEM_SNEAKER].graphics[1].patchnames[0] = "K_ISSHOE"; + + A(KITEM_INVINCIBILITY, 7, 6); + kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[0] = "K_ITINV1"; + kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[1] = "K_ITINV2"; + kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[2] = "K_ITINV3"; + kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[3] = "K_ITINV4"; + kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[4] = "K_ITINV5"; + kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[5] = "K_ITINV6"; + kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[6] = "K_ITINV7"; + kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[0] = "K_ISINV1"; + kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[1] = "K_ISINV2"; + kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[2] = "K_ISINV3"; + kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[3] = "K_ISINV4"; + kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[4] = "K_ISINV5"; + kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[5] = "K_ISINV6"; + + A(KITEM_BANANA, 4, 1); + kartitems[KITEM_BANANA].graphics[0].patchnames[0] = "K_ITBANA"; + kartitems[KITEM_BANANA].graphics[0].patchnames[1] = "K_ITBAN2"; + kartitems[KITEM_BANANA].graphics[0].patchnames[2] = "K_ITBAN3"; + kartitems[KITEM_BANANA].graphics[0].patchnames[3] = "K_ITBAN4"; + kartitems[KITEM_BANANA].graphics[1].patchnames[0] = "K_ISBANA"; + + A(KITEM_ORBINAUT, 4, 1); + kartitems[KITEM_ORBINAUT].graphics[0].patchnames[0] = "K_ITORB1"; + kartitems[KITEM_ORBINAUT].graphics[0].patchnames[1] = "K_ITORB2"; + kartitems[KITEM_ORBINAUT].graphics[0].patchnames[2] = "K_ITORB3"; + kartitems[KITEM_ORBINAUT].graphics[0].patchnames[3] = "K_ITORB4"; + kartitems[KITEM_ORBINAUT].graphics[1].patchnames[0] = "K_ISORBN"; + + A(KITEM_JAWZ, 2, 1); + kartitems[KITEM_JAWZ].graphics[0].patchnames[0] = "K_ITJAWZ"; + kartitems[KITEM_JAWZ].graphics[0].patchnames[1] = "K_ITJAW2"; + kartitems[KITEM_JAWZ].graphics[1].patchnames[0] = "K_ISJAWZ"; +#undef A + +#define ONE(item, big, small) \ + kartitems[item].graphics[0].numpatches = 1; \ + kartitems[item].graphics[1].numpatches = 1; \ + Z_Malloc(sizeof(const char *), PU_STATIC, &kartitems[item].graphics[0].patchnames); \ + Z_Malloc(sizeof(const char *), PU_STATIC, &kartitems[item].graphics[1].patchnames); \ + Z_Calloc(sizeof(patch_t *), PU_STATIC, &kartitems[item].graphics[0].patches); \ + Z_Calloc(sizeof(patch_t *), PU_STATIC, &kartitems[item].graphics[1].patches); \ + kartitems[item].graphics[0].patchnames[0] = big; \ + kartitems[item].graphics[1].patchnames[0] = small + + ONE(KITEM_ROCKETSNEAKER, "K_ITRSHE", "K_ISRSHE"); + ONE(KITEM_EGGMAN, "K_ITEGGM", "K_ISEGGM"); + ONE(KITEM_MINE, "K_ITMINE", "K_ISMINE"); + ONE(KITEM_BALLHOG, "K_ITBHOG", "K_ISBHOG"); + ONE(KITEM_SPB, "K_ITSPB", "K_ISSPB"); + ONE(KITEM_GROW, "K_ITGROW", "K_ISGROW"); + ONE(KITEM_SHRINK, "K_ITSHRK", "K_ISSHRK"); + ONE(KITEM_THUNDERSHIELD, "K_ITTHNS", "K_ISTHNS"); + ONE(KITEM_HYUDORO, "K_ITHYUD", "K_ISHYUD"); + ONE(KITEM_POGOSPRING, "K_ITPOGO", "K_ISPOGO"); + ONE(KITEM_KITCHENSINK, "K_ITSINK", "K_ISSINK"); + ONE(KITEM_SUPERRING, "K_ITRING", "K_ISRING"); + ONE(KITEM_LANDMINE, "K_ITLNDM", "K_ISLNDM"); + ONE(KITEM_BUBBLESHIELD, "K_ITBUBS", "K_ISBUBS"); + ONE(KITEM_FLAMESHIELD, "K_ITFLMS", "K_ISFLMS"); +#undef ONE + + kartitems[KITEM_INVINCIBILITY].animated = true; + + kartitems[KITEM_INVINCIBILITY].darkbg = true; + kartitems[KITEM_SPB].darkbg = true; + kartitems[KITEM_THUNDERSHIELD].darkbg = true; + kartitems[KITEM_BUBBLESHIELD].darkbg = true; + kartitems[KITEM_FLAMESHIELD].darkbg = true; + + numkartresults = 0; + K_RegisterResult("sneaker", KITEM_SNEAKER, 1, 0); + K_RegisterResult("rocketsneaker", KITEM_ROCKETSNEAKER, 1, KRF_POWERITEM); + K_RegisterResult("invincibility", KITEM_INVINCIBILITY, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); + K_RegisterResult("banana", KITEM_BANANA, 1, KRF_NOTNEAREND|KRF_NOTFORBOTTOM); + K_RegisterResult("eggman", KITEM_EGGMAN, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND|KRF_NOTFORBOTTOM); + K_RegisterResult("orbinaut", KITEM_ORBINAUT, 1, 0); + K_RegisterResult("jawz", KITEM_JAWZ, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); + K_RegisterResult("mine", KITEM_MINE, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); + K_RegisterResult("ballhog", KITEM_BALLHOG, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); + K_RegisterResult("spb", KITEM_SPB, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_NOTNEAREND); + K_RegisterResult("grow", KITEM_GROW, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); + K_RegisterResult("shrink", KITEM_SHRINK, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND); + K_RegisterResult("thundershield", KITEM_THUNDERSHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); + K_RegisterResult("hyudoro", KITEM_HYUDORO, 1, KRF_COOLDOWNONSTART); + K_RegisterResult("pogospring", KITEM_POGOSPRING, 1, 0); + K_RegisterResult("kitchensink", KITEM_KITCHENSINK, 1, 0); + K_RegisterResult("superring", KITEM_SUPERRING, 1, KRF_NOTNEAREND|KRF_NOTFORBOTTOM); + K_RegisterResult("landmine", KITEM_LANDMINE, 1, 0); + K_RegisterResult("bubbleshield", KITEM_BUBBLESHIELD, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); + K_RegisterResult("flameshield", KITEM_FLAMESHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); + + K_RegisterResult("dualsneaker", KITEM_SNEAKER, 2, 0); + K_RegisterResult("triplesneaker", KITEM_SNEAKER, 3, KRF_POWERITEM); + K_RegisterResult("triplebanana", KITEM_BANANA, 3, KRF_POWERITEM|KRF_NOTFORBOTTOM|KRF_NOTNEAREND); + K_RegisterResult("decabanana", KITEM_BANANA, 10, KRF_POWERITEM|KRF_NOTFORBOTTOM|KRF_NOTNEAREND); + K_RegisterResult("tripleorbinaut", KITEM_ORBINAUT, 3, KRF_NOTFORBOTTOM|KRF_POWERITEM); + K_RegisterResult("quadorbinaut", KITEM_ORBINAUT, 4, KRF_NOTFORBOTTOM|KRF_POWERITEM); + K_RegisterResult("dualjawz", KITEM_JAWZ, 2, KRF_NOTFORBOTTOM|KRF_POWERITEM); + +#define O(item, ...) memcpy(K_GetKartResult(#item)->odds[WHICH], (UINT8[MAXODDS]){__VA_ARGS__}, MAXODDS); +#define WHICH ODDS_RACE + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + O(sneaker, 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Sneaker + O(rocketsneaker, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37) // Rocket Sneaker + O(invincibility, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 24, 50, 75, 75) // Invincibility + O(banana, 35, 28, 22, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana + O(eggman, 18, 15, 12, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Eggman Monitor + O(orbinaut, 45, 28, 24, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut + O(jawz, 0, 11, 22, 9, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz + O(mine, 0, 0, 5, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Mine + O(ballhog, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Ballhog + O(spb, 0, 0, 2, 3, 4, 5, 6, 9, 10, 12, 8, 5, 2, 2, 0, 0) // Self-Propelled Bomb + O(grow, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 12, 9, 0, 0, 0) // Grow + O(shrink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 3, 0, 0) // Shrink + O(thundershield, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Thunder Shield + O(hyudoro, 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 0, 0, 0, 0, 0, 0) // Hyudoro + O(pogospring, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Pogo Spring + O(kitchensink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Kitchen Sink + O(superring, 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Super Ring + O(landmine, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Land Mine + O(bubbleshield, 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Bubble Shield + O(flameshield, 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0) // Flame Shield + O(dualsneaker, 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0) // Sneaker x2 + O(triplesneaker, 0, 0, 0, 0, 0, 0, 0, 7, 35, 45, 60, 56, 52, 4, 0, 0) // Sneaker x3 + O(triplebanana, 0, 7, 7, 7, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x3 + O(decabanana, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x10 + O(tripleorbinaut, 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x3 + O(quadorbinaut, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x4 + O(dualjawz, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz x2 + //{ 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Egg Mine + //{ 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Alt. Shrink +#undef WHICH + +#define WHICH ODDS_BATTLE + // R S + O(sneaker, 2, 1) // Sneaker + O(rocketsneaker, 0, 0) // Rocket Sneaker + O(invincibility, 4, 1) // Invincibility + O(banana, 0, 0) // Banana + O(eggman, 1, 0) // Eggman Monitor + O(orbinaut, 8, 0) // Orbinaut + O(jawz, 8, 1) // Jawz + O(mine, 6, 1) // Mine + O(ballhog, 2, 1) // Ballhog + O(spb, 0, 0) // Self-Propelled Bomb + O(grow, 2, 1) // Grow + O(shrink, 0, 0) // Shrink + O(thundershield, 4, 0) // Thunder Shield + O(hyudoro, 2, 0) // Hyudoro + O(pogospring, 3, 0) // Pogo Spring + O(kitchensink, 0, 0) // Kitchen Sink + O(superring, 0, 0) // Super Ring + O(landmine, 2, 0) // Land Mine + O(bubbleshield, 1, 0) // Bubble Shield + O(flameshield, 1, 0) // Flame Shield + O(dualsneaker, 0, 0) // Sneaker x2 + O(triplesneaker, 0, 1) // Sneaker x3 + O(triplebanana, 0, 0) // Banana x3 + O(decabanana, 1, 1) // Banana x10 + O(tripleorbinaut, 2, 0) // Orbinaut x3 + O(quadorbinaut, 1, 1) // Orbinaut x4 + O(dualjawz, 5, 1) // Jawz x2 + //{ 2, 0 }, // Egg Mine + //{ 1, 0 } // Alt. Shrink +#undef WHICH + +#define WHICH ODDS_SPECIAL + // M N O P + O(sneaker, 1, 1, 0, 0) // Sneaker + O(rocketsneaker, 0, 0, 0, 0) // Rocket Sneaker + O(invincibility, 0, 0, 0, 0) // Invincibility + O(banana, 0, 0, 0, 0) // Banana + O(eggman, 0, 0, 0, 0) // Eggman Monitor + O(orbinaut, 1, 1, 0, 0) // Orbinaut + O(jawz, 1, 1, 0, 0) // Jawz + O(mine, 0, 0, 0, 0) // Mine + O(ballhog, 0, 0, 0, 0) // Ballhog + O(spb, 0, 0, 0, 1) // Self-Propelled Bomb + O(grow, 0, 0, 0, 0) // Grow + O(shrink, 0, 0, 0, 0) // Shrink + O(thundershield, 0, 0, 0, 0) // Thunder Shield + O(hyudoro, 0, 0, 0, 0) // Hyudoro + O(pogospring, 0, 0, 0, 0) // Pogo Spring + O(kitchensink, 0, 0, 0, 0) // Kitchen Sink + O(superring, 0, 0, 0, 0) // Super Ring + O(landmine, 0, 0, 0, 0) // Land Mine + O(bubbleshield, 0, 0, 0, 0) // Bubble Shield + O(flameshield, 0, 0, 0, 0) // Flame Shield + O(dualsneaker, 0, 1, 1, 0) // Sneaker x2 + O(triplesneaker, 0, 0, 1, 1) // Sneaker x3 + O(triplebanana, 0, 0, 0, 0) // Banana x3 + O(decabanana, 0, 0, 0, 0) // Banana x10 + O(tripleorbinaut, 0, 1, 1, 0) // Orbinaut x3 + O(quadorbinaut, 0, 0, 1, 1) // Orbinaut x4 + O(dualjawz, 0, 0, 1, 1) // Jawz x2 + //{ 0, 0, 0, 0 }, // Egg Mine + //{ 0, 0, 0, 0 } // Alt. Shrink +#undef WHICH +#undef O // Cooldown time table; contains both base (index 0) and current (index 1) // times. Base times are in seconds, current times are in tics. -tic_t ItemBGone[NUMKARTRESULTS][2] = -{ - //BASE CURR - { 0, 0 }, // Null/Sad Face - { 0, 0 }, // Sneaker - { 0, 0 }, // Rocket Sneaker - { 0, 0 }, // Invincibility - { 0, 0 }, // Banana - { 10, 0 }, // Eggman Monitor - { 0, 0 }, // Orbinaut - { 5, 0 }, // Jawz - { 0, 0 }, // Mine - { 10, 0 }, // Ballhog - { 20, 0 }, // Self-Propelled Bomb - { 3, 0 }, // Grow - { 20, 0 }, // Shrink - { 0, 0 }, // Thunder Shield - { 20, 0 }, // Hyudoro - { 0, 0 }, // Pogo Spring - { 0, 0 }, // Kitchen Sink - { 0, 0 }, // Super Ring - { 0, 0 }, // Land Mine - { 5, 0 }, // Bubble Shield - { 8, 0 }, // Flame Shield - { 0, 0 }, // Sneaker x2 - { 0, 0 }, // Sneaker x3 - { 0, 0 }, // Banana x3 - { 30, 0 }, // Banana x10 - { 10, 0 }, // Orbinaut x3 - { 20, 0 }, // Orbinaut x4 - { 10, 0 }, // Jawz x2 - { 0, 0 }, // Egg Mine - { 5, 0 } // Alt. Shrink -}; +#define O(item, v) K_GetKartResult(#item)->basebgone = v; + O(sneaker, 0) // Sneaker + O(rocketsneaker, 0) // Rocket Sneaker + O(invincibility, 0) // Invincibility + O(banana, 0) // Banana + O(eggman, 10) // Eggman Monitor + O(orbinaut, 0) // Orbinaut + O(jawz, 5) // Jawz + O(mine, 0) // Mine + O(ballhog, 10) // Ballhog + O(spb, 20) // Self-Propelled Bomb + O(grow, 3) // Grow + O(shrink, 20) // Shrink + O(thundershield, 0) // Thunder Shield + O(hyudoro, 20) // Hyudoro + O(pogospring, 0) // Pogo Spring + O(kitchensink, 0) // Kitchen Sink + O(superring, 0) // Super Ring + O(landmine, 0) // Land Mine + O(bubbleshield, 5) // Bubble Shield + O(flameshield, 8) // Flame Shield + O(dualsneaker, 0) // Sneaker x2 + O(triplesneaker, 0) // Sneaker x3 + O(triplebanana, 0) // Banana x3 + O(decabanana, 30) // Banana x10 + O(tripleorbinaut, 10) // Orbinaut x3 + O(quadorbinaut, 20) // Orbinaut x4 + O(dualjawz, 10) // Jawz x2 + //O(, 0) // Egg Mine + //O(, 5) // Alt. Shrink +#undef O -// TODO: Vectorize all item tables and shove them into gamemode-uniqe pools (k_oddstable.cpp?). -// Basically necessary for any hopes of easy SOC support in the near future. - -static SINT8 K_GetAlternateVersion(SINT8 item) -{ - if (K_IsKartItemAlternate(item)) - { - switch(item) - { - /*case KITEM_INVINCIBILITY: - return KITEM_INVINCIBILITY;*/ - - case KITEM_EGGMAN: - return KAITEM_EGGMINE; - - case KITEM_SHRINK: - return KAITEM_ALTERNSHRINK; - - default: - break; - } - } - - return item; // Return ourselves; we don't have an alternative. + CV_RegisterVar(&cv_kartdebugitem); + CV_RegisterVar(&cv_kartdebugamount); } -/** \brief Sets the cooldown timer for an item. When active, the item is removed - * from all players' item pools for some time. - - \param item (SINT8) item to assign cooldown for - \param time (tic_t) cooldown time - - \return void -*/ -void K_SetBGone(SINT8 item, tic_t time) +void K_SetupItemOdds(void) { - ItemBGone[item][GONER_CURRCOOLDOWN] = time; + // TODO: something } -/** \brief Sets the cooldown timer for an item to its "base" (intended) time. - - \param item (SINT8) item to assign base cooldown for - - \return void -*/ -void K_SetBGoneToBase(SINT8 item) +boolean K_ItemResultEnabled(const char *name) { - ItemBGone[item][GONER_CURRCOOLDOWN] = (ItemBGone[item][GONER_BASECOOLDOWN] * TICRATE); -} - -/** \brief Gets the cooldown timer for an item. - - \param item (SINT8) item to retrieve cooldown for - \param base (bool) if true, returns the "base" (intended) cooldown instead - - \return (tic_t) cooldown time -*/ -tic_t K_GetBGone(SINT8 item, boolean base) -{ - return ItemBGone[item][base ? GONER_BASECOOLDOWN : GONER_CURRCOOLDOWN] * (base ? TICRATE : 1); + const kartresult_t *result = K_GetKartResult(name); + return result != NULL && result->cvar->value == 1; } /** \brief Are the odds in legacy distancing mode? @@ -362,86 +486,24 @@ static UINT32 K_IntDistanceForMap(fixed_t curx, destz / mapobjectscale); } -SINT8 K_ItemResultToType(SINT8 getitem) -{ - if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback) - { - if (getitem != 0) - { - CONS_Printf("ERROR: K_GetItemResultToItemType - Item roulette gave bad item (%d) :(\n", getitem); - } - - return KITEM_SAD; - } - - if (getitem >= NUMKARTITEMS) - { - switch (getitem) - { - case KRITEM_DUALSNEAKER: - case KRITEM_TRIPLESNEAKER: - return KITEM_SNEAKER; - - case KRITEM_TRIPLEBANANA: - case KRITEM_TENFOLDBANANA: - return KITEM_BANANA; - - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - return KITEM_ORBINAUT; - - case KRITEM_DUALJAWZ: - return KITEM_JAWZ; - - default: - I_Error("K_ItemResultToType: Bad item redirect for result %d\n", getitem); - break; - } - } - - return getitem; -} - -UINT8 K_ItemResultToAmount(SINT8 getitem) -{ - switch (getitem) - { - case KRITEM_DUALSNEAKER: - case KRITEM_DUALJAWZ: - return 2; - - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEBANANA: - case KRITEM_TRIPLEORBINAUT: - return 3; - - case KRITEM_QUADORBINAUT: - return 4; - - case KRITEM_TENFOLDBANANA: - return 10; - - default: - return 1; - } -} - /** \brief Prevent overpowered rolls while under the effects of Alt. Shrink. \param item the item to check \return Don't double for this item? */ -static boolean K_DontDoubleMyItems(SINT8 item) +static boolean K_DontDoubleMyItems(kartresult_t *result) { - return ((item == KITEM_BALLHOG) || (item == KRITEM_TENFOLDBANANA) - || (item == KRITEM_TRIPLEORBINAUT) - || (item == KRITEM_QUADORBINAUT) || (item == KRITEM_TRIPLESNEAKER) - || (item == KRITEM_DUALJAWZ) || (item == KITEM_SPB) - || (item == KITEM_INVINCIBILITY) || (item == KITEM_GROW) - || (item == KITEM_BUBBLESHIELD) || (item == KITEM_FLAMESHIELD) - || (item == KITEM_ROCKETSNEAKER) || (item == KITEM_SHRINK) - || (item == KITEM_HYUDORO)); + return result->type == KITEM_BALLHOG || result->type == KITEM_SPB + || result->type == KITEM_INVINCIBILITY || result->type == KITEM_GROW + || result->type == KITEM_BUBBLESHIELD || result->type == KITEM_FLAMESHIELD + || result->type == KITEM_ROCKETSNEAKER || result->type == KITEM_SHRINK + || result->type == KITEM_HYUDORO + || (result->type == KITEM_BANANA && result->amount >= 4) + || (result->type == KITEM_ORBINAUT && result->amount >= 3) + || (result->type == KITEM_SNEAKER && result->amount >= 3) + || (result->type == KITEM_JAWZ && result->amount >= 2) + || result->type >= KITEM_FIRSTFREESLOT; // TODO: excludes custom items for now } /** \brief Item Roulette for Kart @@ -451,31 +513,35 @@ static boolean K_DontDoubleMyItems(SINT8 item) \return void */ -static void K_KartGetItemResult(player_t *player, SINT8 getitem) +static void K_KartGetItemResult(player_t *player, const char *name) { - SINT8 trueitem = K_GetAlternateVersion(getitem); + kartresult_t *result = K_GetKartResult(name); + K_BotResetItemConfirm(player, true); - if (trueitem == KITEM_SPB || (trueitem == KITEM_SHRINK)) // Indirect items + if (result == NULL) + { + player->itemtype = KITEM_SAD; + player->itemamount = 1; + return; + } + + if (result->type == KITEM_SPB || result->type == KITEM_SHRINK) // Indirect items { indirectitemcooldown = 20*TICRATE; } - else if (K_GetBGone(trueitem, true) > 0) // Item cooldowns + else if (result->basebgone > 0) // Item cooldowns { - K_SetBGoneToBase(trueitem); + result->bgone = result->basebgone * TICRATE; } - K_BotResetItemConfirm(player, true); + player->itemtype = result->type; + UINT8 itemamount = result->amount; - player->itemtype = K_ItemResultToType(getitem); - UINT8 itemamount = K_ItemResultToAmount(getitem); - - if (K_IsAltShrunk(player) && (!K_DontDoubleMyItems(getitem))) + if (K_IsAltShrunk(player) && (!K_DontDoubleMyItems(result))) { itemamount *= 2; } - if (cv_kartdebugitem.value != KITEM_NONE && cv_kartdebugitem.value == player->itemtype && cv_kartdebugamount.value > 1) - itemamount = cv_kartdebugamount.value; player->itemamount = itemamount; } @@ -595,9 +661,9 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist) #undef FRAC_3h // Assigns general cooldowns to shield items. -void K_KartHandleShieldCooldown(SINT8 item) +void K_KartHandleShieldCooldown(kartitemtype_e item) { - INT32 i; + INT32 i, j; UINT8 pingame = 0, pexiting = 0; @@ -624,12 +690,17 @@ void K_KartHandleShieldCooldown(SINT8 item) if (((shieldtype == K_GetShieldFromItem(players[i].itemtype)) || (shieldtype == K_GetShieldFromPlayer(&players[i])))) { - // If this shield has a cooldown, force-apply the cooldown preeemptively for - // the entire time the shield is being held by a player. - if (K_GetBGone(item, true) > 0) + for (j = 0; j < numkartresults; j++) { - K_SetBGoneToBase(item); - break; + kartresult_t *result = &kartresults[j]; + + // If this shield has a cooldown, force-apply the cooldown preeemptively for + // the entire time the shield is being held by a player. + if (result->type == item && result->basebgone > 0) + { + result->bgone = result->basebgone * TICRATE; + break; + } } } } @@ -643,7 +714,7 @@ void K_KartHandleShieldCooldown(SINT8 item) */ INT32 K_KartGetItemOdds( - UINT8 pos, SINT8 item, + UINT8 pos, const kartresult_t *result, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, @@ -658,22 +729,11 @@ INT32 K_KartGetItemOdds( UINT32 firstDist = UINT32_MAX; UINT32 secondToFirst = UINT32_MAX; - boolean powerItem = false; - boolean cooldownOnStart = false; - boolean indirectItem = false; - boolean notNearEnd = false; - boolean notForBottom = false; + UINT8 flags = result->flags; INT32 shieldtype = KSHIELD_NONE; - I_Assert(item > KITEM_NONE); // too many off by one scenarioes. - I_Assert(KartItemCVars[NUMBASEKARTRESULTS-2] != NULL); // Make sure this exists - - // Alt. item definitions are literally only for odds tables and cooldowns. - // Don't let the actual item listing step out of that boundary. - item = CLAMP(item, 0, NUMBASEKARTRESULTS); - - if (!KartItemCVars[item-1]->value && !modeattacking) + if (!K_ItemResultEnabled(result->name) && !modeattacking) return 0; /* @@ -697,28 +757,27 @@ INT32 K_KartGetItemOdds( INT32 oddsmul = BASEODDSMUL; // Item type used for actual odds retrieval and cooldown assignments. - SINT8 useitem = K_GetAlternateVersion(item); + UINT8 oddstable; if (gametyperules & GTR_BATTLEODDS) - { - I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table - newodds = K_KartItemOddsBattle[useitem-1][pos]; - oddsmul = BATTLEODDSMUL; - } + oddstable = ODDS_BATTLE; else if (gametyperules & GTR_RACEODDS) - { - I_Assert(pos < MAXODDS); // Ditto - newodds = K_KartItemOddsRace[useitem-1][pos]; - } + oddstable = ODDS_RACE; else - { - newodds = 0; - } + oddstable = ODDS_SPECIAL; + + I_Assert(pos < oddstablelen[oddstable]); // DO NOT allow positions past the bounds of the table + + if (gametyperules & GTR_BATTLEODDS) + oddsmul = BATTLEODDSMUL; + + // TODO: braaap (make a separate table for the current level!) + newodds = result->odds[oddstable][pos]; // Blow up the odds with a multiplier. newodds *= oddsmul; - shieldtype = K_GetShieldFromItem(item); + shieldtype = K_GetShieldFromItem(result->type); for (i = 0; i < MAXPLAYERS; i++) { @@ -781,72 +840,36 @@ INT32 K_KartGetItemOdds( } } - switch (item) + switch (result->type) { - case KITEM_BANANA: - notNearEnd = true; - notForBottom = true; - break; case KITEM_EGGMAN: if (K_IsKartItemAlternate(KITEM_EGGMAN)) - { - notForBottom = true; - } - else - { - // It blows you up and is overall ridiculous. This was *overdue*. - cooldownOnStart = true; - powerItem = true; - - notNearEnd = true; - notForBottom = true; - } + flags = KRF_NOTFORBOTTOM; break; - case KITEM_SUPERRING: - notNearEnd = true; - notForBottom = true; + case KITEM_SUPERRING: if ((K_RingsActive() == false)) // No rings rolled if rings are turned off. { newodds = 0; } break; + case KITEM_LANDMINE: // au revoir newodds = 0; break; - case KITEM_ROCKETSNEAKER: - case KRITEM_TRIPLESNEAKER: - powerItem = true; - break; - case KITEM_BUBBLESHIELD: - // Experiment: Given the refactoring of the item, remove it from the cooldown pool. - // Also: a given. - case KITEM_BALLHOG: - case KITEM_JAWZ: - case KRITEM_DUALJAWZ: - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - notForBottom = true; - powerItem = true; - break; - case KRITEM_TRIPLEBANANA: - case KRITEM_TENFOLDBANANA: - powerItem = true; - notForBottom = true; - notNearEnd = true; - break; + case KITEM_INVINCIBILITY: if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (gametyperules & GTR_RACEODDS)) { // It's a power item, yes, but we don't want mashing to lessen // its chances, so we lie to the game's face. // Nonetheless, apply the start cooldown. - cooldownOnStart = true; + flags = KRF_COOLDOWNONSTART; // Also, PLEASE prevent shitty last lap bagging endings. - notNearEnd = true; + flags |= KRF_NOTNEAREND; // Unique odds for Invincibility. newodds = K_KartGetInvincibilityOdds(clusterDist); @@ -855,24 +878,14 @@ INT32 K_KartGetItemOdds( // finishes, remove the cooldown flag. if (newodds >= INVFORCEODDS) { - cooldownOnStart = false; + flags &= ~KRF_COOLDOWNONSTART; } - + newodds *= BASEODDSMUL; break; } - /*FALLTHRU*/ - case KITEM_MINE: - case KITEM_GROW: - case KITEM_FLAMESHIELD: - cooldownOnStart = true; - powerItem = true; - break; - case KITEM_SPB: - cooldownOnStart = true; - indirectItem = true; - notNearEnd = true; + case KITEM_SPB: if (!K_LegacyOddsMode() && firstDist < (UINT32)ENDDIST) // No SPB near the end of the race { newodds = 0; @@ -897,17 +910,11 @@ INT32 K_KartGetItemOdds( newodds *= multiplier; } break; + case KITEM_SHRINK: - cooldownOnStart = true; - powerItem = true; - - // "Why keep this for Alt. Shrink?" Bag. Ging. - // ESPECIALLY with double items. - notNearEnd = true; - if (!K_IsKartItemAlternate(KITEM_SHRINK)) { - indirectItem = true; + flags |= KRF_INDIRECTITEM; if (pingame-1 <= pexiting) newodds = 0; @@ -922,15 +929,9 @@ INT32 K_KartGetItemOdds( } break; case KITEM_THUNDERSHIELD: - cooldownOnStart = true; - powerItem = true; - if (spbplace != -1) newodds = 0; break; - case KITEM_HYUDORO: - cooldownOnStart = true; - break; default: break; } @@ -938,7 +939,7 @@ INT32 K_KartGetItemOdds( // In very small matches, remove the stupid bottom half item limiter if (pingame < 6) { - notForBottom = false; + flags &= ~KRF_NOTFORBOTTOM; } if (newodds == 0) @@ -947,32 +948,32 @@ INT32 K_KartGetItemOdds( return newodds; } - if (K_GetBGone(useitem, false) > 0) + if (result->bgone > 0) { // (Replaces hyubgone) This item is on cooldown; don't let it get rolled. newodds = 0; } - else if ((indirectItem == true) && (indirectitemcooldown > 0)) + else if (flags & KRF_INDIRECTITEM && indirectitemcooldown > 0) { // Too many items that act indirectly in a match can feel kind of bad. newodds = 0; } - else if ((cooldownOnStart == true) && (leveltime < (30*TICRATE)+starttime)) + else if (flags & KRF_COOLDOWNONSTART && leveltime < (30*TICRATE)+starttime) { // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) newodds = 0; } - else if (!K_LegacyOddsMode() && (notNearEnd == true) && (ourDist < (UINT32)ENDDIST)) + else if (!K_LegacyOddsMode() && flags & KRF_NOTNEAREND && ourDist < (UINT32)ENDDIST) { // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) newodds = 0; } - else if ((notForBottom == true) && (inBottom == true) && (leveltime >= (30*TICRATE)+starttime)) + else if (flags & KRF_NOTFORBOTTOM && inBottom && leveltime >= (30*TICRATE)+starttime) { // This item should not appear for losing players. (Usually items that feel less effective at these positions) newodds = 0; } - else if (powerItem == true) + else if (flags & KRF_POWERITEM) { // This item is a "power item". This activates "frantic item" toggle related functionality. fixed_t fracOdds = newodds * FRACUNIT; @@ -1036,10 +1037,10 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b break; } - for (j = 1; j < NUMBASEKARTRESULTS; j++) + for (j = 0; j < numkartresults; j++) { if (K_KartGetItemOdds( - i, j, + i, &kartresults[j], player->distancetofinish, player->distancefromcluster, mashed, @@ -1150,43 +1151,37 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b INT32 K_GetRollingRouletteItem(player_t *player) { - static UINT8 translation[NUMKARTITEMS-1]; + static UINT8 translation[MAXKARTRESULTS]; static UINT16 roulette_size; static INT16 odds_cached = -1; // Race odds have more columns than Battle - const UINT8 EMPTYODDS[sizeof K_KartItemOddsRace[0]] = {0}; + const UINT8 EMPTYODDS[MAXODDS] = {0}; // or not if (odds_cached != gametype) { - UINT8 *odds_row; - size_t odds_row_size; - - UINT8 i; + UINT8 oddstable = 0; + kartitemtype_e seen[MAXKARTITEMS]; + size_t numseen = 0, j; roulette_size = 0; if (gametyperules & GTR_BATTLEODDS) - { - odds_row = K_KartItemOddsBattle[0]; - odds_row_size = sizeof K_KartItemOddsBattle[0]; - } + oddstable = ODDS_BATTLE; else - { - odds_row = K_KartItemOddsRace[0]; - odds_row_size = sizeof K_KartItemOddsRace[0]; - } + oddstable = ODDS_RACE; - for (i = 1; i < NUMKARTITEMS; ++i) + for (UINT8 i = 0; i < numkartresults; i++) { - if (memcmp(odds_row, EMPTYODDS, odds_row_size)) - { - translation[roulette_size] = i; - roulette_size++; - } + kartresult_t *result = &kartresults[i]; - odds_row += odds_row_size; + for (j = 0; j < numseen; j++) + if (seen[j] == result->type) + break; + + if (j == numseen && memcmp(result->odds[oddstable], EMPTYODDS, sizeof(EMPTYODDS))) + translation[roulette_size++] = seen[numseen++] = result->type; } roulette_size *= 3; @@ -1423,7 +1418,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) UINT8 roulettestop; UINT32 pdis = 0; UINT8 useodds = 0; - INT32 spawnchance[NUMBASEKARTRESULTS]; + INT32 spawnchance[MAXKARTRESULTS]; INT64 totalspawnchance = 0; // 75-scale numbers are going to get BIG. This is for paranoia's sake. UINT8 bestbumper = 0; fixed_t mashed = 0; @@ -1497,7 +1492,8 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // Give a debug item instead if specified if (cv_kartdebugitem.value != 0 && !modeattacking) { - K_KartGetItemResult(player, cv_kartdebugitem.value); + player->itemtype = cv_kartdebugitem.value; + player->itemamount = cv_kartdebugamount.value; player->itemblink = TICRATE; player->itemblinkmode = KITEMBLINKMODE_KARMA; player->itemroulette = KROULETTE_DISABLED; @@ -1511,9 +1507,9 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // This Gametype never specified an odds type. Roll something random please! if (!(gametyperules & GTR_RACEODDS) && !(gametyperules & GTR_BATTLEODDS)) { - SINT8 itemroll = P_RandomRange(KITEM_SNEAKER, NUMKARTITEMS - 1); + UINT8 itemroll = P_RandomRange(0, numkartresults - 1); - K_KartGetItemResult(player, itemroll); + K_KartGetItemResult(player, kartresults[itemroll].name); player->itemblink = TICRATE; player->itemblinkmode = KITEMBLINKMODE_NORMAL; player->itemroulette = KROULETTE_DISABLED; @@ -1531,19 +1527,19 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { if ((gametyperules & GTR_RACEODDS)) { - if (mashed && ((K_RingsActive() == true) && (modeattacking || cv_superring.value))) // ANY mashed value? You get rings. + if (mashed && K_RingsActive() && (modeattacking || K_ItemResultEnabled("superring"))) // ANY mashed value? You get rings. { - K_KartGetItemResult(player, KITEM_SUPERRING); + K_KartGetItemResult(player, "superring"); player->itemblinkmode = KITEMBLINKMODE_MASHED; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); } else { - if (modeattacking || cv_sneaker.value) // Waited patiently? You get a sneaker! - K_KartGetItemResult(player, KITEM_SNEAKER); + if (modeattacking || K_ItemResultEnabled("sneaker")) // Waited patiently? You get a sneaker! + K_KartGetItemResult(player, "sneaker"); else // Default to sad if nothing's enabled... - K_KartGetItemResult(player, KITEM_SAD); + K_KartGetItemResult(player, ""); player->itemblinkmode = KITEMBLINKMODE_NORMAL; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); @@ -1551,23 +1547,23 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } else if (gametyperules & GTR_BATTLEODDS) { - if (mashed && (bossinfo.boss || cv_banana.value) && !itembreaker) // ANY mashed value? You get a banana. + if (mashed && (bossinfo.boss || K_ItemResultEnabled("banana")) && !itembreaker) // ANY mashed value? You get a banana. { - K_KartGetItemResult(player, KITEM_BANANA); + K_KartGetItemResult(player, "banana"); player->itemblinkmode = KITEMBLINKMODE_MASHED; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); } else if (bossinfo.boss) { - K_KartGetItemResult(player, KITEM_ORBINAUT); + K_KartGetItemResult(player, "orbinaut"); player->itemblinkmode = KITEMBLINKMODE_NORMAL; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); } else if (itembreaker) { - K_KartGetItemResult(player, KITEM_SNEAKER); + K_KartGetItemResult(player, "sneaker"); player->itemblinkmode = KITEMBLINKMODE_MASHED; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); @@ -1582,12 +1578,12 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // SPECIAL CASE No. 5: // Being in ring debt occasionally forces Super Ring on you if you mashed - if ((K_RingsActive() == true) && mashed && player->rings < 0 && cv_superring.value) + if (K_RingsActive() && mashed && player->rings < 0 && K_ItemResultEnabled("superring")) { INT32 debtamount = min(abs(player->ringmin), abs(player->rings)); if (P_RandomChance((debtamount*FRACUNIT)/abs(player->ringmin))) { - K_KartGetItemResult(player, KITEM_SUPERRING); + K_KartGetItemResult(player, "superring"); player->itemblink = TICRATE; player->itemblinkmode = KITEMBLINKMODE_MASHED; player->itemroulette = KROULETTE_DISABLED; @@ -1603,9 +1599,9 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // In battle, an SPB is forced onto players to target the "most wanted" player if (K_CanForceSPB(player, pdis) && spbplace == -1 && !indirectitemcooldown && !dontforcespb - && cv_selfpropelledbomb.value) + && K_ItemResultEnabled("spb")) { - K_KartGetItemResult(player, KITEM_SPB); + K_KartGetItemResult(player, "spb"); player->itemblink = TICRATE; player->itemblinkmode = KITEMBLINKMODE_KARMA; player->itemroulette = KROULETTE_DISABLED; @@ -1617,17 +1613,15 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // NOW that we're done with all of those specialized cases, we can move onto the REAL item roulette tables. // Initializes existing spawnchance values - for (i = 0; i < NUMBASEKARTRESULTS; i++) - spawnchance[i] = 0; + memset(spawnchance, 0, sizeof(spawnchance)); // Split into another function for a debug function below useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); - for (i = 1; i < NUMBASEKARTRESULTS; i++) + for (i = 0; i < numkartresults; i++) { - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, i, + useodds, &kartresults[i], player->distancetofinish, player->distancefromcluster, mashed, @@ -1642,14 +1636,13 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) if (totalspawnchance > 0) { totalspawnchance = P_RandomKey(totalspawnchance); - for (i = 0; i < NUMBASEKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + for (i = 0; i < numkartresults && spawnchance[i] <= totalspawnchance; i++); - K_KartGetItemResult(player, i); + K_KartGetItemResult(player, kartresults[i].name); } else { - player->itemtype = KITEM_SAD; - player->itemamount = 1; + K_KartGetItemResult(player, ""); } if (P_IsDisplayPlayer(player)) @@ -1661,3 +1654,76 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) player->itemroulette = KROULETTE_DISABLED; // Since we're done, clear the roulette number player->roulettetype = KROULETTETYPE_NORMAL; // This too } + +/** \brief Returns true or false if the player is shrunk. + + \param player (player_t) Player to test shrunken status for + + \return Is this player shrunk? +*/ +boolean K_IsShrunk(const player_t *player) +{ + return (player->growshrinktimer < 0); +} + +INT16 K_GetShrinkTime(player_t *player) +{ + return (player->growshrinktimer * -1); +} + +fixed_t K_AccomodateShrinkScaling(fixed_t x) +{ + return FixedDiv(x, SHRINK_SCALE); +} + +/** \brief Depending on the Shrink type set, this returns true or false if the player is shrunk. + + \param player (player_t) Player to test shrunken status for + \param legacy (boolean) Legacy shrink? + + \return void +*/ +boolean K_IsShrunkMode(const player_t *player, boolean legacy) +{ + boolean shrunk = K_IsShrunk(player); + boolean legacytype = (!K_IsKartItemAlternate(KITEM_SHRINK)); + + if (legacy) + { + return ((shrunk) && (legacytype)); + } + + return ((shrunk) && (!legacytype)); +} + +#define ALTSHRINK_EPSILON (320 * FRACUNIT) +#define NEIGHBOR_IFRAMES (TICRATE / 2) +#define BASE_IFRAMES (2 * TICRATE) + +void K_AltShrinkIFrames(player_t *player) +{ + vector3_t tempclusterpoint; + + // Find neighbors + INT32 N = K_CountNeighboringPlayers(player, ALTSHRINK_EPSILON, &tempclusterpoint); + + // For every neighbor, add some iframes for a clean breakaway. + player->flashing = (UINT16)min((INT32)(UINT16_MAX), BASE_IFRAMES + (NEIGHBOR_IFRAMES * N)); +} + +#define PITY_SHRINKINCREASE_BASE (3) +#define PITY_SHRINKINCREASE (TICRATE / 2) + +void K_AltShrinkPityIncrease(player_t *player) +{ + // Increase your shrink timer by a little bit for every player you run into. + INT32 shrinktime = (UINT32)(K_GetShrinkTime(player)); + + fixed_t dimin = FRACUNIT; + + dimin = max(0, 5 - player->altshrinktimeshit) * FRACUNIT / 5; + + shrinktime = min(INT16_MAX, shrinktime + PITY_SHRINKINCREASE_BASE + FixedMul(PITY_SHRINKINCREASE, dimin)); + + player->growshrinktimer = ((INT16)shrinktime) * -1; +} diff --git a/src/k_items.h b/src/k_items.h new file mode 100644 index 000000000..d6183a68e --- /dev/null +++ b/src/k_items.h @@ -0,0 +1,147 @@ +// BLANKART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2025 by Kart Krew. +// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 Blankart Team. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_items.h +/// \brief Kart item systems. + +#ifndef __K_ITEMS__ +#define __K_ITEMS__ + +#include "doomdef.h" +#include "doomtype.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Max odds count +#define MAXODDS 16 + +// Distance variables +#define DISTVAR CV_Get(&cv_kartoddsdist) +#define DISTVAR_LEGACY CV_Get(&cv_kartlegacyoddsdist) +#define SPBDISTVAR CV_Get(&cv_kartspbdist) +#define SPBDISTVAR_LEGACY CV_Get(&cv_kartlegacyspbdist) + +typedef enum +{ + KITEM_SAD = -1, + KITEM_NONE = 0, + KITEM_SNEAKER, + KITEM_ROCKETSNEAKER, + KITEM_INVINCIBILITY, + KITEM_BANANA, + KITEM_EGGMAN, + KITEM_ORBINAUT, + KITEM_JAWZ, + KITEM_MINE, + KITEM_BALLHOG, + KITEM_SPB, + KITEM_GROW, + KITEM_SHRINK, + KITEM_THUNDERSHIELD, + KITEM_HYUDORO, + KITEM_POGOSPRING, + KITEM_KITCHENSINK, + KITEM_SUPERRING, + KITEM_LANDMINE, + KITEM_BUBBLESHIELD, + KITEM_FLAMESHIELD, + + KITEM_FIRSTFREESLOT, + KITEM_LASTFREESLOT = 126, + MAXKARTITEMS +} ATTRPACK kartitemtype_e; + +#define MAXKARTRESULTS 255 + +typedef enum +{ + ODDS_RACE, + ODDS_BATTLE, + ODDS_SPECIAL, + MAXODDSTABLES +} ATTRPACK kartoddstable_e; + +struct kartitemgraphics_t +{ + UINT8 numpatches; + const char **patchnames; + patch_t **patches; +}; + +struct kartitem_t +{ + const char *name; + consvar_t *altcvar; + kartitemgraphics_t graphics[2]; + boolean animated; + boolean darkbg; +}; + +enum +{ + KRF_INDIRECTITEM = 1<<0, + KRF_POWERITEM = 1<<1, + KRF_COOLDOWNONSTART = 1<<2, + KRF_NOTNEAREND = 1<<3, + KRF_NOTFORBOTTOM = 1<<4, +}; + +struct kartresult_t +{ + const char *name; + consvar_t *cvar; + kartitemtype_e type; + UINT8 amount; + UINT8 odds[MAXODDSTABLES][MAXODDS]; + UINT8 flags; + tic_t basebgone, bgone; +}; + +extern kartitem_t kartitems[MAXKARTITEMS]; +extern UINT8 numkartitems; +extern kartresult_t kartresults[MAXKARTRESULTS]; +extern UINT8 numkartresults; + +kartresult_t *K_GetKartResult(const char *name); +void K_InitializeItems(void); +boolean K_ItemResultEnabled(const char *name); +void K_SetupItemOdds(void); + +void K_KartHandleShieldCooldown(kartitemtype_e item); +boolean K_LegacyOddsMode(void); +UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos); + +UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame); +UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush); +UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); +UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); +INT32 K_KartGetItemOdds(UINT8 pos, const kartresult_t *result, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); +INT32 K_GetRollingRouletteItem(player_t *player); +void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); + +boolean K_IsShrunk(const player_t *player); +INT16 K_GetShrinkTime(player_t *player); +fixed_t K_AccomodateShrinkScaling(fixed_t x); + +boolean K_IsShrunkMode(const player_t *player, boolean legacy); + +#define K_IsLegacyShrunk(player) (K_IsShrunkMode(player, true)) +#define K_IsAltShrunk(player) (K_IsShrunkMode(player, false)) + +void K_AltShrinkIFrames(player_t *player); +void K_AltShrinkPityIncrease(player_t *player); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __K_ITEMS__ diff --git a/src/k_kart.c b/src/k_kart.c index 04f3316af..31f801ec4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -64,9 +64,8 @@ #include "k_follower.h" #include "k_grandprix.h" #include "k_cluster.hpp" -#include "k_odds.h" +#include "k_items.h" #include "k_specialstage.h" -#include "k_altshrink.h" #include "h_timers.h" #include "blan/b_soc.h" @@ -233,37 +232,6 @@ angle_t K_ReflectAngle(angle_t yourangle, angle_t theirangle, fixed_t yourspeed, void K_RegisterKartStuff(void) { - CV_RegisterVar(&cv_sneaker); - CV_RegisterVar(&cv_rocketsneaker); - CV_RegisterVar(&cv_invincibility); - CV_RegisterVar(&cv_banana); - CV_RegisterVar(&cv_eggmanmonitor); - CV_RegisterVar(&cv_orbinaut); - CV_RegisterVar(&cv_jawz); - CV_RegisterVar(&cv_mine); - CV_RegisterVar(&cv_ballhog); - CV_RegisterVar(&cv_selfpropelledbomb); - CV_RegisterVar(&cv_grow); - CV_RegisterVar(&cv_shrink); - CV_RegisterVar(&cv_thundershield); - CV_RegisterVar(&cv_hyudoro); - CV_RegisterVar(&cv_pogospring); - CV_RegisterVar(&cv_kitchensink); - - // Nu-ITEMS - CV_RegisterVar(&cv_superring); - CV_RegisterVar(&cv_landmine); // FIXME: Kill it. - CV_RegisterVar(&cv_bubbleshield); - CV_RegisterVar(&cv_flameshield); - - CV_RegisterVar(&cv_dualsneaker); - CV_RegisterVar(&cv_triplesneaker); - CV_RegisterVar(&cv_triplebanana); - CV_RegisterVar(&cv_decabanana); - CV_RegisterVar(&cv_tripleorbinaut); - CV_RegisterVar(&cv_quadorbinaut); - CV_RegisterVar(&cv_dualjawz); - CV_RegisterVar(&cv_kartminimap); CV_RegisterVar(&cv_kartcheck); CV_RegisterVar(&cv_kartinvinsfx); @@ -289,8 +257,6 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartusepwrlv); CV_RegisterVar(&cv_votetime); - CV_RegisterVar(&cv_kartdebugitem); - CV_RegisterVar(&cv_kartdebugamount); CV_RegisterVar(&cv_kartdebugshrink); CV_RegisterVar(&cv_kartdebugdistribution); CV_RegisterVar(&cv_kartdebughuddrop); @@ -415,10 +381,8 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartslopeboost); - CV_RegisterVar(&cv_kartshrinktype); CV_RegisterVar(&cv_kartaltshrinktime); - CV_RegisterVar(&cv_kartinvintype); CV_RegisterVar(&cv_kartinvindist); CV_RegisterVar(&cv_kartinvindistmul); @@ -4742,7 +4706,7 @@ static void K_DoHyudoroSteal(player_t *player) prandom = P_RandomFixed(); S_StartSound(player->mo, sfx_s3k92); - if (sink && numplayers > 0 && cv_kitchensink.value) // BEHOLD THE KITCHEN SINK + if (sink && numplayers > 0 && K_ItemResultEnabled("kitchensink")) // BEHOLD THE KITCHEN SINK { player->hyudorotimer = hyu; player->stealingtimer = stealtime; @@ -5538,7 +5502,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (type == 0) { UINT8 useodds = 0; - INT32 spawnchance[NUMBASEKARTRESULTS]; + INT32 spawnchance[MAXKARTRESULTS]; INT32 totalspawnchance = 0; INT32 i; @@ -5546,10 +5510,10 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 useodds = amount; - for (i = 1; i < NUMBASEKARTRESULTS; i++) + for (i = 0; i < numkartresults; i++) { spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, i, + useodds, &kartresults[i], UINT32_MAX, 0, 0, @@ -5561,30 +5525,29 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (totalspawnchance > 0) { totalspawnchance = P_RandomKey(totalspawnchance); - for (i = 0; i < NUMBASEKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + for (i = 0; i < numkartresults && spawnchance[i] <= totalspawnchance; i++); // TODO: this is bad! // K_KartGetItemResult requires a player // but item roulette will need rewritten to change this - const UINT8 newType = K_ItemResultToType(i); - const UINT8 newAmount = K_ItemResultToAmount(i); + const kartresult_t *result = &kartresults[i]; - if (newAmount > 1) + if (result->amount > 1) { UINT8 j; - for (j = 0; j < newAmount-1; j++) + for (j = 0; j < result->amount-1; j++) { K_CreatePaperItem( x, y, z, angle, flip, - newType, 1 + result->type, 1 ); } } - drop->threshold = newType; + drop->threshold = result->type; drop->movecount = 1; } else @@ -11507,30 +11470,10 @@ boolean K_ItemPushingActive(void) return itempushing; } -boolean K_GetKartInvinType(void) -{ - return invintype; -} - boolean K_IsKartItemAlternate(UINT8 itemtype) { - switch (itemtype) - { - case KITEM_INVINCIBILITY: - return invintype == KARTITEM_ALTERN; - break; - case KITEM_EGGMAN: - return eggmantype == KARTITEM_ALTERN; - break; - case KITEM_SHRINK: - return shrinktype == KARTITEM_ALTERN; - break; - - default: - return false; - break; - } - return false; + return itemtype >= 0 && itemtype < numkartitems + && kartitems[itemtype].altcvar != NULL && kartitems[itemtype].altcvar->value == 1; } boolean K_UsingLegacyCheckpoints(void) diff --git a/src/k_kart.h b/src/k_kart.h index e85aae584..d5503cb44 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -126,6 +126,11 @@ extern vector3_t clusterpoint, clusterdtf; #define FLAMEACCELBOOST CV_Get(&cv_kartstacking_flame_accelboost) #define FLAMESTACKABLE CV_Get(&cv_kartstacking_flame_stackable) +#define ALTSHRINKTIME CV_Get(&cv_kartaltshrinktime) +#define SHRINKSPEEDBOOST CV_Get(&cv_kartstacking_altshrink_speedboost) +#define SHRINKACCELBOOST CV_Get(&cv_kartstacking_altshrink_accelboost) +#define SHRINKSTACKABLE CV_Get(&cv_kartstacking_altshrink_stackable) + #define STARTSPEEDBOOST CV_Get(&cv_kartstacking_start_speedboost) #define STARTACCELBOOST CV_Get(&cv_kartstacking_start_accelboost) #define STARTSTACKABLE CV_Get(&cv_kartstacking_start_stackable) @@ -344,7 +349,6 @@ boolean K_DraftingActive(void); boolean K_AirDropActive(void); boolean K_ItemLitterActive(void); boolean K_ItemPushingActive(void); -boolean K_GetKartInvinType(void); boolean K_IsKartItemAlternate(UINT8 itemtype); INT32 K_GetBumpSpark(void); boolean K_BoostChain(player_t *player, INT32 timer, boolean chainsound); diff --git a/src/k_odds.h b/src/k_odds.h deleted file mode 100644 index a762f8f0e..000000000 --- a/src/k_odds.h +++ /dev/null @@ -1,72 +0,0 @@ -// BLANKART -//----------------------------------------------------------------------------- -// Copyright (C) 2018-2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". -// Copyright (C) 2025 Blankart Team. -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- -/// \file k_odds.hpp -/// \brief Kart item odds systems. - -#ifndef __K_ODDS__ -#define __K_ODDS__ - -#include "command.h" // Need for player_t -#include "doomdef.h" -#include "doomtype.h" -#include "d_player.h" // Need for player_t -#include "d_ticcmd.h" -#include "m_fixed.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// Max odds count -#define MAXODDS 16 - -// Distance variables -#define DISTVAR CV_Get(&cv_kartoddsdist) -#define DISTVAR_LEGACY CV_Get(&cv_kartlegacyoddsdist) -#define SPBDISTVAR CV_Get(&cv_kartspbdist) -#define SPBDISTVAR_LEGACY CV_Get(&cv_kartlegacyspbdist) - -extern consvar_t *KartItemCVars[NUMKARTRESULTS-1]; - -// Goner; tracks how long an item should be "gone" for. -// AKA a time-based cooldown so Hyudoro and whatever else piss off to prevent -// spamming. - -typedef enum goner_sect_e { - GONER_BASECOOLDOWN = 0, - GONER_CURRCOOLDOWN = 1, -} goner_sect_t; - -extern tic_t ItemBGone[NUMKARTRESULTS][2]; - -void K_SetBGone(SINT8 item, tic_t time); -void K_SetBGoneToBase(SINT8 item); -tic_t K_GetBGone(SINT8 item, boolean base); -void K_KartHandleShieldCooldown(SINT8 item); - -boolean K_LegacyOddsMode(void); -UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos); - -UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame); -UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush); -UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); -INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); -INT32 K_GetRollingRouletteItem(player_t *player); -SINT8 K_ItemResultToType(SINT8 getitem); -UINT8 K_ItemResultToAmount(SINT8 getitem); -void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // __K_ODDS__ diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 9762c280e..9862db465 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -33,7 +33,7 @@ #include "k_color.h" #include "k_hud.h" #include "k_waypoint.h" -#include "k_odds.h" +#include "k_items.h" #include "d_netcmd.h" // IsPlayerAdmin #include "m_menu.h" // Player Setup menu color stuff #include "p_spec.h" // P_StartQuake @@ -4026,10 +4026,20 @@ static int lib_kGetKartFlashing(lua_State *L) static int lib_kGetItemPatch(lua_State *L) { - UINT8 item = (UINT8)luaL_optinteger(L, 1, KITEM_NONE); + kartitemtype_e item = (kartitemtype_e)luaL_optinteger(L, 1, KITEM_NONE); boolean tiny = lua_optboolean(L, 2); //HUDSAFE - lua_pushstring(L, K_GetItemPatch(item, tiny)); + + // TODO: compatmode KRITEM + const char *sad = tiny ? "K_ISSAD" : "K_ITSAD"; + if (item <= 0 || item >= numkartitems) + { + lua_pushstring(L, sad); + return 1; + } + + kartitemgraphics_t *graphics = &kartitems[item].graphics[tiny ? 1 : 0]; + lua_pushstring(L, graphics->numpatches == 0 ? sad : graphics->patchnames[0]); return 1; } @@ -4111,48 +4121,10 @@ static int lib_kSetHyuCountdown(lua_State *L) { tic_t c = (tic_t)luaL_checkinteger(L, 1); NOHUD - K_SetBGone(KITEM_HYUDORO, c); + K_GetKartResult("hyudoro")->bgone = c; return 0; } -static int lib_kSetBGone(lua_State *L) -{ - SINT8 item = (SINT8)luaL_checkinteger(L, 1); - tic_t c = (tic_t)luaL_checkinteger(L, 2); - NOHUD - if (item < KITEM_SNEAKER || item > (NUMKARTRESULTS - 1)) - { - luaL_error(L, "given item ID is outside bgone range"); - } - K_SetBGone(item, c); - return 0; -} - -static int lib_kSetBGoneToBase(lua_State *L) -{ - SINT8 item = (SINT8)luaL_checkinteger(L, 1); - NOHUD - if (item < KITEM_SNEAKER || item > (NUMKARTRESULTS - 1)) - { - luaL_error(L, "given item ID is outside bgone range"); - } - K_SetBGoneToBase(item); - return 0; -} - -static int lib_kGetBGone(lua_State *L) -{ - SINT8 item = (SINT8)luaL_checkinteger(L, 1); - boolean base = lua_isnoneornil(L, 2) ? false : luaL_checkboolean(L, 2); - NOHUD - if (item < KITEM_SNEAKER || item > (NUMKARTRESULTS - 1)) - { - luaL_error(L, "given item ID is outside bgone range"); - } - lua_pushinteger(L, K_GetBGone(item, base)); - return 1; -} - // Checks if the floor closet floor under an object would be safe to respawn/land on. static int lib_kSafeRespawnPosition(lua_State *L) { @@ -4227,13 +4199,6 @@ static int lib_kItemLitterActive(lua_State *L) return 1; } -// Grabs the currently active invintype. -static int lib_kGetKartInvinType(lua_State *L) -{ - lua_pushinteger(L, K_GetKartInvinType()); - return 1; -} - // Gets the currently active bumpspark type. static int lib_kGetBumpSpark(lua_State *L) { @@ -5341,9 +5306,6 @@ static luaL_Reg lib[] = { {"K_SetExitCountdown",lib_kSetExitCountdown}, {"K_SetIndirectItemCooldown",lib_kSetIndirectItemCountdown}, {"K_SetHyudoroCooldown",lib_kSetHyuCountdown}, - {"K_SetBGone", lib_kSetBGone}, - {"K_SetBGoneToBase", lib_kSetBGoneToBase}, - {"K_GetBGone", lib_kGetBGone}, {"K_SafeRespawnPosition",lib_kSafeRespawnPosition}, {"K_GetCollideAngle",lib_kGetCollideAngle}, @@ -5358,7 +5320,6 @@ static luaL_Reg lib[] = { {"K_AirDropActive",lib_kAirDropActive}, {"K_ItemLitterActive",lib_kItemLitterActive}, {"K_GetBumpSpark",lib_kGetBumpSpark}, - {"K_GetKartInvinType",lib_kGetKartInvinType}, {"K_UsingLegacyCheckpoints",lib_kUsingLegacyCheckpoints}, {"K_DoBoost",lib_kDoBoost}, {"K_ClearBoost",lib_kClearBoost}, diff --git a/src/lua_script.c b/src/lua_script.c index 5a4136d23..3a63dbe32 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -27,7 +27,7 @@ #include "p_slopes.h" // for P_SlopeById and slopelist #include "p_polyobj.h" // polyobj_t, PolyObjects #include "k_battle.h" -#include "k_odds.h" +#include "k_items.h" #include "k_waypoint.h" #ifdef LUA_ALLOW_BYTECODE #include "d_netfil.h" // for LUA_DumpFile @@ -417,11 +417,8 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"itempushing")) { lua_pushboolean(L, itempushing); // hmm... i think this should be a boolean return 1; - } else if (fastcmp(word,"invintype")) { - lua_pushinteger(L, invintype); - return 1; } else if (fastcmp(word,"hyubgone")) { - lua_pushinteger(L, K_GetBGone(KITEM_HYUDORO, false)); + lua_pushinteger(L, K_GetKartResult("hyudoro")->bgone); return 1; } else if (fastcmp(word,"encoremode")) { lua_pushboolean(L, encoremode); @@ -564,9 +561,7 @@ int LUA_WriteGlobals(lua_State *L, const char *word) else if (fastcmp(word,"indirectitemcooldown")) indirectitemcooldown = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"hyubgone")) - { - K_SetBGone(KITEM_HYUDORO, (tic_t)luaL_checkinteger(L, 2)); - } + K_GetKartResult("hyudoro")->bgone = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"starttime")) starttime = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"introtime")) diff --git a/src/m_menu.c b/src/m_menu.c index baa98c7df..02b8ac739 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -67,7 +67,7 @@ #include "i_sound.h" #include "k_hud.h" // SRB2kart #include "k_kart.h" -#include "k_odds.h" // KartItemCVars +#include "k_items.h" // KartItemCVars #include "k_pwrlv.h" #include "k_stats.h" // SRB2kart #include "d_player.h" // KITEM_ constants @@ -8327,8 +8327,7 @@ void MD_DrawMonitorToggles(void) INT32 leftdraw, rightdraw, totaldraw; INT32 x = currentMenu->x, y = currentMenu->y+(spacing/4); INT32 onx = 0, ony = 0; - consvar_t *cv; - INT32 i, translucent, drawnum; + INT32 i, translucent, drawnum, arg; M_DrawMenuTitle(); @@ -8353,7 +8352,7 @@ void MD_DrawMonitorToggles(void) { INT32 j; - for (j = 0; j < height; j++) + for (j = 0; j < height; j++, y += spacing) { const INT32 thisitem = (i*height)+j; @@ -8364,64 +8363,45 @@ void MD_DrawMonitorToggles(void) { onx = x; ony = y; - y += spacing; continue; } -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[thisitem].argument == 255) + arg = currentMenu->menuitems[thisitem].argument; + if (arg == 255) { V_DrawScaledPatch(x, y, V_TRANSLUCENT, W_CachePatchName("K_ISBG", PU_CACHE)); continue; } -#endif - if (currentMenu->menuitems[thisitem].argument == 0) + else if (arg == 0) { V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISTOGL", PU_CACHE)); continue; } - - cv = KartItemCVars[currentMenu->menuitems[thisitem].argument-1]; - translucent = (cv->value ? 0 : V_TRANSLUCENT); - - switch (currentMenu->menuitems[thisitem].argument) + else if (arg <= 0 && arg > numkartresults) { - case KRITEM_DUALSNEAKER: - case KRITEM_DUALJAWZ: - drawnum = 2; - break; - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEBANANA: - case KRITEM_TRIPLEORBINAUT: - drawnum = 3; - break; - case KRITEM_QUADORBINAUT: - drawnum = 4; - break; - case KRITEM_TENFOLDBANANA: - drawnum = 10; - break; - default: - drawnum = 0; - break; + continue; } - if (cv->value) + kartresult_t *result = &kartresults[arg - 1]; + kartitem_t *item = &kartitems[result->type]; + boolean enabled = K_ItemResultEnabled(result->name); + translucent = enabled ? 0 : V_TRANSLUCENT; + drawnum = item->animated ? 0 : CLAMP(result->amount, 0, item->graphics[1].numpatches - 1); + + if (enabled) V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); else V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBGD", PU_CACHE)); - if (drawnum != 0) + if (drawnum > 1) { V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISMUL", PU_CACHE)); - V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].argument, true), PU_CACHE)); + V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|translucent, va("x%d", drawnum)); } else - V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].argument, true), PU_CACHE)); - - y += spacing; + V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); } x += spacing; @@ -8429,8 +8409,8 @@ void MD_DrawMonitorToggles(void) } { -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[itemOn].argument == 255) + arg = currentMenu->menuitems[itemOn].argument; + if (arg == 255) { V_DrawScaledPatch(onx-1, ony-2, V_TRANSLUCENT, W_CachePatchName("K_ITBG", PU_CACHE)); if (shitsfree) @@ -8443,46 +8423,43 @@ void MD_DrawMonitorToggles(void) V_DrawScaledPatch(onx-1, ony-2, trans, W_CachePatchName("K_ITFREE", PU_CACHE)); } } - else -#endif - if (currentMenu->menuitems[itemOn].argument == 0) + else if (arg == 0) { V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITTOGL", PU_CACHE)); } - else + else if (arg > 0 && arg <= numkartresults) { - cv = KartItemCVars[currentMenu->menuitems[itemOn].argument-1]; - translucent = (cv->value ? 0 : V_TRANSLUCENT); + kartresult_t *result = &kartresults[arg - 1]; + kartitem_t *item = &kartitems[result->type]; + boolean enabled = K_ItemResultEnabled(result->name); + translucent = enabled ? 0 : V_TRANSLUCENT; + drawnum = item->animated ? 0 : CLAMP(result->amount, 0, item->graphics[0].numpatches - 1); - switch (currentMenu->menuitems[itemOn].argument) - { - case KRITEM_TENFOLDBANANA: - drawnum = 10; - break; - default: - drawnum = 0; - break; - } - - if (cv->value) + if (enabled) V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); else V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE)); - if (drawnum != 0) + if (drawnum > 1) { V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITMUL", PU_CACHE)); - V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].argument, false), PU_CACHE)); + V_DrawScaledPatch(onx-1, ony-2, translucent, K_GetCachedItemPatch(result->type, false, result->amount)); V_DrawScaledPatch(onx+27, ony+39, translucent, W_CachePatchName("K_ITX", PU_CACHE)); V_DrawKartString(onx+37, ony+34, translucent, va("%d", drawnum)); } else - V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].argument, false), PU_CACHE)); + V_DrawScaledPatch(onx-1, ony-2, translucent, K_GetCachedItemPatch(result->type, false, result->amount)); + + if (item->altcvar != NULL) + { + translucent = item->altcvar->value == 1 ? 0 : V_TRANSLUCENT; + V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName("K_ALTITM", PU_CACHE)); + } } } - if (shitsfree) + if (renderisnewtic && shitsfree) shitsfree--; V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, MENUCAPS|highlightflags, va("* %s *", currentMenu->menuitems[itemOn].text)); @@ -8493,7 +8470,11 @@ INT32 MR_HandleMonitorToggles(INT32 choice) const INT32 width = 6, height = 4; INT32 column = itemOn/height, row = itemOn%height; INT16 next; - UINT8 i; + + kartresult_t *result = NULL; + INT32 arg = currentMenu->menuitems[itemOn].argument; + if (arg > 0 && arg <= numkartresults) + result = &kartresults[arg - 1]; switch (choice) { @@ -8542,8 +8523,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) break; case KEY_ENTER: -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[itemOn].argument == 255) + if (choice == 255) { //S_StartSound(NULL, sfx_s26d); if (!shitsfree) @@ -8552,29 +8532,34 @@ INT32 MR_HandleMonitorToggles(INT32 choice) S_StartSound(NULL, sfx_itfree); } } - else -#endif - if (currentMenu->menuitems[itemOn].argument == 0) + else if (choice == 0) { - INT32 v = cv_sneaker.value; + INT32 v = K_ItemResultEnabled("sneaker") ? 1 : 0; S_StartSound(NULL, sfx_s1b4); - for (i = 0; i < NUMBASEKARTRESULTS-1; i++) + for (UINT8 i = 0; i < numkartresults; i++) { - if (KartItemCVars[i]->value == v) - CV_AddValue(KartItemCVars[i], 1); + if (kartresults[i].cvar->value == v) + CV_AddValue(kartresults[i].cvar, 1); } } - else + else if (result != NULL) { S_StartSound(NULL, sfx_s1ba); - CV_AddValue(KartItemCVars[currentMenu->menuitems[itemOn].argument-1], 1); + CV_AddValue(result->cvar, 1); + } + break; + + case KEY_BACKSPACE: + if (result != NULL && kartitems[result->type].altcvar != NULL) + { + S_StartSound(NULL, sfx_s3kb7); + CV_AddValue(kartitems[result->type].altcvar, 1); } break; default: return false; } - return true; } diff --git a/src/p_enemy.c b/src/p_enemy.c index af239e4f8..db181750c 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -30,6 +30,7 @@ #include "k_waypoint.h" #include "k_battle.h" #include "k_collide.h" +#include "k_items.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" diff --git a/src/p_inter.c b/src/p_inter.c index 64cf29405..1066319ff 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -39,7 +39,7 @@ #include "k_boss.h" #include "p_spec.h" #include "k_objects.h" -#include "k_odds.h" +#include "k_items.h" #include "acs/interface.h" // CTF player names @@ -1742,7 +1742,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget break; } - if (target->threshold < 1 || target->threshold >= NUMKARTITEMS) // bruh moment prevention + if (target->threshold < 1 || target->threshold >= numkartitems) // bruh moment prevention { player->itemtype = KITEM_SAD; player->itemamount = 1; diff --git a/src/p_mobj.c b/src/p_mobj.c index 599ddf2ea..fe4ffc362 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -50,7 +50,7 @@ #include "k_terrain.h" #include "k_collide.h" #include "k_objects.h" -#include "k_odds.h" +#include "k_items.h" #include "k_waypoint.h" // BlanKart @@ -4166,7 +4166,7 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) mobj_t *part; UINT32 newRenderFlags = 0; - if (itemType < 1 || itemType >= NUMKARTITEMS) + if (itemType < 1 || itemType >= numkartitems) itemType = KITEM_SAD; part = mobj; @@ -11385,7 +11385,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) else if (P_RandomChance(FRACUNIT/3)) mobj->threshold = KITEM_ORBINAUT; else - mobj->threshold = P_RandomRange(1, NUMKARTITEMS - 1); + mobj->threshold = P_RandomRange(1, numkartitems - 1); mobj->movecount = P_RandomChance(FRACUNIT/3) ? 1 : P_RandomKey(32) + 1; #else mobj->threshold = KITEM_SUPERRING; // default item is super ring @@ -12807,7 +12807,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) } case MT_ITEMCAPSULE: { - boolean isRingCapsule = (mthing->args[0] < 1 || mthing->args[0] == KITEM_SUPERRING || mthing->args[0] >= NUMKARTITEMS); + boolean isRingCapsule = mthing->args[0] < 1 || mthing->args[0] == KITEM_SUPERRING || mthing->args[0] >= numkartitems; // don't spawn ring capsules in with rings off. if (isRingCapsule && (K_RingsActive() == false)) @@ -13666,7 +13666,7 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean mobj->flags |= MF_NOGRAVITY; // Angle = item type - if (mthing->args[0] > 0 && mthing->args[0] < NUMKARTITEMS) + if (mthing->args[0] > 0 && mthing->args[0] < numkartitems) mobj->threshold = mthing->args[0]; // Parameter = extra items (x5 for rings) diff --git a/src/p_saveg.c b/src/p_saveg.c index 92982a02b..9ed9194e5 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -41,11 +41,10 @@ #include "k_battle.h" #include "k_pwrlv.h" #include "k_terrain.h" -#include "k_odds.h" +#include "k_items.h" #include "acs/interface.h" #include "g_party.h" #include "k_waypoint.h" -#include "k_altshrink.h" #include @@ -4099,8 +4098,6 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNCBOOLEAN(itemlittering); SYNCBOOLEAN(itempushing); SYNC(bumpsparkactive); - SYNC(invintype); - SYNC(shrinktype); SYNC(antibumptime); for (i = 0; i < sizeof(votelevels)/sizeof(*votelevels); i++) @@ -4187,11 +4184,11 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNC(wantedcalcdelay); SYNC(indirectitemcooldown); - for (i = 0; i < NUMKARTRESULTS; i++) + for (i = 0; i < numkartresults; i++) { // hyubgone - //SYNC(ItemBGone[i][GONER_BASECOOLDOWN]); - SYNC(ItemBGone[i][GONER_CURRCOOLDOWN]); + //SYNC(kartresults[i].basebgone); + SYNC(kartresults[i].bgone); } SYNC(mapreset); diff --git a/src/p_setup.c b/src/p_setup.c index 99376d695..75b01c62e 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -103,13 +103,12 @@ #include "k_terrain.h" // TRF_TRIPWIRE #include "k_brightmap.h" #include "k_director.h" // K_InitDirector -#include "k_odds.h" // ItemBGone +#include "k_items.h" #include "acs/interface.h" #include "doomstat.h" // MAXMUSNAMES #include "k_mapuser.h" #include "p_deepcopy.h" #include "k_specialstage.h" -#include "k_altshrink.h" #include "blan/b_soc.h" @@ -8072,11 +8071,12 @@ static void P_InitLevelSettings(boolean reloadinggamestate) itempushing = true; bumpsparkactive = (UINT8)cv_kartbumpspark.value; - invintype = (UINT8)cv_kartinvintype.value; - K_UpdateShrinkType(); antibumptime = (tic_t)cv_kartantibump.value * TICRATE; + if (!reloadinggamestate) + K_SetupItemOdds(); + // emerald hunt hunt1 = hunt2 = hunt3 = NULL; @@ -8402,10 +8402,10 @@ static void P_InitGametype(void) wantedcalcdelay = wantedfrequency*2; indirectitemcooldown = 0; - for (i = 0; i < NUMKARTRESULTS; i++) + for (i = 0; i < numkartresults; i++) { // hyubgone - ItemBGone[i][GONER_CURRCOOLDOWN] = 0; + kartresults[i].bgone = 0; } mapreset = 0; diff --git a/src/p_spec.c b/src/p_spec.c index e0a7f990f..0f1506a67 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -44,7 +44,7 @@ // SRB2kart #include "k_kart.h" -#include "k_altshrink.h" +#include "k_items.h" #include "console.h" // CON_LogMessage #include "k_terrain.h" #include "acs/interface.h" diff --git a/src/p_tick.c b/src/p_tick.c index d08fcce92..9733f5b07 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -41,7 +41,7 @@ #include "k_director.h" #include "acs/interface.h" #include "k_bot.h" // K_BotTicker -#include "k_odds.h" // ItemBGone +#include "k_items.h" // ItemBGone #include "k_specialstage.h" #ifdef PARANOIA @@ -902,13 +902,11 @@ void P_Ticker(boolean run) if (indirectitemcooldown > 0) indirectitemcooldown--; - for (i = 0; i < NUMKARTRESULTS; i++) + for (i = 0; i < numkartresults; i++) { // hyubgone - if (K_GetBGone(i, false) > 0) - { - ItemBGone[i][GONER_CURRCOOLDOWN]--; - } + if (kartresults[i].bgone > 0) + kartresults[i].bgone--; } if (gametyperules & GTR_RACEODDS) diff --git a/src/p_user.c b/src/p_user.c index fb45513a9..1aac175f2 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -61,8 +61,7 @@ #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" #include "k_follower.h" -#include "k_odds.h" -#include "k_altshrink.h" +#include "k_items.h" #include "g_party.h" #include "acs/interface.h" diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index 607a8afe4..1623fb2f5 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -18,6 +18,7 @@ #include "p_tick.h" #include "typedef.h" #include "r_fps.h" +#include "k_items.h" #ifdef ROTSPRITE fixed_t rollcosang[ROTANGLES]; diff --git a/src/typedef.h b/src/typedef.h index 9db777746..7d0d3dab7 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -208,6 +208,11 @@ TYPEDEF (waypoint_t); TYPEDEF (mapUserProperty_t); TYPEDEF (mapUserProperties_t); +// k_items.h +TYPEDEF (kartitem_t); +TYPEDEF (kartresult_t); +TYPEDEF (kartitemgraphics_t); + // lua_hudlib_drawlist.h typedef struct huddrawlist_s *huddrawlist_h; From 19d67aa4e023c94c8c5b21c054a7084b8527f4b8 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Thu, 6 Nov 2025 18:59:02 -0500 Subject: [PATCH 12/76] KPF_* item flags for HUD rendering replaces the boolean values with a flags enum --- src/k_hud.c | 14 +++++++++++--- src/k_items.c | 11 +++++------ src/k_items.h | 17 +++++++++++++++-- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index a807dbdd6..2fd251b6a 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -657,7 +657,7 @@ patch_t *K_GetCachedItemPatch(SINT8 type, boolean tiny, UINT8 amount) kartitem_t *item = &kartitems[type]; kartitemgraphics_t *graphics = &item->graphics[tiny ? 1 : 0]; - if (item->animated) + if ((item->hudflags & KPF_ANIMATED)) return graphics->patches[(leveltime % (graphics->numpatches*3)) / 3]; else return graphics->patches[CLAMP(amount - 1, 0, graphics->numpatches - 1)]; @@ -1299,8 +1299,8 @@ static void K_drawKartItem(void) if (stplyr->itemtype > 0 && stplyr->itemtype < numkartitems) { kartitem_t *item = &kartitems[stplyr->itemtype]; - numberdisplaymin = item->animated ? 2 : item->graphics[tiny ? 1 : 0].numpatches + 1; - dark = item->darkbg; + numberdisplaymin = (item->hudflags & KPF_ANIMATED) ? 2 : item->graphics[tiny ? 1 : 0].numpatches + 1; + dark = (item->hudflags & KPF_DARKBG); } if ((stplyr->itemflags & IF_ITEMOUT) && !(leveltime & 1)) @@ -1334,7 +1334,14 @@ static void K_drawKartItem(void) fflags = info.flags; flipamount = info.flipamount; if (localcolor != SKINCOLOR_NONE) + { colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); + } + else if ((kartitems[stplyr->itemtype]).hudflags & KPF_COLPATCH2PLAYER) + { + colmap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); + } + if (K_UseColorHud()) colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); @@ -1345,6 +1352,7 @@ static void K_drawKartItem(void) fixed_t rfy = fy<itemroulette && !stplyr->deadtimer) { fixed_t frac = R_GetTimeFrac(RTF_LEVEL); diff --git a/src/k_items.c b/src/k_items.c index 679ebb273..51166b6a3 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -228,13 +228,12 @@ void K_InitializeItems(void) ONE(KITEM_FLAMESHIELD, "K_ITFLMS", "K_ISFLMS"); #undef ONE - kartitems[KITEM_INVINCIBILITY].animated = true; + kartitems[KITEM_INVINCIBILITY].hudflags = KPF_ANIMATED|KPF_DARKBG; - kartitems[KITEM_INVINCIBILITY].darkbg = true; - kartitems[KITEM_SPB].darkbg = true; - kartitems[KITEM_THUNDERSHIELD].darkbg = true; - kartitems[KITEM_BUBBLESHIELD].darkbg = true; - kartitems[KITEM_FLAMESHIELD].darkbg = true; + kartitems[KITEM_SPB].hudflags = KPF_DARKBG; + kartitems[KITEM_THUNDERSHIELD].hudflags = KPF_DARKBG; + kartitems[KITEM_BUBBLESHIELD].hudflags = KPF_DARKBG; + kartitems[KITEM_FLAMESHIELD].hudflags = KPF_DARKBG; numkartresults = 0; K_RegisterResult("sneaker", KITEM_SNEAKER, 1, 0); diff --git a/src/k_items.h b/src/k_items.h index d6183a68e..f307f9e3d 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -82,10 +82,14 @@ struct kartitem_t const char *name; consvar_t *altcvar; kartitemgraphics_t graphics[2]; - boolean animated; - boolean darkbg; + UINT8 hudflags; + + // note: could be how we do this? + // int (*oddsOverrideFunc)(player_t *p, int pos, int mashed, boolean spbrush, fixed_t seconddist, int pingame, int pexiting); + // int (*resultOverrideFunc)(player_t *p); }; +// item flags relevant to item rolls and usage enum { KRF_INDIRECTITEM = 1<<0, @@ -93,6 +97,15 @@ enum KRF_COOLDOWNONSTART = 1<<2, KRF_NOTNEAREND = 1<<3, KRF_NOTFORBOTTOM = 1<<4, + KRF_UNIQUE = 1<<5, +}; + +// item flags relevant to patch rendering in the HUD +enum +{ + KPF_ANIMATED = 1<<0, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT) + KPF_DARKBG = 1<<1, // use dark item roulette BG + KPF_COLPATCH2PLAYER = 1<<2, // colourize patch to player }; struct kartresult_t From 34cfb6d2ef71faf5c7adc8734ba7e5f0167da248 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Fri, 7 Nov 2025 04:18:25 -0500 Subject: [PATCH 13/76] (Attempt to) make things a bit more boilerplate * SPB forcing has been generalized as a 'forceme' variable that any item can set to force itself (based on priority) * SPB odds in general have been generalized; the KRF_RUNNERAUGMENT flag lets any item use SPB odds rules, minus the forcing * Items can modify or override their odds with unique odds functions * Odds tables are further indexed by 'item format', an evolution of the kart item type (Legacy, Alternate) system * Distribution debugger has been updated in respect to the fact that any item can force themselves. Red 'FRC' text, including the force priority, appears on any item forcing itself --- src/k_hud.c | 4 +- src/k_items.c | 490 +++++++++++++++++++++++++++++++------------------- src/k_items.h | 43 ++++- src/k_kart.c | 2 +- src/m_menu.c | 4 +- src/p_setup.c | 20 +++ 6 files changed, 364 insertions(+), 199 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 2fd251b6a..1bd01e8a3 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5603,7 +5603,7 @@ static void K_drawDistributionDebugger(void) { kartresult_t *result = &kartresults[i]; INT32 itemodds = K_KartGetItemOdds( - useodds, result, + pdis, stplyr->position, useodds, result, stplyr->distancetofinish, stplyr->distancefromcluster, 0, @@ -5615,7 +5615,7 @@ static void K_drawDistributionDebugger(void) continue; V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(result->type, true, 0)); - V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds)); + V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, result->forceme ? va("\x85" "FRC(%d)" "\x80 ", result->forceme) : va("%d", itemodds)); // Display amount for multi-items if (result->amount > 1) diff --git a/src/k_items.c b/src/k_items.c index 51166b6a3..c8598fa40 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -48,7 +48,7 @@ consvar_t cv_kartdebugitem = CVAR_INIT ("kartdebugitem", "NONE", CV_NETVAR|CV_CH static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}}; consvar_t cv_kartdebugamount = CVAR_INIT ("kartdebugamount", "1", CV_NETVAR|CV_CHEAT, kartdebugamount_cons_t, NULL); -static CV_PossibleValue_t kartitemtype_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; +static CV_PossibleValue_t kartitemtype_cons_t[] = {{KIFRMT_STANDARD, "Legacy"}, {KIFRMT_ALTERN, "Alternative"}, {0, NULL}}; #define NUMKARTODDS (MAXODDS*10) @@ -105,7 +105,11 @@ static void K_RegisterResult(const char *name, kartitemtype_e type, UINT8 amount result->name = name; result->type = type; result->amount = amount; - result->flags = flags; + + kartitemformat_e fmt; + + for (fmt = KIFRMT_STANDARD; fmt < NUMKARTITEMFORMATS; fmt++) + result->flags[fmt] = flags; consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &result->cvar); var->name = name; @@ -127,9 +131,24 @@ kartresult_t *K_GetKartResult(const char *name) return NULL; } +boolean K_IsKartResultAlternate(const char *name) +{ + return (K_GetKartResult(name)->format == KIFRMT_ALTERN); +} + +// Unique odds, per-item basis + +// Alt. Invincibility's odds. +static INT32 K_AltInvinOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, INT32 curodds, UINT8 pos, UINT8 pingame, UINT8 pexiting, UINT32 firstDist, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); + +// SPB odds (Race) +static INT32 K_SPBRaceOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, INT32 curodds, UINT8 pos, UINT8 pingame, UINT8 pexiting, UINT32 firstDist, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); + void K_InitializeItems(void) { numkartitems = 0; + + // Part 1: standard items K_RegisterItem("NONE", false); K_RegisterItem("SNEAKER", false); K_RegisterItem("ROCKETSNEAKER", false); @@ -235,17 +254,27 @@ void K_InitializeItems(void) kartitems[KITEM_BUBBLESHIELD].hudflags = KPF_DARKBG; kartitems[KITEM_FLAMESHIELD].hudflags = KPF_DARKBG; +#define K_ResultSetAltFlags(name, _flags) (K_GetKartResult(name)->flags[KIFRMT_ALTERN] = _flags) + numkartresults = 0; K_RegisterResult("sneaker", KITEM_SNEAKER, 1, 0); K_RegisterResult("rocketsneaker", KITEM_ROCKETSNEAKER, 1, KRF_POWERITEM); + K_RegisterResult("invincibility", KITEM_INVINCIBILITY, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); + + // It's a power item, yes, but we don't want mashing to lessen + // its chances, so we lie to the game's face. + // Start cooldowns are handled in the odds function; don't set the flag here. + // (Also, PLEASE prevent shitty last lap bagging endings.) + K_ResultSetAltFlags("invincibility", KRF_NOTNEAREND); + K_RegisterResult("banana", KITEM_BANANA, 1, KRF_NOTNEAREND|KRF_NOTFORBOTTOM); K_RegisterResult("eggman", KITEM_EGGMAN, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND|KRF_NOTFORBOTTOM); K_RegisterResult("orbinaut", KITEM_ORBINAUT, 1, 0); K_RegisterResult("jawz", KITEM_JAWZ, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); K_RegisterResult("mine", KITEM_MINE, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); K_RegisterResult("ballhog", KITEM_BALLHOG, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); - K_RegisterResult("spb", KITEM_SPB, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_NOTNEAREND); + K_RegisterResult("spb", KITEM_SPB, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_NOTNEAREND|KRF_RUNNERAUGMENT); K_RegisterResult("grow", KITEM_GROW, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); K_RegisterResult("shrink", KITEM_SHRINK, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND); K_RegisterResult("thundershield", KITEM_THUNDERSHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); @@ -265,106 +294,125 @@ void K_InitializeItems(void) K_RegisterResult("quadorbinaut", KITEM_ORBINAUT, 4, KRF_NOTFORBOTTOM|KRF_POWERITEM); K_RegisterResult("dualjawz", KITEM_JAWZ, 2, KRF_NOTFORBOTTOM|KRF_POWERITEM); -#define O(item, ...) memcpy(K_GetKartResult(#item)->odds[WHICH], (UINT8[MAXODDS]){__VA_ARGS__}, MAXODDS); +#define OSTD(item, ...) memcpy(K_GetKartResult(#item)->odds[WHICH][KIFRMT_STANDARD], (UINT8[MAXODDS]){__VA_ARGS__}, MAXODDS); +#define OALT(item, ...) memcpy(K_GetKartResult(#item)->odds[WHICH][KIFRMT_ALTERN], (UINT8[MAXODDS]){__VA_ARGS__}, MAXODDS); + +#define OSTDFUNC(item, func) {K_GetKartResult(#item)->unique_odds[WHICH][KIFRMT_STANDARD] = &func;} +#define OALTFUNC(item, func) {K_GetKartResult(#item)->unique_odds[WHICH][KIFRMT_ALTERN] = &func;} + #define WHICH ODDS_RACE // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - O(sneaker, 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Sneaker - O(rocketsneaker, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37) // Rocket Sneaker - O(invincibility, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 24, 50, 75, 75) // Invincibility - O(banana, 35, 28, 22, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana - O(eggman, 18, 15, 12, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Eggman Monitor - O(orbinaut, 45, 28, 24, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut - O(jawz, 0, 11, 22, 9, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz - O(mine, 0, 0, 5, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Mine - O(ballhog, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Ballhog - O(spb, 0, 0, 2, 3, 4, 5, 6, 9, 10, 12, 8, 5, 2, 2, 0, 0) // Self-Propelled Bomb - O(grow, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 12, 9, 0, 0, 0) // Grow - O(shrink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 3, 0, 0) // Shrink - O(thundershield, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Thunder Shield - O(hyudoro, 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 0, 0, 0, 0, 0, 0) // Hyudoro - O(pogospring, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Pogo Spring - O(kitchensink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Kitchen Sink - O(superring, 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Super Ring - O(landmine, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Land Mine - O(bubbleshield, 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Bubble Shield - O(flameshield, 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0) // Flame Shield - O(dualsneaker, 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0) // Sneaker x2 - O(triplesneaker, 0, 0, 0, 0, 0, 0, 0, 7, 35, 45, 60, 56, 52, 4, 0, 0) // Sneaker x3 - O(triplebanana, 0, 7, 7, 7, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x3 - O(decabanana, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x10 - O(tripleorbinaut, 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x3 - O(quadorbinaut, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x4 - O(dualjawz, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz x2 + OSTD(sneaker, 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Sneaker + OSTD(rocketsneaker, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37) // Rocket Sneaker + + // Invincibility + OSTD(invincibility, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 24, 50, 75, 75) // Standard + OALTFUNC(invincibility, K_AltInvinOdds) // Alternate: uses unique odds + OALT(invincibility, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + OSTD(banana, 35, 28, 22, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana + OSTD(eggman, 18, 15, 12, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Eggman Monitor + OSTD(orbinaut, 45, 28, 24, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut + OSTD(jawz, 0, 11, 22, 9, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz + OSTD(mine, 0, 0, 5, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Mine + OSTD(ballhog, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Ballhog + + // Self-Propelled Bomb + OSTD(spb, 0, 0, 2, 3, 4, 5, 6, 9, 10, 12, 8, 5, 2, 2, 0, 0) + OSTDFUNC(spb, K_SPBRaceOdds) + + OSTD(grow, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 12, 9, 0, 0, 0) // Grow + + // Shrink + OSTD(shrink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 3, 0, 0) // Standard + OALT(shrink, 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Alternate + + OSTD(thundershield, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Thunder Shield + OSTD(hyudoro, 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 0, 0, 0, 0, 0, 0) // Hyudoro + OSTD(pogospring, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Pogo Spring + OSTD(kitchensink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Kitchen Sink + OSTD(superring, 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Super Ring + OSTD(landmine, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Land Mine + OSTD(bubbleshield, 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Bubble Shield + OSTD(flameshield, 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0) // Flame Shield + OSTD(dualsneaker, 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0) // Sneaker x2 + OSTD(triplesneaker, 0, 0, 0, 0, 0, 0, 0, 7, 35, 45, 60, 56, 52, 4, 0, 0) // Sneaker x3 + OSTD(triplebanana, 0, 7, 7, 7, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x3 + OSTD(decabanana, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x10 + OSTD(tripleorbinaut, 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x3 + OSTD(quadorbinaut, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x4 + OSTD(dualjawz, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz x2 //{ 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Egg Mine //{ 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Alt. Shrink #undef WHICH #define WHICH ODDS_BATTLE // R S - O(sneaker, 2, 1) // Sneaker - O(rocketsneaker, 0, 0) // Rocket Sneaker - O(invincibility, 4, 1) // Invincibility - O(banana, 0, 0) // Banana - O(eggman, 1, 0) // Eggman Monitor - O(orbinaut, 8, 0) // Orbinaut - O(jawz, 8, 1) // Jawz - O(mine, 6, 1) // Mine - O(ballhog, 2, 1) // Ballhog - O(spb, 0, 0) // Self-Propelled Bomb - O(grow, 2, 1) // Grow - O(shrink, 0, 0) // Shrink - O(thundershield, 4, 0) // Thunder Shield - O(hyudoro, 2, 0) // Hyudoro - O(pogospring, 3, 0) // Pogo Spring - O(kitchensink, 0, 0) // Kitchen Sink - O(superring, 0, 0) // Super Ring - O(landmine, 2, 0) // Land Mine - O(bubbleshield, 1, 0) // Bubble Shield - O(flameshield, 1, 0) // Flame Shield - O(dualsneaker, 0, 0) // Sneaker x2 - O(triplesneaker, 0, 1) // Sneaker x3 - O(triplebanana, 0, 0) // Banana x3 - O(decabanana, 1, 1) // Banana x10 - O(tripleorbinaut, 2, 0) // Orbinaut x3 - O(quadorbinaut, 1, 1) // Orbinaut x4 - O(dualjawz, 5, 1) // Jawz x2 + OSTD(sneaker, 2, 1) // Sneaker + OSTD(rocketsneaker, 0, 0) // Rocket Sneaker + OSTD(invincibility, 4, 1) // Invincibility + OSTD(banana, 0, 0) // Banana + OSTD(eggman, 1, 0) // Eggman Monitor + OSTD(orbinaut, 8, 0) // Orbinaut + OSTD(jawz, 8, 1) // Jawz + OSTD(mine, 6, 1) // Mine + OSTD(ballhog, 2, 1) // Ballhog + OSTD(spb, 0, 0) // Self-Propelled Bomb + OSTD(grow, 2, 1) // Grow + OSTD(shrink, 0, 0) // Shrink + OSTD(thundershield, 4, 0) // Thunder Shield + OSTD(hyudoro, 2, 0) // Hyudoro + OSTD(pogospring, 3, 0) // Pogo Spring + OSTD(kitchensink, 0, 0) // Kitchen Sink + OSTD(superring, 0, 0) // Super Ring + OSTD(landmine, 2, 0) // Land Mine + OSTD(bubbleshield, 1, 0) // Bubble Shield + OSTD(flameshield, 1, 0) // Flame Shield + OSTD(dualsneaker, 0, 0) // Sneaker x2 + OSTD(triplesneaker, 0, 1) // Sneaker x3 + OSTD(triplebanana, 0, 0) // Banana x3 + OSTD(decabanana, 1, 1) // Banana x10 + OSTD(tripleorbinaut, 2, 0) // Orbinaut x3 + OSTD(quadorbinaut, 1, 1) // Orbinaut x4 + OSTD(dualjawz, 5, 1) // Jawz x2 //{ 2, 0 }, // Egg Mine //{ 1, 0 } // Alt. Shrink #undef WHICH #define WHICH ODDS_SPECIAL // M N O P - O(sneaker, 1, 1, 0, 0) // Sneaker - O(rocketsneaker, 0, 0, 0, 0) // Rocket Sneaker - O(invincibility, 0, 0, 0, 0) // Invincibility - O(banana, 0, 0, 0, 0) // Banana - O(eggman, 0, 0, 0, 0) // Eggman Monitor - O(orbinaut, 1, 1, 0, 0) // Orbinaut - O(jawz, 1, 1, 0, 0) // Jawz - O(mine, 0, 0, 0, 0) // Mine - O(ballhog, 0, 0, 0, 0) // Ballhog - O(spb, 0, 0, 0, 1) // Self-Propelled Bomb - O(grow, 0, 0, 0, 0) // Grow - O(shrink, 0, 0, 0, 0) // Shrink - O(thundershield, 0, 0, 0, 0) // Thunder Shield - O(hyudoro, 0, 0, 0, 0) // Hyudoro - O(pogospring, 0, 0, 0, 0) // Pogo Spring - O(kitchensink, 0, 0, 0, 0) // Kitchen Sink - O(superring, 0, 0, 0, 0) // Super Ring - O(landmine, 0, 0, 0, 0) // Land Mine - O(bubbleshield, 0, 0, 0, 0) // Bubble Shield - O(flameshield, 0, 0, 0, 0) // Flame Shield - O(dualsneaker, 0, 1, 1, 0) // Sneaker x2 - O(triplesneaker, 0, 0, 1, 1) // Sneaker x3 - O(triplebanana, 0, 0, 0, 0) // Banana x3 - O(decabanana, 0, 0, 0, 0) // Banana x10 - O(tripleorbinaut, 0, 1, 1, 0) // Orbinaut x3 - O(quadorbinaut, 0, 0, 1, 1) // Orbinaut x4 - O(dualjawz, 0, 0, 1, 1) // Jawz x2 + OSTD(sneaker, 1, 1, 0, 0) // Sneaker + OSTD(rocketsneaker, 0, 0, 0, 0) // Rocket Sneaker + OSTD(invincibility, 0, 0, 0, 0) // Invincibility + OSTD(banana, 0, 0, 0, 0) // Banana + OSTD(eggman, 0, 0, 0, 0) // Eggman Monitor + OSTD(orbinaut, 1, 1, 0, 0) // Orbinaut + OSTD(jawz, 1, 1, 0, 0) // Jawz + OSTD(mine, 0, 0, 0, 0) // Mine + OSTD(ballhog, 0, 0, 0, 0) // Ballhog + OSTD(spb, 0, 0, 0, 1) // Self-Propelled Bomb + OSTD(grow, 0, 0, 0, 0) // Grow + OSTD(shrink, 0, 0, 0, 0) // Shrink + OSTD(thundershield, 0, 0, 0, 0) // Thunder Shield + OSTD(hyudoro, 0, 0, 0, 0) // Hyudoro + OSTD(pogospring, 0, 0, 0, 0) // Pogo Spring + OSTD(kitchensink, 0, 0, 0, 0) // Kitchen Sink + OSTD(superring, 0, 0, 0, 0) // Super Ring + OSTD(landmine, 0, 0, 0, 0) // Land Mine + OSTD(bubbleshield, 0, 0, 0, 0) // Bubble Shield + OSTD(flameshield, 0, 0, 0, 0) // Flame Shield + OSTD(dualsneaker, 0, 1, 1, 0) // Sneaker x2 + OSTD(triplesneaker, 0, 0, 1, 1) // Sneaker x3 + OSTD(triplebanana, 0, 0, 0, 0) // Banana x3 + OSTD(decabanana, 0, 0, 0, 0) // Banana x10 + OSTD(tripleorbinaut, 0, 1, 1, 0) // Orbinaut x3 + OSTD(quadorbinaut, 0, 0, 1, 1) // Orbinaut x4 + OSTD(dualjawz, 0, 0, 1, 1) // Jawz x2 //{ 0, 0, 0, 0 }, // Egg Mine //{ 0, 0, 0, 0 } // Alt. Shrink #undef WHICH -#undef O +#undef OSTD +#undef OALT // Cooldown time table; contains both base (index 0) and current (index 1) // times. Base times are in seconds, current times are in tics. @@ -424,6 +472,11 @@ boolean K_LegacyOddsMode(void) return (K_UsingLegacyCheckpoints() || (cv_kartforcelegacyodds.value)); } +static boolean K_InStartCooldown(void) +{ + return (leveltime < (30*TICRATE)+starttime); +} + // Magic number distance for use with item roulette tiers #define ACTIVEDISTVAR (K_LegacyOddsMode() ? DISTVAR_LEGACY : DISTVAR) #define ACTIVESPBDIST (K_LegacyOddsMode() ? SPBDISTVAR_LEGACY : SPBDISTVAR) @@ -433,6 +486,11 @@ boolean K_LegacyOddsMode(void) #define ENDDIST (12*ACTIVEDISTVAR) // Distance when the game stops giving you bananas +static boolean K_RaceForceSPB(SINT8 playerpos, UINT32 pdis) +{ + return ((gametyperules & GTR_CIRCUIT) && playerpos == 2 && pdis > (UINT32)SPBFORCEDIST); +} + // 1/21/2025: I hate tiptoeing around the integer limit. // This is at a smaller scale. static UINT32 K_Dist2D(INT32 x1, INT32 y1, INT32 x2, INT32 y2) @@ -705,6 +763,57 @@ void K_KartHandleShieldCooldown(kartitemtype_e item) } } +// Unique odds functions, for REAL this time + +// Alt. Invin. odds +static INT32 K_AltInvinOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, INT32 curodds, UINT8 pos, UINT8 pingame, UINT8 pexiting, UINT32 firstDist, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom) +{ + INT32 odds = K_KartGetInvincibilityOdds(clusterDist); + + // Special case: if you're SERIOUSLY far behind before the cooldown finishes, ignore it and start forcing, + if (odds >= INVFORCEODDS) + { + result->forceme = 3; // Take priority over SPBs + } + else if (K_InStartCooldown()) + { + odds = 0; + } + + odds *= BASEODDSMUL; + + return odds; +} + +// SPB odds +static INT32 K_SPBRaceOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, INT32 curodds, UINT8 pos, UINT8 pingame, UINT8 pexiting, UINT32 firstDist, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom) +{ + INT32 odds = curodds; + + if (!K_LegacyOddsMode() && firstDist < (UINT32)ENDDIST) // No SPB near the end of the race + { + odds = 0; + } + else if (K_LegacyOddsMode() && pexiting > 0) + { + odds = 0; + } + + // No forced SPB in 1v1s, it has to be randomly rolled + if (pingame <= 2) + { + result->forceme = 0; + } + else if (K_RaceForceSPB(playerpos, pdis) + && spbplace == -1 && !indirectitemcooldown) + { + // Force SPB onto 2nd if they get too far behind. + result->forceme = 2; + } + + return odds; +} + /** \brief Item Roulette for Kart \param player player object passed from P_KartPlayerThink @@ -713,7 +822,7 @@ void K_KartHandleShieldCooldown(kartitemtype_e item) */ INT32 K_KartGetItemOdds( - UINT8 pos, const kartresult_t *result, + UINT32 pdis, SINT8 playerpos, UINT8 pos, kartresult_t *result, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, @@ -721,6 +830,7 @@ INT32 K_KartGetItemOdds( { INT32 newodds; INT32 i; + useoddsfunc_t uoddsfunc; UINT8 pingame = 0, pexiting = 0; @@ -728,7 +838,7 @@ INT32 K_KartGetItemOdds( UINT32 firstDist = UINT32_MAX; UINT32 secondToFirst = UINT32_MAX; - UINT8 flags = result->flags; + UINT8 flags = result->flags[result->format]; INT32 shieldtype = KSHIELD_NONE; @@ -770,8 +880,11 @@ INT32 K_KartGetItemOdds( if (gametyperules & GTR_BATTLEODDS) oddsmul = BATTLEODDSMUL; + // Reset forceme + result->forceme = 0; + // TODO: braaap (make a separate table for the current level!) - newodds = result->odds[oddstable][pos]; + newodds = result->odds[oddstable][result->format][pos]; // Blow up the odds with a multiplier. newodds *= oddsmul; @@ -839,100 +952,64 @@ INT32 K_KartGetItemOdds( } } - switch (result->type) + if (result->unique_odds[oddstable][result->format]) { - case KITEM_EGGMAN: - if (K_IsKartItemAlternate(KITEM_EGGMAN)) - flags = KRF_NOTFORBOTTOM; - break; + // This item has unique odds! - case KITEM_SUPERRING: - if ((K_RingsActive() == false)) // No rings rolled if rings are turned off. - { - newodds = 0; - } + //CONS_Printf("Unique odds found. Assigning uoddsfunc..."); + uoddsfunc = (useoddsfunc_t)result->unique_odds[oddstable][result->format]; - break; - - case KITEM_LANDMINE: - // au revoir - newodds = 0; - break; - - case KITEM_INVINCIBILITY: - if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (gametyperules & GTR_RACEODDS)) - { - // It's a power item, yes, but we don't want mashing to lessen - // its chances, so we lie to the game's face. - // Nonetheless, apply the start cooldown. - flags = KRF_COOLDOWNONSTART; - - // Also, PLEASE prevent shitty last lap bagging endings. - flags |= KRF_NOTNEAREND; - - // Unique odds for Invincibility. - newodds = K_KartGetInvincibilityOdds(clusterDist); - - // Special case: if you're SERIOUSLY far behind before the cooldown - // finishes, remove the cooldown flag. - if (newodds >= INVFORCEODDS) - { - flags &= ~KRF_COOLDOWNONSTART; - } - - newodds *= BASEODDSMUL; + //CONS_Printf("Running..."); + newodds = uoddsfunc(result, playerpos, pdis, newodds, pos, pingame, pexiting, firstDist, ourDist, clusterDist, mashed, spbrush, bot, rival, inBottom); + //CONS_Printf("OK!\n"); + } + else + { + switch (result->type) + { + // Alt. Invin. odds are handled in its odds function + // So are SPB odds + case KITEM_EGGMAN: + if (K_IsKartItemAlternate(KITEM_EGGMAN)) + flags = KRF_NOTFORBOTTOM; break; - } - case KITEM_SPB: - if (!K_LegacyOddsMode() && firstDist < (UINT32)ENDDIST) // No SPB near the end of the race - { - newodds = 0; - } - else if (K_LegacyOddsMode() && pexiting > 0) - { - newodds = 0; - } - else - { - const INT32 distFromStart = max(secondToFirst - SPBSTARTDIST, 0); - const INT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const INT32 mulMax = 24; - - INT32 multiplier = (distFromStart * mulMax) / distRange; - - if (multiplier < 0) - multiplier = 0; - if (multiplier > mulMax) - multiplier = mulMax; - - newodds *= multiplier; - } - break; - - case KITEM_SHRINK: - if (!K_IsKartItemAlternate(KITEM_SHRINK)) - { - flags |= KRF_INDIRECTITEM; - - if (pingame-1 <= pexiting) - newodds = 0; - } - else - { - if (rival) + case KITEM_SUPERRING: + if ((K_RingsActive() == false)) // No rings rolled if rings are turned off. { - // Rival bot or already shrunk. DON'T roll another. newodds = 0; } - } - break; - case KITEM_THUNDERSHIELD: - if (spbplace != -1) + + break; + + case KITEM_LANDMINE: + // au revoir newodds = 0; - break; - default: - break; + break; + case KITEM_SHRINK: + if (!K_IsKartItemAlternate(KITEM_SHRINK)) + { + flags |= KRF_INDIRECTITEM; + + if (pingame-1 <= pexiting) + newodds = 0; + } + else + { + if (rival) + { + // Rival bot or already shrunk. DON'T roll another. + newodds = 0; + } + } + break; + case KITEM_THUNDERSHIELD: + if (spbplace != -1) + newodds = 0; + break; + default: + break; + } } // In very small matches, remove the stupid bottom half item limiter @@ -947,6 +1024,23 @@ INT32 K_KartGetItemOdds( return newodds; } + if (flags & KRF_RUNNERAUGMENT) + { + // These odds get stronger as 1st's frontrun increases. + const INT32 distFromStart = max(secondToFirst - SPBSTARTDIST, 0); + const INT32 distRange = SPBFORCEDIST - SPBSTARTDIST; + const INT32 mulMax = 24; + + INT32 multiplier = (distFromStart * mulMax) / distRange; + + if (multiplier < 0) + multiplier = 0; + if (multiplier > mulMax) + multiplier = mulMax; + + newodds *= multiplier; + } + if (result->bgone > 0) { // (Replaces hyubgone) This item is on cooldown; don't let it get rolled. @@ -957,7 +1051,7 @@ INT32 K_KartGetItemOdds( // Too many items that act indirectly in a match can feel kind of bad. newodds = 0; } - else if (flags & KRF_COOLDOWNONSTART && leveltime < (30*TICRATE)+starttime) + else if (flags & KRF_COOLDOWNONSTART && K_InStartCooldown()) { // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) newodds = 0; @@ -1039,7 +1133,7 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b for (j = 0; j < numkartresults; j++) { if (K_KartGetItemOdds( - i, &kartresults[j], + pdis, player->position, i, &kartresults[j], player->distancetofinish, player->distancefromcluster, mashed, @@ -1179,7 +1273,7 @@ INT32 K_GetRollingRouletteItem(player_t *player) if (seen[j] == result->type) break; - if (j == numseen && memcmp(result->odds[oddstable], EMPTYODDS, sizeof(EMPTYODDS))) + if (j == numseen && memcmp(result->odds[oddstable][result->format], EMPTYODDS, sizeof(EMPTYODDS))) translation[roulette_size++] = seen[numseen++] = result->type; } @@ -1400,14 +1494,11 @@ UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrus return pdis; } -static boolean K_CanForceSPB(player_t *player, UINT32 pdis) +static boolean K_BattleForceSPB(player_t *player, UINT32 pdis) { - boolean battlecond, racecond; + boolean battlecond = ((gametyperules & GTR_WANTED) && (gametyperules & GTR_WANTEDSPB) && (mostwanted != -1) && (!K_IsPlayerMostWanted(player))); - battlecond = ((gametyperules & GTR_WANTED) && (gametyperules & GTR_WANTEDSPB) && (mostwanted != -1) && (!K_IsPlayerMostWanted(player))); - racecond = ((gametyperules & GTR_CIRCUIT) && player->position == 2 && pdis > (UINT32)SPBFORCEDIST); - - return ((battlecond) || (racecond)); + return battlecond; } void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) @@ -1594,9 +1685,36 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } // SPECIAL CASE No. 6: - // Force SPB onto 2nd if they get too far behind + // Item forcing; the item with the highest "forceme" priority is the one given. + { + kartresult_t *forceresult; + UINT8 bestforce = 0; + + for (i = 0; i < numkartresults; i++) + { + if ((kartresults[i].forceme) && (kartresults[i].forceme > bestforce)) + { + bestforce = kartresults[i].forceme; + forceresult = &kartresults[i]; + } + } + + if ((bestforce) && (forceresult)) + { + K_KartGetItemResult(player, forceresult->name); + player->itemblink = TICRATE; + player->itemblinkmode = KITEMBLINKMODE_KARMA; + player->itemroulette = KROULETTE_DISABLED; + player->roulettetype = KROULETTETYPE_NORMAL; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_itrolk); + return; + } + } + + // SPECIAL CASE No. 7: // In battle, an SPB is forced onto players to target the "most wanted" player - if (K_CanForceSPB(player, pdis) + if (K_BattleForceSPB(player, pdis) && spbplace == -1 && !indirectitemcooldown && !dontforcespb && K_ItemResultEnabled("spb")) { @@ -1620,7 +1738,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) for (i = 0; i < numkartresults; i++) { spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, &kartresults[i], + pdis, player->position, useodds, &kartresults[i], player->distancetofinish, player->distancefromcluster, mashed, diff --git a/src/k_items.h b/src/k_items.h index f307f9e3d..8fddee016 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -60,6 +60,13 @@ typedef enum MAXKARTITEMS } ATTRPACK kartitemtype_e; +typedef enum +{ + KIFRMT_STANDARD = 0, + KIFRMT_ALTERN, + NUMKARTITEMFORMATS +} ATTRPACK kartitemformat_e; + #define MAXKARTRESULTS 255 typedef enum @@ -92,12 +99,14 @@ struct kartitem_t // item flags relevant to item rolls and usage enum { - KRF_INDIRECTITEM = 1<<0, - KRF_POWERITEM = 1<<1, - KRF_COOLDOWNONSTART = 1<<2, - KRF_NOTNEAREND = 1<<3, - KRF_NOTFORBOTTOM = 1<<4, + KRF_INDIRECTITEM = 1<<0, // This item affects others indirectly. A 20 second cooldown is applied to all other items with this flag when an indirect item is rolled. + KRF_POWERITEM = 1<<1, // This item is a "power item". This activates "frantic item" toggle related functionality. + KRF_COOLDOWNONSTART = 1<<2, // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + KRF_NOTNEAREND = 1<<3, // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) + KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this item stops appearing for losing players. (Usually items that feel less effective at these positions) KRF_UNIQUE = 1<<5, + KRF_RUNNERAUGMENT = 1<<6, // This item's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) + // free: 1<<7 }; // item flags relevant to patch rendering in the HUD @@ -108,14 +117,30 @@ enum KPF_COLPATCH2PLAYER = 1<<2, // colourize patch to player }; +// Unique useodds function: +// uoddsfunc(result, playerpos, pdis, newodds, pos, pingame, pexiting, firstDist, ourDist, clusterDist, mashed, spbrush, bot, rival, inBottom) +// FIXME: This is fucking gross; is there not a better way to do this? +typedef INT32(*useoddsfunc_t)(void *, SINT8, UINT32, INT32, UINT8, UINT8, UINT8, UINT32, UINT32, UINT32, fixed_t, boolean, boolean, boolean, boolean); + struct kartresult_t { const char *name; consvar_t *cvar; + kartitemtype_e type; UINT8 amount; - UINT8 odds[MAXODDSTABLES][MAXODDS]; - UINT8 flags; + + kartitemformat_e format; // An item's format. Different formats have different odds + UINT8 forceme; // This item is being forced into a player's item slot for one reason or another. Higher value = higher priority. + + // 3D array determining item odds, selected by table, then format. + UINT8 odds[MAXODDSTABLES][NUMKARTITEMFORMATS][MAXODDS]; + + // Functions that tell the game to use unique means of rolling these items. + // If NULL, the functions never execute. + void *unique_odds[MAXODDSTABLES][NUMKARTITEMFORMATS]; + + UINT8 flags[NUMKARTITEMFORMATS]; tic_t basebgone, bgone; }; @@ -124,6 +149,8 @@ extern UINT8 numkartitems; extern kartresult_t kartresults[MAXKARTRESULTS]; extern UINT8 numkartresults; +boolean K_IsKartResultAlternate(const char *name); + kartresult_t *K_GetKartResult(const char *name); void K_InitializeItems(void); boolean K_ItemResultEnabled(const char *name); @@ -137,7 +164,7 @@ UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame); UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush); UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); -INT32 K_KartGetItemOdds(UINT8 pos, const kartresult_t *result, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); +INT32 K_KartGetItemOdds(UINT32 pdis, SINT8 playerpos, UINT8 pos, kartresult_t *result, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); INT32 K_GetRollingRouletteItem(player_t *player); void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); diff --git a/src/k_kart.c b/src/k_kart.c index 31f801ec4..ac2eed2d1 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5513,7 +5513,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 for (i = 0; i < numkartresults; i++) { spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, &kartresults[i], + 0, 0, useodds, &kartresults[i], UINT32_MAX, 0, 0, diff --git a/src/m_menu.c b/src/m_menu.c index 02b8ac739..32bd3c578 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -8387,7 +8387,7 @@ void MD_DrawMonitorToggles(void) kartitem_t *item = &kartitems[result->type]; boolean enabled = K_ItemResultEnabled(result->name); translucent = enabled ? 0 : V_TRANSLUCENT; - drawnum = item->animated ? 0 : CLAMP(result->amount, 0, item->graphics[1].numpatches - 1); + drawnum = (item->hudflags & KPF_ANIMATED) ? 0 : CLAMP(result->amount, 0, item->graphics[1].numpatches - 1); if (enabled) V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); @@ -8434,7 +8434,7 @@ void MD_DrawMonitorToggles(void) kartitem_t *item = &kartitems[result->type]; boolean enabled = K_ItemResultEnabled(result->name); translucent = enabled ? 0 : V_TRANSLUCENT; - drawnum = item->animated ? 0 : CLAMP(result->amount, 0, item->graphics[0].numpatches - 1); + drawnum = (item->hudflags & KPF_ANIMATED) ? 0 : CLAMP(result->amount, 0, item->graphics[0].numpatches - 1); if (enabled) V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); diff --git a/src/p_setup.c b/src/p_setup.c index 75b01c62e..d0f165bce 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8077,6 +8077,26 @@ static void P_InitLevelSettings(boolean reloadinggamestate) if (!reloadinggamestate) K_SetupItemOdds(); + for (i = 0; i < numkartitems; i++) + { + const kartitem_t *kitm = &kartitems[i]; + char *findname = malloc(strlen(kitm->name)+1); + + sprintf(findname, "%s", kitm->name); + strlwr(findname); + + kartresult_t *kres = K_GetKartResult(findname); + + if ((kres) && (kitm->altcvar)) + { + // Update the result's format based on the cvar's value. + CONS_Printf("%s: updating format to %s\n", kres->name, kitm->altcvar->string); + kres->format = kitm->altcvar->value; + } + + free(findname); // Don't need this anymore + } + // emerald hunt hunt1 = hunt2 = hunt3 = NULL; From f0b7f3822e028c23a13bcde698b3b37b71d708fd Mon Sep 17 00:00:00 2001 From: Anonimus Date: Fri, 7 Nov 2025 05:19:43 -0500 Subject: [PATCH 14/76] Of course I forget this --- src/k_items.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/k_items.c b/src/k_items.c index c8598fa40..1f109f996 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -414,6 +414,9 @@ void K_InitializeItems(void) #undef OSTD #undef OALT +#undef OSTDFUNC +#undef OALTFUNC + // Cooldown time table; contains both base (index 0) and current (index 1) // times. Base times are in seconds, current times are in tics. #define O(item, v) K_GetKartResult(#item)->basebgone = v; From 21dcf384fb12ed39ffd060853fa150c2c508c002 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Fri, 7 Nov 2025 05:33:55 -0500 Subject: [PATCH 15/76] More last-minute nitpicks * Display forced items in the debugger, no matter their odds * Only update an item's format if there's a mismatch with its cvar --- src/k_hud.c | 2 +- src/p_setup.c | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 1bd01e8a3..08faa0dd4 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5611,7 +5611,7 @@ static void K_drawDistributionDebugger(void) K_IsPlayerLosing(stplyr) ); - if (itemodds <= 0) + if ((itemodds <= 0) && (!result->forceme)) // At the very least display forced items; that info's also important. continue; V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(result->type, true, 0)); diff --git a/src/p_setup.c b/src/p_setup.c index d0f165bce..0ff9f844f 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8090,8 +8090,11 @@ static void P_InitLevelSettings(boolean reloadinggamestate) if ((kres) && (kitm->altcvar)) { // Update the result's format based on the cvar's value. - CONS_Printf("%s: updating format to %s\n", kres->name, kitm->altcvar->string); - kres->format = kitm->altcvar->value; + if (kres->format != kitm->altcvar->value) + { + CONS_Printf("%s: updating format to %s\n", kres->name, kitm->altcvar->string); + kres->format = kitm->altcvar->value; + } } free(findname); // Don't need this anymore From 540978570abbedfa8cfefa43e470169566ec0464 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Fri, 7 Nov 2025 06:20:40 -0500 Subject: [PATCH 16/76] Kill voidptrs --- src/k_items.c | 2 +- src/k_items.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/k_items.c b/src/k_items.c index 1f109f996..cc3829112 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -960,7 +960,7 @@ INT32 K_KartGetItemOdds( // This item has unique odds! //CONS_Printf("Unique odds found. Assigning uoddsfunc..."); - uoddsfunc = (useoddsfunc_t)result->unique_odds[oddstable][result->format]; + uoddsfunc = result->unique_odds[oddstable][result->format]; //CONS_Printf("Running..."); newodds = uoddsfunc(result, playerpos, pdis, newodds, pos, pingame, pexiting, firstDist, ourDist, clusterDist, mashed, spbrush, bot, rival, inBottom); diff --git a/src/k_items.h b/src/k_items.h index 8fddee016..21b6c0dde 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -120,7 +120,7 @@ enum // Unique useodds function: // uoddsfunc(result, playerpos, pdis, newodds, pos, pingame, pexiting, firstDist, ourDist, clusterDist, mashed, spbrush, bot, rival, inBottom) // FIXME: This is fucking gross; is there not a better way to do this? -typedef INT32(*useoddsfunc_t)(void *, SINT8, UINT32, INT32, UINT8, UINT8, UINT8, UINT32, UINT32, UINT32, fixed_t, boolean, boolean, boolean, boolean); +typedef INT32(*useoddsfunc_t)(kartresult_t *, SINT8, UINT32, INT32, UINT8, UINT8, UINT8, UINT32, UINT32, UINT32, fixed_t, boolean, boolean, boolean, boolean); struct kartresult_t { @@ -138,7 +138,7 @@ struct kartresult_t // Functions that tell the game to use unique means of rolling these items. // If NULL, the functions never execute. - void *unique_odds[MAXODDSTABLES][NUMKARTITEMFORMATS]; + useoddsfunc_t unique_odds[MAXODDSTABLES][NUMKARTITEMFORMATS]; UINT8 flags[NUMKARTITEMFORMATS]; tic_t basebgone, bgone; From a2396fbdac40ba74eb6c7cedf6a4e2f1f4a80637 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Fri, 7 Nov 2025 17:58:19 +0100 Subject: [PATCH 17/76] Item refactor, part 2 Clean up alt item stuff, replace the billion parameters with kartroulette_t --- src/d_player.h | 2 +- src/deh_tables.c | 2 +- src/k_botitem.cpp | 2 +- src/k_hud.c | 45 ++-- src/k_items.c | 635 +++++++++++++++++++++++----------------------- src/k_items.h | 104 ++++---- src/k_kart.c | 33 +-- src/k_kart.h | 1 - src/m_menu.c | 10 +- src/p_setup.c | 23 -- src/typedef.h | 1 + 11 files changed, 429 insertions(+), 429 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index a914f59da..230fe9fc9 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -140,7 +140,7 @@ typedef enum typedef enum { KROULETTE_DISABLED, KROULETTE_ACTIVE, -} kartroulette_t; +} kartroulettestate_t; typedef enum { KROULETTETYPE_NORMAL, diff --git a/src/deh_tables.c b/src/deh_tables.c index f714214c2..73f00bb07 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1519,7 +1519,7 @@ struct int_const_s const INT_CONST[] = { {"KSHIELD_FLAME",KSHIELD_FLAME}, {"NUMKARTSHIELDS",NUMKARTSHIELDS}, - // kartroulette_t + // kartroulettestate_t {"KROULETTE_DISABLED",KROULETTE_DISABLED}, {"KROULETTE_ACTIVE",KROULETTE_ACTIVE}, diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index 277d64552..0e9df0107 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -1155,7 +1155,7 @@ static void K_BotItemRouletteMash(botdata_t *bd, const player_t *player) } } - if (player->rings < 0 && K_ItemResultEnabled("superring") && K_RingsActive()) + if (player->rings < 0 && K_ItemResultEnabled(K_GetKartResult("superring"))) { // Uh oh, we need a loan! // It'll be better in the long run for bots to lose an item set for 5-15 free rings. diff --git a/src/k_hud.c b/src/k_hud.c index 08faa0dd4..72b5a1cb3 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -657,7 +657,7 @@ patch_t *K_GetCachedItemPatch(SINT8 type, boolean tiny, UINT8 amount) kartitem_t *item = &kartitems[type]; kartitemgraphics_t *graphics = &item->graphics[tiny ? 1 : 0]; - if ((item->hudflags & KPF_ANIMATED)) + if (item->flags & KIF_ANIMATED) return graphics->patches[(leveltime % (graphics->numpatches*3)) / 3]; else return graphics->patches[CLAMP(amount - 1, 0, graphics->numpatches - 1)]; @@ -1299,8 +1299,8 @@ static void K_drawKartItem(void) if (stplyr->itemtype > 0 && stplyr->itemtype < numkartitems) { kartitem_t *item = &kartitems[stplyr->itemtype]; - numberdisplaymin = (item->hudflags & KPF_ANIMATED) ? 2 : item->graphics[tiny ? 1 : 0].numpatches + 1; - dark = (item->hudflags & KPF_DARKBG); + numberdisplaymin = item->flags & KIF_ANIMATED ? 2 : item->graphics[tiny ? 1 : 0].numpatches + 1; + dark = item->flags & KIF_DARKBG; } if ((stplyr->itemflags & IF_ITEMOUT) && !(leveltime & 1)) @@ -1337,7 +1337,7 @@ static void K_drawKartItem(void) { colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); } - else if ((kartitems[stplyr->itemtype]).hudflags & KPF_COLPATCH2PLAYER) + else if (kartitems[stplyr->itemtype].flags & KIF_COLPATCH2PLAYER) { colmap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); } @@ -5591,7 +5591,7 @@ static void K_drawDistributionDebugger(void) if (pingame == 1) { - if (stplyr->itemroulette && stplyr->cmd.buttons & BT_ATTACK && K_ItemResultEnabled("superring") && K_RingsActive()) + if (stplyr->itemroulette && stplyr->cmd.buttons & BT_ATTACK && K_ItemResultEnabled(K_GetKartResult("superring"))) V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(KITEM_SUPERRING, true, 0)); else V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(KITEM_SNEAKER, true, 0)); @@ -5599,30 +5599,35 @@ static void K_drawDistributionDebugger(void) } else { + INT32 itemodds[MAXKARTRESULTS]; + kartroulette_t roulette = { + .pdis = pdis, + .playerpos = stplyr->position, + .pos = useodds, + .ourDist = stplyr->distancetofinish, + .clusterDist = stplyr->distancefromcluster, + .mashed = 0, + .spbrush = spbrush, + .bot = stplyr->bot, + .rival = stplyr->bot && stplyr->botvars.rival, + .inBottom = K_IsPlayerLosing(stplyr), + }; + + K_KartGetItemOdds(&roulette, itemodds); + for (i = 0; i < numkartresults; i++) { kartresult_t *result = &kartresults[i]; - INT32 itemodds = K_KartGetItemOdds( - pdis, stplyr->position, useodds, result, - stplyr->distancetofinish, - stplyr->distancefromcluster, - 0, - spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival), - K_IsPlayerLosing(stplyr) - ); - if ((itemodds <= 0) && (!result->forceme)) // At the very least display forced items; that info's also important. + if (itemodds[i] <= 0 && !result->forceme) // At the very least display forced items; that info's also important. continue; V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(result->type, true, 0)); - V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, result->forceme ? va("\x85" "FRC(%d)" "\x80 ", result->forceme) : va("%d", itemodds)); - - // Display amount for multi-items + if (result->isalt) + V_DrawScaledPatch(x+2, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, W_CachePatchName("K_ALTITS", PU_CACHE)); + V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, result->forceme ? va("\x85" "FRC(%d)" "\x80 ", result->forceme) : va("%d", itemodds[i])); if (result->amount > 1) - { V_DrawString(x+24, y+31, V_SPLITSCREEN|V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", result->amount)); - } - x += 32; if (x >= 297) diff --git a/src/k_items.c b/src/k_items.c index cc3829112..072e16815 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -48,7 +48,7 @@ consvar_t cv_kartdebugitem = CVAR_INIT ("kartdebugitem", "NONE", CV_NETVAR|CV_CH static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}}; consvar_t cv_kartdebugamount = CVAR_INIT ("kartdebugamount", "1", CV_NETVAR|CV_CHEAT, kartdebugamount_cons_t, NULL); -static CV_PossibleValue_t kartitemtype_cons_t[] = {{KIFRMT_STANDARD, "Legacy"}, {KIFRMT_ALTERN, "Alternative"}, {0, NULL}}; +static CV_PossibleValue_t kartitemvariant_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; #define NUMKARTODDS (MAXODDS*10) @@ -68,30 +68,12 @@ static CV_PossibleValue_t kartitemtype_cons_t[] = {{KIFRMT_STANDARD, "Legacy"}, static UINT8 oddstablelen[MAXODDSTABLES] = { MAXODDS, 2, 4 }; -static void K_RegisterItem(const char *name, boolean hasalt) +static void K_RegisterItem(const char *name, kartitemflags_e flags) { kartitem_t *item = &kartitems[numkartitems++]; item->name = name; - - if (hasalt) - { - char *cvname = malloc(strlen(name)+1 + 8); - sprintf(cvname, "altitem_%s", name); - strlwr(cvname); - - consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &item->altcvar); - var->name = cvname; - var->defaultvalue = "Legacy"; - var->flags = CV_NETVAR/*|CV_CALL*/; - var->PossibleValue = kartitemtype_cons_t; - var->func = NULL; - CV_RegisterVar(var); - } - else - { - item->altcvar = NULL; - } + item->flags = flags; kartdebugitem_cons_t[numkartitems - 1].strvalue = name; kartdebugitem_cons_t[numkartitems - 1].value = numkartitems - 1; @@ -99,77 +81,100 @@ static void K_RegisterItem(const char *name, boolean hasalt) kartdebugitem_cons_t[numkartitems].value = 0; } -static void K_RegisterResult(const char *name, kartitemtype_e type, UINT8 amount, UINT8 flags) +static kartresult_t *K_RegisterResult(const char *name, kartitemtype_e type, UINT8 amount, UINT8 flags) { - kartresult_t *result = &kartresults[numkartresults++]; - result->name = name; - result->type = type; - result->amount = amount; + kartresult_t *result = &kartresults[numkartresults]; + result->flags = flags; - kartitemformat_e fmt; + // check if a result with this name already exists + kartresult_t *prev = K_GetKartResult(name); + if (prev != NULL) + { + // link to it as an alternate item + result->cvar = prev->cvar; + result->type = prev->type; + result->amount = prev->amount; + result->isalt = true; - for (fmt = KIFRMT_STANDARD; fmt < NUMKARTITEMFORMATS; fmt++) - result->flags[fmt] = flags; + // check if the kartitem needs to have its alt cvar registered + kartitem_t *item = &kartitems[result->type]; + if (item->altcvar == NULL) + { + char *cvname = malloc(strlen(item->name)+1 + 8); + sprintf(cvname, "altitem_%s", item->name); + strlwr(cvname); - consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &result->cvar); - var->name = name; - var->defaultvalue = "On"; - var->flags = CV_NETVAR|CV_CHEAT; - var->PossibleValue = CV_OnOff; - var->func = NULL; - CV_RegisterVar(var); + consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &item->altcvar); + var->name = cvname; + var->defaultvalue = "Legacy"; + var->flags = CV_NETVAR/*|CV_CALL*/; + var->PossibleValue = kartitemvariant_cons_t; + var->func = NULL; + CV_RegisterVar(var); + } + } + else + { + result->type = type; + result->amount = amount; + result->isalt = false; + + consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &result->cvar); + var->name = name; + var->defaultvalue = "On"; + var->flags = CV_NETVAR|CV_CHEAT; + var->PossibleValue = CV_OnOff; + var->func = NULL; + CV_RegisterVar(var); + } + + numkartresults++; + return result; } kartresult_t *K_GetKartResult(const char *name) { for (UINT8 i = 0; i < numkartresults; i++) { - if (!strcmp(name, kartresults[i].name)) + if (!strcmp(name, kartresults[i].cvar->name)) return &kartresults[i]; } return NULL; } -boolean K_IsKartResultAlternate(const char *name) -{ - return (K_GetKartResult(name)->format == KIFRMT_ALTERN); -} - // Unique odds, per-item basis // Alt. Invincibility's odds. -static INT32 K_AltInvinOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, INT32 curodds, UINT8 pos, UINT8 pingame, UINT8 pexiting, UINT32 firstDist, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); +static useoddsfunc_f K_AltInvinOdds; // SPB odds (Race) -static INT32 K_SPBRaceOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, INT32 curodds, UINT8 pos, UINT8 pingame, UINT8 pexiting, UINT32 firstDist, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); +static useoddsfunc_f K_SPBRaceOdds; void K_InitializeItems(void) { numkartitems = 0; - - // Part 1: standard items - K_RegisterItem("NONE", false); - K_RegisterItem("SNEAKER", false); - K_RegisterItem("ROCKETSNEAKER", false); - K_RegisterItem("INVINCIBILITY", true); - K_RegisterItem("BANANA", false); - K_RegisterItem("EGGMAN", true); - K_RegisterItem("ORBINAUT", false); - K_RegisterItem("JAWZ", false); - K_RegisterItem("MINE", false); - K_RegisterItem("BALLHOG", false); - K_RegisterItem("SPB", false); - K_RegisterItem("GROW", false); - K_RegisterItem("SHRINK", true); - K_RegisterItem("THUNDERSHIELD", false); - K_RegisterItem("HYUDORO", false); - K_RegisterItem("POGOSPRING", false); - K_RegisterItem("KITCHENSINK", false); - K_RegisterItem("SUPERRING", false); - K_RegisterItem("LANDMINE", false); - K_RegisterItem("BUBBLESHIELD", false); - K_RegisterItem("FLAMESHIELD", false); + K_RegisterItem("NONE", 0); + K_RegisterItem("SNEAKER", 0); + K_RegisterItem("ROCKETSNEAKER", 0); + K_RegisterItem("INVINCIBILITY", KIF_ANIMATED|KIF_DARKBG); + K_RegisterItem("BANANA", 0); + K_RegisterItem("EGGMAN", 0); + K_RegisterItem("ORBINAUT", 0); + K_RegisterItem("JAWZ", 0); + K_RegisterItem("MINE", 0); + K_RegisterItem("BALLHOG", 0); + K_RegisterItem("SPB", KIF_DARKBG); + K_RegisterItem("GROW", 0); + K_RegisterItem("SHRINK", 0); + K_RegisterItem("THUNDERSHIELD", KIF_DARKBG); + K_RegisterItem("HYUDORO", 0); + K_RegisterItem("POGOSPRING", 0); + K_RegisterItem("KITCHENSINK", 0); + K_RegisterItem("SUPERRING", 0); + K_RegisterItem("LANDMINE", 0); + K_RegisterItem("BUBBLESHIELD", KIF_DARKBG); + K_RegisterItem("FLAMESHIELD", KIF_DARKBG); #define A(item, amount, samount) \ kartitems[item].graphics[0].numpatches = amount; \ @@ -247,36 +252,19 @@ void K_InitializeItems(void) ONE(KITEM_FLAMESHIELD, "K_ITFLMS", "K_ISFLMS"); #undef ONE - kartitems[KITEM_INVINCIBILITY].hudflags = KPF_ANIMATED|KPF_DARKBG; - - kartitems[KITEM_SPB].hudflags = KPF_DARKBG; - kartitems[KITEM_THUNDERSHIELD].hudflags = KPF_DARKBG; - kartitems[KITEM_BUBBLESHIELD].hudflags = KPF_DARKBG; - kartitems[KITEM_FLAMESHIELD].hudflags = KPF_DARKBG; - -#define K_ResultSetAltFlags(name, _flags) (K_GetKartResult(name)->flags[KIFRMT_ALTERN] = _flags) - numkartresults = 0; K_RegisterResult("sneaker", KITEM_SNEAKER, 1, 0); K_RegisterResult("rocketsneaker", KITEM_ROCKETSNEAKER, 1, KRF_POWERITEM); - K_RegisterResult("invincibility", KITEM_INVINCIBILITY, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); - - // It's a power item, yes, but we don't want mashing to lessen - // its chances, so we lie to the game's face. - // Start cooldowns are handled in the odds function; don't set the flag here. - // (Also, PLEASE prevent shitty last lap bagging endings.) - K_ResultSetAltFlags("invincibility", KRF_NOTNEAREND); - K_RegisterResult("banana", KITEM_BANANA, 1, KRF_NOTNEAREND|KRF_NOTFORBOTTOM); K_RegisterResult("eggman", KITEM_EGGMAN, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND|KRF_NOTFORBOTTOM); K_RegisterResult("orbinaut", KITEM_ORBINAUT, 1, 0); K_RegisterResult("jawz", KITEM_JAWZ, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); K_RegisterResult("mine", KITEM_MINE, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); K_RegisterResult("ballhog", KITEM_BALLHOG, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); - K_RegisterResult("spb", KITEM_SPB, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_NOTNEAREND|KRF_RUNNERAUGMENT); + kartresult_t *spb = K_RegisterResult("spb", KITEM_SPB, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_NOTNEAREND|KRF_RUNNERAUGMENT); K_RegisterResult("grow", KITEM_GROW, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); - K_RegisterResult("shrink", KITEM_SHRINK, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND); + K_RegisterResult("shrink", KITEM_SHRINK, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_POWERITEM|KRF_NOTNEAREND); K_RegisterResult("thundershield", KITEM_THUNDERSHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); K_RegisterResult("hyudoro", KITEM_HYUDORO, 1, KRF_COOLDOWNONSTART); K_RegisterResult("pogospring", KITEM_POGOSPRING, 1, 0); @@ -294,128 +282,133 @@ void K_InitializeItems(void) K_RegisterResult("quadorbinaut", KITEM_ORBINAUT, 4, KRF_NOTFORBOTTOM|KRF_POWERITEM); K_RegisterResult("dualjawz", KITEM_JAWZ, 2, KRF_NOTFORBOTTOM|KRF_POWERITEM); -#define OSTD(item, ...) memcpy(K_GetKartResult(#item)->odds[WHICH][KIFRMT_STANDARD], (UINT8[MAXODDS]){__VA_ARGS__}, MAXODDS); -#define OALT(item, ...) memcpy(K_GetKartResult(#item)->odds[WHICH][KIFRMT_ALTERN], (UINT8[MAXODDS]){__VA_ARGS__}, MAXODDS); + // alt items defined below (it's ass, but this will do until everything is SOCced) -#define OSTDFUNC(item, func) {K_GetKartResult(#item)->unique_odds[WHICH][KIFRMT_STANDARD] = &func;} -#define OALTFUNC(item, func) {K_GetKartResult(#item)->unique_odds[WHICH][KIFRMT_ALTERN] = &func;} + // It's a power item, yes, but we don't want mashing to lessen + // its chances, so we lie to the game's face. + // Start cooldowns are handled in the odds function; don't set the flag here. + // (Also, PLEASE prevent shitty last lap bagging endings.) + kartresult_t *altinv = K_RegisterResult("invincibility", 0, 0, KRF_NOTNEAREND); + kartresult_t *altshrink = K_RegisterResult("shrink", 0, 0, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND); + + kartresult_t *eggmine = K_RegisterResult("eggman", 0, 0, KRF_NOTFORBOTTOM); + +#define O(item, ...) memcpy(K_GetKartResult(#item)->odds[WHICH], (UINT8[MAXODDS]){__VA_ARGS__}, MAXODDS); #define WHICH ODDS_RACE // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - OSTD(sneaker, 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Sneaker - OSTD(rocketsneaker, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37) // Rocket Sneaker + O(sneaker, 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Sneaker + O(rocketsneaker, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37) // Rocket Sneaker + O(invincibility, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 24, 50, 75, 75) // Invincibility + O(banana, 35, 28, 22, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana + O(eggman, 18, 15, 12, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Eggman Monitor + O(orbinaut, 45, 28, 24, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut + O(jawz, 0, 11, 22, 9, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz + O(mine, 0, 0, 5, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Mine + O(ballhog, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Ballhog + O(spb, 0, 0, 2, 3, 4, 5, 6, 9, 10, 12, 8, 5, 2, 2, 0, 0) // Self-Propelled Bomb + O(grow, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 12, 9, 0, 0, 0) // Grow + O(shrink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 3, 0, 0) // Shrink + O(thundershield, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Thunder Shield + O(hyudoro, 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 0, 0, 0, 0, 0, 0) // Hyudoro + O(pogospring, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Pogo Spring + O(kitchensink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Kitchen Sink + O(superring, 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Super Ring + O(landmine, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Land Mine + O(bubbleshield, 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Bubble Shield + O(flameshield, 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0) // Flame Shield + O(dualsneaker, 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0) // Sneaker x2 + O(triplesneaker, 0, 0, 0, 0, 0, 0, 0, 7, 35, 45, 60, 56, 52, 4, 0, 0) // Sneaker x3 + O(triplebanana, 0, 7, 7, 7, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x3 + O(decabanana, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x10 + O(tripleorbinaut, 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x3 + O(quadorbinaut, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x4 + O(dualjawz, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz x2 + memcpy(altinv->odds[WHICH], (UINT8[MAXODDS]) // Alt. Invincibility + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MAXODDS); + memcpy(altshrink->odds[WHICH], (UINT8[MAXODDS]) // Alt. Shrink + { 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MAXODDS); + memcpy(eggmine->odds[WHICH], (UINT8[MAXODDS]) // Egg Mine + { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MAXODDS); - // Invincibility - OSTD(invincibility, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 24, 50, 75, 75) // Standard - OALTFUNC(invincibility, K_AltInvinOdds) // Alternate: uses unique odds - OALT(invincibility, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - - OSTD(banana, 35, 28, 22, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana - OSTD(eggman, 18, 15, 12, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Eggman Monitor - OSTD(orbinaut, 45, 28, 24, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut - OSTD(jawz, 0, 11, 22, 9, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz - OSTD(mine, 0, 0, 5, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Mine - OSTD(ballhog, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Ballhog - - // Self-Propelled Bomb - OSTD(spb, 0, 0, 2, 3, 4, 5, 6, 9, 10, 12, 8, 5, 2, 2, 0, 0) - OSTDFUNC(spb, K_SPBRaceOdds) - - OSTD(grow, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 12, 9, 0, 0, 0) // Grow - - // Shrink - OSTD(shrink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 3, 0, 0) // Standard - OALT(shrink, 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Alternate - - OSTD(thundershield, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Thunder Shield - OSTD(hyudoro, 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 0, 0, 0, 0, 0, 0) // Hyudoro - OSTD(pogospring, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Pogo Spring - OSTD(kitchensink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Kitchen Sink - OSTD(superring, 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Super Ring - OSTD(landmine, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Land Mine - OSTD(bubbleshield, 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Bubble Shield - OSTD(flameshield, 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0) // Flame Shield - OSTD(dualsneaker, 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0) // Sneaker x2 - OSTD(triplesneaker, 0, 0, 0, 0, 0, 0, 0, 7, 35, 45, 60, 56, 52, 4, 0, 0) // Sneaker x3 - OSTD(triplebanana, 0, 7, 7, 7, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x3 - OSTD(decabanana, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x10 - OSTD(tripleorbinaut, 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x3 - OSTD(quadorbinaut, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x4 - OSTD(dualjawz, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz x2 - //{ 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Egg Mine - //{ 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Alt. Shrink + altinv->unique_odds[WHICH] = K_AltInvinOdds; // Alternate: uses unique odds + spb->unique_odds[WHICH] = K_SPBRaceOdds; #undef WHICH #define WHICH ODDS_BATTLE // R S - OSTD(sneaker, 2, 1) // Sneaker - OSTD(rocketsneaker, 0, 0) // Rocket Sneaker - OSTD(invincibility, 4, 1) // Invincibility - OSTD(banana, 0, 0) // Banana - OSTD(eggman, 1, 0) // Eggman Monitor - OSTD(orbinaut, 8, 0) // Orbinaut - OSTD(jawz, 8, 1) // Jawz - OSTD(mine, 6, 1) // Mine - OSTD(ballhog, 2, 1) // Ballhog - OSTD(spb, 0, 0) // Self-Propelled Bomb - OSTD(grow, 2, 1) // Grow - OSTD(shrink, 0, 0) // Shrink - OSTD(thundershield, 4, 0) // Thunder Shield - OSTD(hyudoro, 2, 0) // Hyudoro - OSTD(pogospring, 3, 0) // Pogo Spring - OSTD(kitchensink, 0, 0) // Kitchen Sink - OSTD(superring, 0, 0) // Super Ring - OSTD(landmine, 2, 0) // Land Mine - OSTD(bubbleshield, 1, 0) // Bubble Shield - OSTD(flameshield, 1, 0) // Flame Shield - OSTD(dualsneaker, 0, 0) // Sneaker x2 - OSTD(triplesneaker, 0, 1) // Sneaker x3 - OSTD(triplebanana, 0, 0) // Banana x3 - OSTD(decabanana, 1, 1) // Banana x10 - OSTD(tripleorbinaut, 2, 0) // Orbinaut x3 - OSTD(quadorbinaut, 1, 1) // Orbinaut x4 - OSTD(dualjawz, 5, 1) // Jawz x2 - //{ 2, 0 }, // Egg Mine - //{ 1, 0 } // Alt. Shrink + O(sneaker, 2, 1) // Sneaker + O(rocketsneaker, 0, 0) // Rocket Sneaker + O(invincibility, 4, 1) // Invincibility + O(banana, 0, 0) // Banana + O(eggman, 1, 0) // Eggman Monitor + O(orbinaut, 8, 0) // Orbinaut + O(jawz, 8, 1) // Jawz + O(mine, 6, 1) // Mine + O(ballhog, 2, 1) // Ballhog + O(spb, 0, 0) // Self-Propelled Bomb + O(grow, 2, 1) // Grow + O(shrink, 0, 0) // Shrink + O(thundershield, 4, 0) // Thunder Shield + O(hyudoro, 2, 0) // Hyudoro + O(pogospring, 3, 0) // Pogo Spring + O(kitchensink, 0, 0) // Kitchen Sink + O(superring, 0, 0) // Super Ring + O(landmine, 2, 0) // Land Mine + O(bubbleshield, 1, 0) // Bubble Shield + O(flameshield, 1, 0) // Flame Shield + O(dualsneaker, 0, 0) // Sneaker x2 + O(triplesneaker, 0, 1) // Sneaker x3 + O(triplebanana, 0, 0) // Banana x3 + O(decabanana, 1, 1) // Banana x10 + O(tripleorbinaut, 2, 0) // Orbinaut x3 + O(quadorbinaut, 1, 1) // Orbinaut x4 + O(dualjawz, 5, 1) // Jawz x2 + memcpy(altinv->odds[WHICH], (UINT8[MAXODDS]) // Alt. Invincibility + { 0, 0}, MAXODDS); + memcpy(altshrink->odds[WHICH], (UINT8[MAXODDS]) // Alt. Shrink + { 1, 0}, MAXODDS); + memcpy(eggmine->odds[WHICH], (UINT8[MAXODDS]) // Egg Mine + { 2, 0}, MAXODDS); #undef WHICH #define WHICH ODDS_SPECIAL // M N O P - OSTD(sneaker, 1, 1, 0, 0) // Sneaker - OSTD(rocketsneaker, 0, 0, 0, 0) // Rocket Sneaker - OSTD(invincibility, 0, 0, 0, 0) // Invincibility - OSTD(banana, 0, 0, 0, 0) // Banana - OSTD(eggman, 0, 0, 0, 0) // Eggman Monitor - OSTD(orbinaut, 1, 1, 0, 0) // Orbinaut - OSTD(jawz, 1, 1, 0, 0) // Jawz - OSTD(mine, 0, 0, 0, 0) // Mine - OSTD(ballhog, 0, 0, 0, 0) // Ballhog - OSTD(spb, 0, 0, 0, 1) // Self-Propelled Bomb - OSTD(grow, 0, 0, 0, 0) // Grow - OSTD(shrink, 0, 0, 0, 0) // Shrink - OSTD(thundershield, 0, 0, 0, 0) // Thunder Shield - OSTD(hyudoro, 0, 0, 0, 0) // Hyudoro - OSTD(pogospring, 0, 0, 0, 0) // Pogo Spring - OSTD(kitchensink, 0, 0, 0, 0) // Kitchen Sink - OSTD(superring, 0, 0, 0, 0) // Super Ring - OSTD(landmine, 0, 0, 0, 0) // Land Mine - OSTD(bubbleshield, 0, 0, 0, 0) // Bubble Shield - OSTD(flameshield, 0, 0, 0, 0) // Flame Shield - OSTD(dualsneaker, 0, 1, 1, 0) // Sneaker x2 - OSTD(triplesneaker, 0, 0, 1, 1) // Sneaker x3 - OSTD(triplebanana, 0, 0, 0, 0) // Banana x3 - OSTD(decabanana, 0, 0, 0, 0) // Banana x10 - OSTD(tripleorbinaut, 0, 1, 1, 0) // Orbinaut x3 - OSTD(quadorbinaut, 0, 0, 1, 1) // Orbinaut x4 - OSTD(dualjawz, 0, 0, 1, 1) // Jawz x2 - //{ 0, 0, 0, 0 }, // Egg Mine - //{ 0, 0, 0, 0 } // Alt. Shrink + O(sneaker, 1, 1, 0, 0) // Sneaker + O(rocketsneaker, 0, 0, 0, 0) // Rocket Sneaker + O(invincibility, 0, 0, 0, 0) // Invincibility + O(banana, 0, 0, 0, 0) // Banana + O(eggman, 0, 0, 0, 0) // Eggman Monitor + O(orbinaut, 1, 1, 0, 0) // Orbinaut + O(jawz, 1, 1, 0, 0) // Jawz + O(mine, 0, 0, 0, 0) // Mine + O(ballhog, 0, 0, 0, 0) // Ballhog + O(spb, 0, 0, 0, 1) // Self-Propelled Bomb + O(grow, 0, 0, 0, 0) // Grow + O(shrink, 0, 0, 0, 0) // Shrink + O(thundershield, 0, 0, 0, 0) // Thunder Shield + O(hyudoro, 0, 0, 0, 0) // Hyudoro + O(pogospring, 0, 0, 0, 0) // Pogo Spring + O(kitchensink, 0, 0, 0, 0) // Kitchen Sink + O(superring, 0, 0, 0, 0) // Super Ring + O(landmine, 0, 0, 0, 0) // Land Mine + O(bubbleshield, 0, 0, 0, 0) // Bubble Shield + O(flameshield, 0, 0, 0, 0) // Flame Shield + O(dualsneaker, 0, 1, 1, 0) // Sneaker x2 + O(triplesneaker, 0, 0, 1, 1) // Sneaker x3 + O(triplebanana, 0, 0, 0, 0) // Banana x3 + O(decabanana, 0, 0, 0, 0) // Banana x10 + O(tripleorbinaut, 0, 1, 1, 0) // Orbinaut x3 + O(quadorbinaut, 0, 0, 1, 1) // Orbinaut x4 + O(dualjawz, 0, 0, 1, 1) // Jawz x2 + memcpy(altinv->odds[WHICH], (UINT8[MAXODDS]) // Alt. Invincibility + { 0, 0, 0, 0}, MAXODDS); + memcpy(altshrink->odds[WHICH], (UINT8[MAXODDS]) // Alt. Shrink + { 0, 0, 0, 0}, MAXODDS); + memcpy(eggmine->odds[WHICH], (UINT8[MAXODDS]) // Egg Mine + { 0, 0, 0, 0}, MAXODDS); #undef WHICH -#undef OSTD -#undef OALT - -#undef OSTDFUNC -#undef OALTFUNC +#undef O // Cooldown time table; contains both base (index 0) and current (index 1) // times. Base times are in seconds, current times are in tics. @@ -447,8 +440,9 @@ void K_InitializeItems(void) O(tripleorbinaut, 10) // Orbinaut x3 O(quadorbinaut, 20) // Orbinaut x4 O(dualjawz, 10) // Jawz x2 - //O(, 0) // Egg Mine - //O(, 5) // Alt. Shrink + altinv->basebgone = 0; // Alt. Invincibility + altshrink->basebgone = 5; // Alt. Shrink + eggmine->basebgone = 0; // Egg Mine #undef O CV_RegisterVar(&cv_kartdebugitem); @@ -457,13 +451,32 @@ void K_InitializeItems(void) void K_SetupItemOdds(void) { - // TODO: something + // Update the item's variant based on the cvar's value. + for (kartitemtype_e i = 0; i < numkartitems; i++) + { + kartitem_t *item = &kartitems[i]; + if (item->altcvar != NULL && item->altenabled != !!item->altcvar->value) + { + CONS_Printf("KITEM_%s: updating variant to %s\n", item->name, item->altcvar->string); + item->altenabled = !!item->altcvar->value; + } + } } -boolean K_ItemResultEnabled(const char *name) +boolean K_ItemResultEnabled(const kartresult_t *result) { - const kartresult_t *result = K_GetKartResult(name); - return result != NULL && result->cvar->value == 1; + if (result == NULL) + return false; + + if (result->type == KITEM_SUPERRING && !K_RingsActive()) + return false; + + return result->cvar->value == 1; +} + +boolean K_IsKartItemAlternate(kartitemtype_e itemtype) +{ + return itemtype >= 0 && itemtype < numkartitems && kartitems[itemtype].altenabled; } /** \brief Are the odds in legacy distancing mode? @@ -552,7 +565,7 @@ static UINT32 K_IntDistanceForMap(fixed_t curx, \return Don't double for this item? */ -static boolean K_DontDoubleMyItems(kartresult_t *result) +static boolean K_DontDoubleMyItems(const kartresult_t *result) { return result->type == KITEM_BALLHOG || result->type == KITEM_SPB || result->type == KITEM_INVINCIBILITY || result->type == KITEM_GROW @@ -566,16 +579,8 @@ static boolean K_DontDoubleMyItems(kartresult_t *result) || result->type >= KITEM_FIRSTFREESLOT; // TODO: excludes custom items for now } -/** \brief Item Roulette for Kart - - \param player player - \param getitem what item we're looking for - - \return void -*/ -static void K_KartGetItemResult(player_t *player, const char *name) +static void K_AwardPlayerItem(player_t *player, kartresult_t *result) { - kartresult_t *result = K_GetKartResult(name); K_BotResetItemConfirm(player, true); if (result == NULL) @@ -597,7 +602,7 @@ static void K_KartGetItemResult(player_t *player, const char *name) player->itemtype = result->type; UINT8 itemamount = result->amount; - if (K_IsAltShrunk(player) && (!K_DontDoubleMyItems(result))) + if (K_IsAltShrunk(player) && !K_DontDoubleMyItems(result)) { itemamount *= 2; } @@ -769,9 +774,9 @@ void K_KartHandleShieldCooldown(kartitemtype_e item) // Unique odds functions, for REAL this time // Alt. Invin. odds -static INT32 K_AltInvinOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, INT32 curodds, UINT8 pos, UINT8 pingame, UINT8 pexiting, UINT32 firstDist, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom) +static INT32 K_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result) { - INT32 odds = K_KartGetInvincibilityOdds(clusterDist); + odds = K_KartGetInvincibilityOdds(roulette->clusterDist); // Special case: if you're SERIOUSLY far behind before the cooldown finishes, ignore it and start forcing, if (odds >= INVFORCEODDS) @@ -789,25 +794,23 @@ static INT32 K_AltInvinOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, } // SPB odds -static INT32 K_SPBRaceOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, INT32 curodds, UINT8 pos, UINT8 pingame, UINT8 pexiting, UINT32 firstDist, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom) +static INT32 K_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result) { - INT32 odds = curodds; - - if (!K_LegacyOddsMode() && firstDist < (UINT32)ENDDIST) // No SPB near the end of the race + if (!K_LegacyOddsMode() && roulette->firstDist < (UINT32)ENDDIST) // No SPB near the end of the race { odds = 0; } - else if (K_LegacyOddsMode() && pexiting > 0) + else if (K_LegacyOddsMode() && roulette->pexiting > 0) { odds = 0; } // No forced SPB in 1v1s, it has to be randomly rolled - if (pingame <= 2) + if (roulette->pingame <= 2) { result->forceme = 0; } - else if (K_RaceForceSPB(playerpos, pdis) + else if (K_RaceForceSPB(roulette->playerpos, roulette->pdis) && spbplace == -1 && !indirectitemcooldown) { // Force SPB onto 2nd if they get too far behind. @@ -817,39 +820,24 @@ static INT32 K_SPBRaceOdds(kartresult_t *result, SINT8 playerpos, UINT32 pdis, I return odds; } -/** \brief Item Roulette for Kart - - \param player player object passed from P_KartPlayerThink - - \return void -*/ - -INT32 K_KartGetItemOdds( - UINT32 pdis, SINT8 playerpos, UINT8 pos, kartresult_t *result, - UINT32 ourDist, - UINT32 clusterDist, - fixed_t mashed, - boolean spbrush, boolean bot, boolean rival, boolean inBottom) +static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result) { INT32 newodds; INT32 i; - useoddsfunc_t uoddsfunc; - - UINT8 pingame = 0, pexiting = 0; SINT8 first = -1, second = -1; - UINT32 firstDist = UINT32_MAX; UINT32 secondToFirst = UINT32_MAX; - UINT8 flags = result->flags[result->format]; + UINT8 flags = result->flags; - INT32 shieldtype = KSHIELD_NONE; - - if (!K_ItemResultEnabled(result->name) && !modeattacking) + if (result->cvar->value == 0 && !modeattacking) return 0; + if (kartitems[result->type].altenabled != result->isalt) + return 0; // wrong alt item result + /* - if (bot) + if (roulette->bot) { // TODO: Item use on bots should all be passed-in functions. // Instead of manually inserting these, it should return 0 @@ -864,7 +852,6 @@ INT32 K_KartGetItemOdds( } } */ - (void)bot; INT32 oddsmul = BASEODDSMUL; @@ -878,7 +865,7 @@ INT32 K_KartGetItemOdds( else oddstable = ODDS_SPECIAL; - I_Assert(pos < oddstablelen[oddstable]); // DO NOT allow positions past the bounds of the table + I_Assert(roulette->pos < oddstablelen[oddstable]); // DO NOT allow positions past the bounds of the table if (gametyperules & GTR_BATTLEODDS) oddsmul = BATTLEODDSMUL; @@ -887,12 +874,16 @@ INT32 K_KartGetItemOdds( result->forceme = 0; // TODO: braaap (make a separate table for the current level!) - newodds = result->odds[oddstable][result->format][pos]; + newodds = result->odds[oddstable][roulette->pos]; // Blow up the odds with a multiplier. newodds *= oddsmul; - shieldtype = K_GetShieldFromItem(result->type); + INT32 shieldtype = K_GetShieldFromItem(result->type); + + roulette->pexiting = 0; + roulette->pingame = 0; + roulette->firstDist = UINT32_MAX; for (i = 0; i < MAXPLAYERS; i++) { @@ -900,10 +891,10 @@ INT32 K_KartGetItemOdds( continue; if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) - pingame++; + roulette->pingame++; if (players[i].exiting) - pexiting++; + roulette->pexiting++; if (shieldtype != KSHIELD_NONE && ((shieldtype == K_GetShieldFromItem(players[i].itemtype)) || (shieldtype == K_GetShieldFromPlayer(&players[i])))) @@ -926,16 +917,16 @@ INT32 K_KartGetItemOdds( if (!K_LegacyOddsMode()) { - firstDist = players[first].distancetofinish; + roulette->firstDist = players[first].distancetofinish; if (mapobjectscale != FRACUNIT) { - firstDist = FixedDiv(firstDist * FRACUNIT, mapobjectscale) / FRACUNIT; + roulette->firstDist = FixedDiv(roulette->firstDist * FRACUNIT, mapobjectscale) / FRACUNIT; } secondToFirst = K_ScaleItemDistance( players[second].distancetofinish - players[first].distancetofinish, - pingame, spbrush + roulette->pingame, roulette->spbrush ); } else @@ -950,20 +941,20 @@ INT32 K_KartGetItemOdds( secondToFirst = K_ScaleItemDistance( secondToFirst, - pingame, spbrush + roulette->pingame, roulette->spbrush ); } } - if (result->unique_odds[oddstable][result->format]) + if (result->unique_odds[oddstable]) { // This item has unique odds! //CONS_Printf("Unique odds found. Assigning uoddsfunc..."); - uoddsfunc = result->unique_odds[oddstable][result->format]; + useoddsfunc_f *uoddsfunc = result->unique_odds[oddstable]; //CONS_Printf("Running..."); - newodds = uoddsfunc(result, playerpos, pdis, newodds, pos, pingame, pexiting, firstDist, ourDist, clusterDist, mashed, spbrush, bot, rival, inBottom); + newodds = uoddsfunc(newodds, roulette, result); //CONS_Printf("OK!\n"); } else @@ -972,19 +963,6 @@ INT32 K_KartGetItemOdds( { // Alt. Invin. odds are handled in its odds function // So are SPB odds - case KITEM_EGGMAN: - if (K_IsKartItemAlternate(KITEM_EGGMAN)) - flags = KRF_NOTFORBOTTOM; - break; - - case KITEM_SUPERRING: - if ((K_RingsActive() == false)) // No rings rolled if rings are turned off. - { - newodds = 0; - } - - break; - case KITEM_LANDMINE: // au revoir newodds = 0; @@ -994,12 +972,12 @@ INT32 K_KartGetItemOdds( { flags |= KRF_INDIRECTITEM; - if (pingame-1 <= pexiting) + if (roulette->pingame-1 <= roulette->pexiting) newodds = 0; } else { - if (rival) + if (roulette->rival) { // Rival bot or already shrunk. DON'T roll another. newodds = 0; @@ -1016,7 +994,7 @@ INT32 K_KartGetItemOdds( } // In very small matches, remove the stupid bottom half item limiter - if (pingame < 6) + if (roulette->pingame < 6) { flags &= ~KRF_NOTFORBOTTOM; } @@ -1059,12 +1037,12 @@ INT32 K_KartGetItemOdds( // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) newodds = 0; } - else if (!K_LegacyOddsMode() && flags & KRF_NOTNEAREND && ourDist < (UINT32)ENDDIST) + else if (!K_LegacyOddsMode() && flags & KRF_NOTNEAREND && roulette->ourDist < (UINT32)ENDDIST) { // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) newodds = 0; } - else if (flags & KRF_NOTFORBOTTOM && inBottom && leveltime >= (30*TICRATE)+starttime) + else if (flags & KRF_NOTFORBOTTOM && roulette->inBottom && leveltime >= (30*TICRATE)+starttime) { // This item should not appear for losing players. (Usually items that feel less effective at these positions) newodds = 0; @@ -1080,18 +1058,18 @@ INT32 K_KartGetItemOdds( fracOdds *= 2; } - if (rival == true) + if (roulette->rival == true) { // The Rival bot gets frantic-like items, also :p fracOdds *= 2; } - fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame, spbrush)); + fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(roulette->pingame, roulette->spbrush)); - if (mashed > 0) + if (roulette->mashed > 0) { // Lastly, it *divides* it based on your mashed value, so that power items are less likely when you mash. - fracOdds = FixedDiv(fracOdds, FRACUNIT + mashed); + fracOdds = FixedDiv(fracOdds, FRACUNIT + roulette->mashed); } newodds = fracOdds / FRACUNIT; @@ -1100,6 +1078,14 @@ INT32 K_KartGetItemOdds( return newodds; } +void K_KartGetItemOdds(kartroulette_t *roulette, INT32 outodds[static MAXKARTRESULTS]) +{ + for (UINT8 i = 0; i < numkartresults; i++) + { + outodds[i] = GetItemOdds(roulette, &kartresults[i]); + } +}; + //{ SRB2kart Roulette Code - Distance Based, yes waypoints UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) @@ -1123,6 +1109,20 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b rivalodds = true; } + INT32 itemodds[MAXKARTRESULTS]; + kartroulette_t roulette = { + .pdis = pdis, + .playerpos = player->position, + //.pos = i, + .ourDist = player->distancetofinish, + .clusterDist = player->distancefromcluster, + .mashed = mashed, + .spbrush = spbrush, + .bot = player->bot, + .rival = rivalodds, + .inBottom = K_IsPlayerLosing(player), + }; + for (i = 0; i < MAXODDS; i++) { boolean available = false; @@ -1133,18 +1133,12 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b break; } + roulette.pos = i; + K_KartGetItemOdds(&roulette, itemodds); + for (j = 0; j < numkartresults; j++) { - if (K_KartGetItemOdds( - pdis, player->position, i, &kartresults[j], - player->distancetofinish, - player->distancefromcluster, - mashed, - spbrush, - player->bot, - rivalodds, - K_IsPlayerLosing(player) - ) > 0) + if (itemodds[j] > 0) { available = true; break; @@ -1276,7 +1270,7 @@ INT32 K_GetRollingRouletteItem(player_t *player) if (seen[j] == result->type) break; - if (j == numseen && memcmp(result->odds[oddstable][result->format], EMPTYODDS, sizeof(EMPTYODDS))) + if (j == numseen && memcmp(result->odds[oddstable], EMPTYODDS, sizeof(EMPTYODDS))) translation[roulette_size++] = seen[numseen++] = result->type; } @@ -1497,7 +1491,7 @@ UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrus return pdis; } -static boolean K_BattleForceSPB(player_t *player, UINT32 pdis) +static boolean K_BattleForceSPB(player_t *player) { boolean battlecond = ((gametyperules & GTR_WANTED) && (gametyperules & GTR_WANTEDSPB) && (mostwanted != -1) && (!K_IsPlayerMostWanted(player))); @@ -1602,7 +1596,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { UINT8 itemroll = P_RandomRange(0, numkartresults - 1); - K_KartGetItemResult(player, kartresults[itemroll].name); + K_AwardPlayerItem(player, &kartresults[itemroll]); player->itemblink = TICRATE; player->itemblinkmode = KITEMBLINKMODE_NORMAL; player->itemroulette = KROULETTE_DISABLED; @@ -1620,19 +1614,19 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { if ((gametyperules & GTR_RACEODDS)) { - if (mashed && K_RingsActive() && (modeattacking || K_ItemResultEnabled("superring"))) // ANY mashed value? You get rings. + if (mashed && (K_ItemResultEnabled(K_GetKartResult("superring")) || (modeattacking && K_RingsActive()))) // ANY mashed value? You get rings. { - K_KartGetItemResult(player, "superring"); + K_AwardPlayerItem(player, K_GetKartResult("superring")); player->itemblinkmode = KITEMBLINKMODE_MASHED; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); } else { - if (modeattacking || K_ItemResultEnabled("sneaker")) // Waited patiently? You get a sneaker! - K_KartGetItemResult(player, "sneaker"); + if (modeattacking || K_ItemResultEnabled(K_GetKartResult("sneaker"))) // Waited patiently? You get a sneaker! + K_AwardPlayerItem(player, K_GetKartResult("sneaker")); else // Default to sad if nothing's enabled... - K_KartGetItemResult(player, ""); + K_AwardPlayerItem(player, NULL); player->itemblinkmode = KITEMBLINKMODE_NORMAL; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); @@ -1640,23 +1634,23 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } else if (gametyperules & GTR_BATTLEODDS) { - if (mashed && (bossinfo.boss || K_ItemResultEnabled("banana")) && !itembreaker) // ANY mashed value? You get a banana. + if (mashed && (bossinfo.boss || K_ItemResultEnabled(K_GetKartResult("banana"))) && !itembreaker) // ANY mashed value? You get a banana. { - K_KartGetItemResult(player, "banana"); + K_AwardPlayerItem(player, K_GetKartResult("banana")); player->itemblinkmode = KITEMBLINKMODE_MASHED; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); } else if (bossinfo.boss) { - K_KartGetItemResult(player, "orbinaut"); + K_AwardPlayerItem(player, K_GetKartResult("orbinaut")); player->itemblinkmode = KITEMBLINKMODE_NORMAL; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); } else if (itembreaker) { - K_KartGetItemResult(player, "sneaker"); + K_AwardPlayerItem(player, K_GetKartResult("sneaker")); player->itemblinkmode = KITEMBLINKMODE_MASHED; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); @@ -1671,12 +1665,12 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // SPECIAL CASE No. 5: // Being in ring debt occasionally forces Super Ring on you if you mashed - if (K_RingsActive() && mashed && player->rings < 0 && K_ItemResultEnabled("superring")) + if (K_ItemResultEnabled(K_GetKartResult("superring")) && mashed && player->rings < 0) { INT32 debtamount = min(abs(player->ringmin), abs(player->rings)); if (P_RandomChance((debtamount*FRACUNIT)/abs(player->ringmin))) { - K_KartGetItemResult(player, "superring"); + K_AwardPlayerItem(player, K_GetKartResult("superring")); player->itemblink = TICRATE; player->itemblinkmode = KITEMBLINKMODE_MASHED; player->itemroulette = KROULETTE_DISABLED; @@ -1704,7 +1698,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) if ((bestforce) && (forceresult)) { - K_KartGetItemResult(player, forceresult->name); + K_AwardPlayerItem(player, forceresult); player->itemblink = TICRATE; player->itemblinkmode = KITEMBLINKMODE_KARMA; player->itemroulette = KROULETTE_DISABLED; @@ -1717,11 +1711,11 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // SPECIAL CASE No. 7: // In battle, an SPB is forced onto players to target the "most wanted" player - if (K_BattleForceSPB(player, pdis) + if (K_BattleForceSPB(player) && spbplace == -1 && !indirectitemcooldown && !dontforcespb - && K_ItemResultEnabled("spb")) + && K_ItemResultEnabled(K_GetKartResult("spb"))) { - K_KartGetItemResult(player, "spb"); + K_AwardPlayerItem(player, K_GetKartResult("spb")); player->itemblink = TICRATE; player->itemblinkmode = KITEMBLINKMODE_KARMA; player->itemroulette = KROULETTE_DISABLED; @@ -1738,18 +1732,25 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // Split into another function for a debug function below useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); + kartroulette_t roulette = { + .pdis = pdis, + .playerpos = player->position, + .pos = useodds, + .ourDist = player->distancetofinish, + .clusterDist = player->distancefromcluster, + .mashed = mashed, + .spbrush = spbrush, + .bot = player->bot, + .rival = player->bot && player->botvars.rival, + .inBottom = K_IsPlayerLosing(player), + }; + + K_KartGetItemOdds(&roulette, spawnchance); + for (i = 0; i < numkartresults; i++) { - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - pdis, player->position, useodds, &kartresults[i], - player->distancetofinish, - player->distancefromcluster, - mashed, - spbrush, - player->bot, - (player->bot && player->botvars.rival), - K_IsPlayerLosing(player)) - ); + totalspawnchance += spawnchance[i]; + spawnchance[i] = totalspawnchance; } // Award the player whatever power is rolled @@ -1758,11 +1759,11 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) totalspawnchance = P_RandomKey(totalspawnchance); for (i = 0; i < numkartresults && spawnchance[i] <= totalspawnchance; i++); - K_KartGetItemResult(player, kartresults[i].name); + K_AwardPlayerItem(player, &kartresults[i]); } else { - K_KartGetItemResult(player, ""); + K_AwardPlayerItem(player, NULL); } if (P_IsDisplayPlayer(player)) diff --git a/src/k_items.h b/src/k_items.h index 21b6c0dde..8da6a719c 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -60,13 +60,6 @@ typedef enum MAXKARTITEMS } ATTRPACK kartitemtype_e; -typedef enum -{ - KIFRMT_STANDARD = 0, - KIFRMT_ALTERN, - NUMKARTITEMFORMATS -} ATTRPACK kartitemformat_e; - #define MAXKARTRESULTS 255 typedef enum @@ -77,6 +70,27 @@ typedef enum MAXODDSTABLES } ATTRPACK kartoddstable_e; +// item flags for HUD and usage +typedef enum +{ + KIF_ANIMATED = 1<<0, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT) + KIF_DARKBG = 1<<1, // use dark item roulette BG + KIF_COLPATCH2PLAYER = 1<<2, // colourize patch to player +} ATTRPACK kartitemflags_e; + +// flags relevant to item rolls and usage +typedef enum +{ + KRF_INDIRECTITEM = 1<<0, // This item affects others indirectly. A 20 second cooldown is applied to all other items with this flag when an indirect item is rolled. + KRF_POWERITEM = 1<<1, // This item is a "power item". This activates "frantic item" toggle related functionality. + KRF_COOLDOWNONSTART = 1<<2, // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + KRF_NOTNEAREND = 1<<3, // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) + KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this item stops appearing for losing players. (Usually items that feel less effective at these positions) + KRF_UNIQUE = 1<<5, // Only one player can use this item at a time + KRF_RUNNERAUGMENT = 1<<6, // This item's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) + // free: 1<<7 +} ATTRPACK kartresultflags_e; + struct kartitemgraphics_t { UINT8 numpatches; @@ -87,73 +101,70 @@ struct kartitemgraphics_t struct kartitem_t { const char *name; - consvar_t *altcvar; kartitemgraphics_t graphics[2]; - UINT8 hudflags; - + kartitemflags_e flags; + + consvar_t *altcvar; // if not NULL, an altitem exists + boolean altenabled; + // note: could be how we do this? // int (*oddsOverrideFunc)(player_t *p, int pos, int mashed, boolean spbrush, fixed_t seconddist, int pingame, int pexiting); // int (*resultOverrideFunc)(player_t *p); }; -// item flags relevant to item rolls and usage -enum -{ - KRF_INDIRECTITEM = 1<<0, // This item affects others indirectly. A 20 second cooldown is applied to all other items with this flag when an indirect item is rolled. - KRF_POWERITEM = 1<<1, // This item is a "power item". This activates "frantic item" toggle related functionality. - KRF_COOLDOWNONSTART = 1<<2, // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) - KRF_NOTNEAREND = 1<<3, // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) - KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this item stops appearing for losing players. (Usually items that feel less effective at these positions) - KRF_UNIQUE = 1<<5, - KRF_RUNNERAUGMENT = 1<<6, // This item's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) - // free: 1<<7 -}; - -// item flags relevant to patch rendering in the HUD -enum -{ - KPF_ANIMATED = 1<<0, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT) - KPF_DARKBG = 1<<1, // use dark item roulette BG - KPF_COLPATCH2PLAYER = 1<<2, // colourize patch to player -}; - // Unique useodds function: // uoddsfunc(result, playerpos, pdis, newodds, pos, pingame, pexiting, firstDist, ourDist, clusterDist, mashed, spbrush, bot, rival, inBottom) // FIXME: This is fucking gross; is there not a better way to do this? -typedef INT32(*useoddsfunc_t)(kartresult_t *, SINT8, UINT32, INT32, UINT8, UINT8, UINT8, UINT32, UINT32, UINT32, fixed_t, boolean, boolean, boolean, boolean); +typedef INT32 (useoddsfunc_f)(INT32 odds, const kartroulette_t *roulette, kartresult_t *result); struct kartresult_t { - const char *name; - consvar_t *cvar; - + // this block is shared by all item variants + consvar_t *cvar; // contains name kartitemtype_e type; UINT8 amount; - kartitemformat_e format; // An item's format. Different formats have different odds + boolean isalt; // is alt (i salt) + kartresultflags_e flags; UINT8 forceme; // This item is being forced into a player's item slot for one reason or another. Higher value = higher priority. - - // 3D array determining item odds, selected by table, then format. - UINT8 odds[MAXODDSTABLES][NUMKARTITEMFORMATS][MAXODDS]; + UINT8 odds[MAXODDSTABLES][MAXODDS]; // Functions that tell the game to use unique means of rolling these items. // If NULL, the functions never execute. - useoddsfunc_t unique_odds[MAXODDSTABLES][NUMKARTITEMFORMATS]; + useoddsfunc_f *unique_odds[MAXODDSTABLES]; - UINT8 flags[NUMKARTITEMFORMATS]; tic_t basebgone, bgone; }; +// contains all data for a call to K_KartGetItemOdds +struct kartroulette_t +{ + UINT32 pdis; // the player's PDIS value + SINT8 playerpos; // the player's position + UINT8 pos; // position in the odds table (why is this just "pos"???) + UINT32 ourDist; // the player's finish line distance + UINT32 clusterDist; // distance to cluster + fixed_t mashed; + boolean spbrush; + boolean bot; + boolean rival; + boolean inBottom; + + // extras for useodds functions + UINT8 pingame; + UINT8 pexiting; + UINT32 firstDist; +}; + extern kartitem_t kartitems[MAXKARTITEMS]; extern UINT8 numkartitems; extern kartresult_t kartresults[MAXKARTRESULTS]; extern UINT8 numkartresults; -boolean K_IsKartResultAlternate(const char *name); - kartresult_t *K_GetKartResult(const char *name); void K_InitializeItems(void); -boolean K_ItemResultEnabled(const char *name); +boolean K_ItemResultEnabled(const kartresult_t *result); +boolean K_IsKartItemAlternate(kartitemtype_e itemtype); void K_SetupItemOdds(void); void K_KartHandleShieldCooldown(kartitemtype_e item); @@ -164,7 +175,6 @@ UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame); UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush); UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); -INT32 K_KartGetItemOdds(UINT32 pdis, SINT8 playerpos, UINT8 pos, kartresult_t *result, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); INT32 K_GetRollingRouletteItem(player_t *player); void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); @@ -184,4 +194,8 @@ void K_AltShrinkPityIncrease(player_t *player); } // extern "C" #endif +#ifndef __cplusplus +void K_KartGetItemOdds(kartroulette_t *roulette, INT32 outodds[static MAXKARTRESULTS]); +#endif + #endif // __K_ITEMS__ diff --git a/src/k_kart.c b/src/k_kart.c index ac2eed2d1..6b031a7e8 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4706,7 +4706,7 @@ static void K_DoHyudoroSteal(player_t *player) prandom = P_RandomFixed(); S_StartSound(player->mo, sfx_s3k92); - if (sink && numplayers > 0 && K_ItemResultEnabled("kitchensink")) // BEHOLD THE KITCHEN SINK + if (sink && numplayers > 0 && K_ItemResultEnabled(K_GetKartResult("kitchensink"))) // BEHOLD THE KITCHEN SINK { player->hyudorotimer = hyu; player->stealingtimer = stealtime; @@ -5510,16 +5510,25 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 useodds = amount; + kartroulette_t roulette = { + .pdis = 0, + .playerpos = 0, + .pos = useodds, + .ourDist = UINT32_MAX, + .clusterDist = 0, + .mashed = 0, + .spbrush = false, + .bot = false, + .rival = false, + .inBottom = false + }; + + K_KartGetItemOdds(&roulette, spawnchance); + for (i = 0; i < numkartresults; i++) { - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - 0, 0, useodds, &kartresults[i], - UINT32_MAX, - 0, - 0, - false, false, false, false - ) - ); + totalspawnchance += spawnchance[i]; + spawnchance[i] = totalspawnchance; } if (totalspawnchance > 0) @@ -11470,12 +11479,6 @@ boolean K_ItemPushingActive(void) return itempushing; } -boolean K_IsKartItemAlternate(UINT8 itemtype) -{ - return itemtype >= 0 && itemtype < numkartitems - && kartitems[itemtype].altcvar != NULL && kartitems[itemtype].altcvar->value == 1; -} - boolean K_UsingLegacyCheckpoints(void) { if (K_UsingPatchedMap() && waypointcap != NULL) diff --git a/src/k_kart.h b/src/k_kart.h index d5503cb44..1d4f09766 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -349,7 +349,6 @@ boolean K_DraftingActive(void); boolean K_AirDropActive(void); boolean K_ItemLitterActive(void); boolean K_ItemPushingActive(void); -boolean K_IsKartItemAlternate(UINT8 itemtype); INT32 K_GetBumpSpark(void); boolean K_BoostChain(player_t *player, INT32 timer, boolean chainsound); INT32 K_ChainOrDeincrementTime(player_t *player, INT32 timer, INT32 deincrement, boolean chainsound); diff --git a/src/m_menu.c b/src/m_menu.c index 32bd3c578..8f4abb59f 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -8385,9 +8385,9 @@ void MD_DrawMonitorToggles(void) kartresult_t *result = &kartresults[arg - 1]; kartitem_t *item = &kartitems[result->type]; - boolean enabled = K_ItemResultEnabled(result->name); + boolean enabled = K_ItemResultEnabled(result); translucent = enabled ? 0 : V_TRANSLUCENT; - drawnum = (item->hudflags & KPF_ANIMATED) ? 0 : CLAMP(result->amount, 0, item->graphics[1].numpatches - 1); + drawnum = item->flags & KIF_ANIMATED ? 0 : CLAMP(result->amount, 0, item->graphics[1].numpatches - 1); if (enabled) V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); @@ -8432,9 +8432,9 @@ void MD_DrawMonitorToggles(void) { kartresult_t *result = &kartresults[arg - 1]; kartitem_t *item = &kartitems[result->type]; - boolean enabled = K_ItemResultEnabled(result->name); + boolean enabled = K_ItemResultEnabled(result); translucent = enabled ? 0 : V_TRANSLUCENT; - drawnum = (item->hudflags & KPF_ANIMATED) ? 0 : CLAMP(result->amount, 0, item->graphics[0].numpatches - 1); + drawnum = item->flags & KIF_ANIMATED ? 0 : CLAMP(result->amount, 0, item->graphics[0].numpatches - 1); if (enabled) V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); @@ -8534,7 +8534,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) } else if (choice == 0) { - INT32 v = K_ItemResultEnabled("sneaker") ? 1 : 0; + INT32 v = K_ItemResultEnabled(K_GetKartResult("sneaker")) ? 1 : 0; S_StartSound(NULL, sfx_s1b4); for (UINT8 i = 0; i < numkartresults; i++) { diff --git a/src/p_setup.c b/src/p_setup.c index 0ff9f844f..75b01c62e 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8077,29 +8077,6 @@ static void P_InitLevelSettings(boolean reloadinggamestate) if (!reloadinggamestate) K_SetupItemOdds(); - for (i = 0; i < numkartitems; i++) - { - const kartitem_t *kitm = &kartitems[i]; - char *findname = malloc(strlen(kitm->name)+1); - - sprintf(findname, "%s", kitm->name); - strlwr(findname); - - kartresult_t *kres = K_GetKartResult(findname); - - if ((kres) && (kitm->altcvar)) - { - // Update the result's format based on the cvar's value. - if (kres->format != kitm->altcvar->value) - { - CONS_Printf("%s: updating format to %s\n", kres->name, kitm->altcvar->string); - kres->format = kitm->altcvar->value; - } - } - - free(findname); // Don't need this anymore - } - // emerald hunt hunt1 = hunt2 = hunt3 = NULL; diff --git a/src/typedef.h b/src/typedef.h index 7d0d3dab7..88536d024 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -212,6 +212,7 @@ TYPEDEF (mapUserProperties_t); TYPEDEF (kartitem_t); TYPEDEF (kartresult_t); TYPEDEF (kartitemgraphics_t); +TYPEDEF (kartroulette_t); // lua_hudlib_drawlist.h typedef struct huddrawlist_s *huddrawlist_h; From 255a866fc34688c669b4ac15da83cbd68c34ddbb Mon Sep 17 00:00:00 2001 From: minenice55 Date: Fri, 7 Nov 2025 12:57:07 -0500 Subject: [PATCH 18/76] implement KRF_UNIQUE (xItem's XIF_UNIQUE) --- src/k_items.c | 22 ++++++++++++++++++++++ src/k_items.h | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/k_items.c b/src/k_items.c index 072e16815..60c7c414e 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -1047,6 +1047,28 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result) // This item should not appear for losing players. (Usually items that feel less effective at these positions) newodds = 0; } + else if (flags & KRF_UNIQUE) + { + // TODO: bring this out + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (players[i].exiting) + continue; + + if ((gametyperules & GTR_BUMPERS) && !players[i].bumper) + continue; + + if (players[i].itemtype == result->type && players[i].itemamount > 0) + { + // Can only appear in one player's item slot, and can't be rolled if already held by a player + newodds = 0; + break; + } + } + } else if (flags & KRF_POWERITEM) { // This item is a "power item". This activates "frantic item" toggle related functionality. diff --git a/src/k_items.h b/src/k_items.h index 8da6a719c..8b83c3e38 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -86,7 +86,7 @@ typedef enum KRF_COOLDOWNONSTART = 1<<2, // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) KRF_NOTNEAREND = 1<<3, // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this item stops appearing for losing players. (Usually items that feel less effective at these positions) - KRF_UNIQUE = 1<<5, // Only one player can use this item at a time + KRF_UNIQUE = 1<<5, // If already in a player's item slot, prevents all other players from obtaining this item from item boxes KRF_RUNNERAUGMENT = 1<<6, // This item's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) // free: 1<<7 } ATTRPACK kartresultflags_e; From bdf06d086d69c9b713d3ce15a95544ba0744c89b Mon Sep 17 00:00:00 2001 From: Anonimus Date: Fri, 7 Nov 2025 14:49:05 -0500 Subject: [PATCH 19/76] Rewrite the shield cooldown system to apply to KRF_UNIQUE items in general --- src/k_items.c | 60 +++++++++++++++++++++------------------------------ src/k_items.h | 2 +- src/p_tick.c | 6 ++---- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/k_items.c b/src/k_items.c index 60c7c414e..91f8a7892 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -265,14 +265,14 @@ void K_InitializeItems(void) kartresult_t *spb = K_RegisterResult("spb", KITEM_SPB, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_NOTNEAREND|KRF_RUNNERAUGMENT); K_RegisterResult("grow", KITEM_GROW, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); K_RegisterResult("shrink", KITEM_SHRINK, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_POWERITEM|KRF_NOTNEAREND); - K_RegisterResult("thundershield", KITEM_THUNDERSHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); + K_RegisterResult("thundershield", KITEM_THUNDERSHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_UNIQUE); K_RegisterResult("hyudoro", KITEM_HYUDORO, 1, KRF_COOLDOWNONSTART); K_RegisterResult("pogospring", KITEM_POGOSPRING, 1, 0); K_RegisterResult("kitchensink", KITEM_KITCHENSINK, 1, 0); K_RegisterResult("superring", KITEM_SUPERRING, 1, KRF_NOTNEAREND|KRF_NOTFORBOTTOM); K_RegisterResult("landmine", KITEM_LANDMINE, 1, 0); - K_RegisterResult("bubbleshield", KITEM_BUBBLESHIELD, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); - K_RegisterResult("flameshield", KITEM_FLAMESHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); + K_RegisterResult("bubbleshield", KITEM_BUBBLESHIELD, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM|KRF_UNIQUE); + K_RegisterResult("flameshield", KITEM_FLAMESHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_UNIQUE); K_RegisterResult("dualsneaker", KITEM_SNEAKER, 2, 0); K_RegisterResult("triplesneaker", KITEM_SNEAKER, 3, KRF_POWERITEM); @@ -590,7 +590,7 @@ static void K_AwardPlayerItem(player_t *player, kartresult_t *result) return; } - if (result->type == KITEM_SPB || result->type == KITEM_SHRINK) // Indirect items + if (result->flags & KRF_INDIRECTITEM) // Indirect items { indirectitemcooldown = 20*TICRATE; } @@ -725,46 +725,36 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist) #undef FRAC_3h -// Assigns general cooldowns to shield items. -void K_KartHandleShieldCooldown(kartitemtype_e item) +// Assigns general cooldowns to item results ith the KRF_UNIQUE flag. +void K_KartHandleUniqueCooldown(void) { - INT32 i, j; + UINT8 i, j; UINT8 pingame = 0, pexiting = 0; - INT32 shieldtype = KSHIELD_NONE; - shieldtype = K_GetShieldFromItem(item); - - if (shieldtype == KSHIELD_NONE) + for (i = 0; i < numkartresults; i++) { - // Not a shield! - return; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - - if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) - pingame++; - - if (players[i].exiting) - pexiting++; - - if (((shieldtype == K_GetShieldFromItem(players[i].itemtype)) - || (shieldtype == K_GetShieldFromPlayer(&players[i])))) + if (kartresults[i].flags & KRF_UNIQUE) { - for (j = 0; j < numkartresults; j++) + // TODO: bring this out + for (j = 0; j < MAXPLAYERS; j++) { - kartresult_t *result = &kartresults[j]; + if (!playeringame[j] || players[j].spectator) + continue; - // If this shield has a cooldown, force-apply the cooldown preeemptively for - // the entire time the shield is being held by a player. - if (result->type == item && result->basebgone > 0) + if (!(gametyperules & GTR_BUMPERS) || players[j].bumper) + pingame++; + + if (players[j].exiting) + pexiting++; + + if (players[j].itemtype == kartresults[i].type && + players[j].itemamount > 0 && kartresults[i].basebgone > 0) { - result->bgone = result->basebgone * TICRATE; - break; + // If this item has a cooldown, force-apply the cooldown + // preeemptively for the entire time the item is being held + // by a player. + kartresults[i].bgone = kartresults[i].basebgone * TICRATE; } } } diff --git a/src/k_items.h b/src/k_items.h index 8b83c3e38..99cbe4afa 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -167,7 +167,7 @@ boolean K_ItemResultEnabled(const kartresult_t *result); boolean K_IsKartItemAlternate(kartitemtype_e itemtype); void K_SetupItemOdds(void); -void K_KartHandleShieldCooldown(kartitemtype_e item); +void K_KartHandleUniqueCooldown(void); boolean K_LegacyOddsMode(void); UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos); diff --git a/src/p_tick.c b/src/p_tick.c index 9733f5b07..230f2ab17 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -911,10 +911,8 @@ void P_Ticker(boolean run) if (gametyperules & GTR_RACEODDS) { - // Shield cooldowns - K_KartHandleShieldCooldown(KITEM_THUNDERSHIELD); - K_KartHandleShieldCooldown(KITEM_BUBBLESHIELD); - K_KartHandleShieldCooldown(KITEM_FLAMESHIELD); + // Unique item cooldowns + K_KartHandleUniqueCooldown(); } K_BossInfoTicker(); From b76d6a6df52ba312dbd1bb18ed7bbda0e77b817f Mon Sep 17 00:00:00 2001 From: Anonimus Date: Fri, 7 Nov 2025 16:23:28 -0500 Subject: [PATCH 20/76] Update items readme --- extras/Items.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extras/Items.md b/extras/Items.md index 5adaa3e8c..94b174af5 100644 --- a/extras/Items.md +++ b/extras/Items.md @@ -8,8 +8,8 @@ Alternative items are secondary versions of existing SRB2Kart items that **reint To toggle on/off these alternate versions, you can do so with the following commands: -- ``kartinvintype`` (for Invincibility) -- ``kartshrinktype`` (for Shrink) +- ``altitem_invincibility`` (for Invincibility) +- ``altitem_shrink`` (for Shrink) ### Alt. Invincibility (Power: Occupies Slot) Invincibility, in its normal form, is a power item that appears in the lower ranks, letting players cut through offroad and spin out opponents with prejudice. **Alt. Invincibility** works significantly different, trading its aggression for speed and optimized recovery. From 3c64f345fb087f66f8495da66d397f05e66678c1 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Fri, 7 Nov 2025 23:02:15 +0100 Subject: [PATCH 21/76] SOC the items... finally! --- src/d_netcmd.c | 6 +- src/deh_lua.c | 11 +- src/deh_soc.c | 212 ++++++++++++++++++++ src/deh_soc.h | 5 + src/deh_tables.c | 39 ++++ src/deh_tables.h | 10 + src/dehacked.c | 37 ++++ src/info.c | 17 +- src/info/kartitems.h | 21 ++ src/k_hud.c | 3 + src/k_items.c | 459 +++++++------------------------------------ src/k_items.h | 51 ++--- src/k_kart.c | 2 + 13 files changed, 447 insertions(+), 426 deletions(-) create mode 100644 src/info/kartitems.h diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 974e844d2..07307f425 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -6197,7 +6197,7 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) } else { - CV_CheaterWarning(playernum, va("give item %s x%d", item < 0 ? "SAD" : kartitems[item].name, amt)); + CV_CheaterWarning(playernum, va("give item %s x%d", item < 0 ? "SAD" : DEH_KartItemName(item), amt)); } break; } @@ -6511,9 +6511,9 @@ static void Command_KartGiveItem_f(void) /* then do very loose partial matching */ for (i = 0; i < numkartitems; i++) { - if (strcasestr(kartitems[i].name, name) != NULL) + if (strcasestr(DEH_KartItemName(i), name) != NULL) { - CONS_Printf("\x83\t%s\n", kartitems[i].name); + CONS_Printf("\x83\t%s\n", DEH_KartItemName(i)); item = i; } } diff --git a/src/deh_lua.c b/src/deh_lua.c index 7b7b279be..ad40a5c2e 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -446,11 +446,12 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) lua_pushinteger(L, KITEM_THUNDERSHIELD); return 1; } - for (i = 0; i < numkartitems; i++) - if (fastcmp(p, kartitems[i].name)) { - lua_pushinteger(L, i); - return 1; - } + i = DEH_FindKartItem(p); + if (i != MAXKARTITEMS) + { + CacheAndPushConstant(L, word, i); + return 1; + } return luaL_error(L, "kartitem '%s' could not be found.\n", word); } else if (fastncmp("SKINCOLOR_",word,10)) { diff --git a/src/deh_soc.c b/src/deh_soc.c index 26bdfb77f..b83845184 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -53,6 +53,7 @@ #include "filesrch.h" // refreshdirmenu #include "k_follower.h" #include "doomstat.h" // MAXMUSNAMES +#include "k_items.h" // Loops through every constant and operation in word and performs its calculations, returning the final value. fixed_t get_number(const char *word) @@ -4035,6 +4036,194 @@ void readweather(MYFILE *f, INT32 num) Z_Free(s); } +#define WARN(str, ...) deh_warning("KartItem %s: " str, strbuf_get(kartitemnames, item->info.nameofs), __VA_ARGS__) +#define WARN0(str) deh_warning("KartItem %s: " str, strbuf_get(kartitemnames, item->info.nameofs)) +void readkartitem(MYFILE *f, kartitem_t *item) +{ + char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + char *word = s; + char *word2; + char *tmp; + + do if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + // First remove trailing newline, if there is one + tmp = strchr(s, '\n'); + if (tmp) + *tmp = '\0'; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + // Get the part before the " = " + tmp = strchr(s, '='); + if (tmp) + *(tmp-1) = '\0'; + else + break; + strupr(word); + + // Now get the part after + word2 = (tmp += 2); + strupr(word2); + + if (fastcmp(word, "BIGPATCHES") || fastcmp(word, "SMALLPATCHES")) + { + kartitemgraphics_t *graphics = &item->graphics[word[0] == 'B' ? 0 : 1]; + UINT8 i; + tmp = strtok(word2,","); + for (i = 0; i < MAXITEMPATCHES; i++) + { + graphics->numpatches++; + graphics->patchnames[i] = Z_StrDup(tmp); + if ((tmp = strtok(NULL,",")) == NULL) + break; + } + } + else if (fastcmp(word, "FLAGS")) + { + item->flags = get_number(word2); + } + else + WARN("unknown word '%s'", word); + } + while (!myfeof(f)); // finish when the line is empty + + Z_Free(s); +} +#undef WARN +#undef WARN0 + +#define WARN(str, ...) deh_warning("KartResult %s: " str, result->cvar->name, __VA_ARGS__) +#define WARN0(str) deh_warning("KartResult %s: " str, result->cvar->name) +void readkartresult(MYFILE *f, kartresult_t *result) +{ + char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + char *word = s; + char *word2; + char *tmp; + + do if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + // First remove trailing newline, if there is one + tmp = strchr(s, '\n'); + if (tmp) + *tmp = '\0'; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + // Get the part before the " = " + tmp = strchr(s, '='); + if (tmp) + *(tmp-1) = '\0'; + else + break; + strupr(word); + + // Now get the part after + word2 = tmp + 2; + //strupr(word2); + + if (fastcmp(word, "TYPE")) + { + if (result->isalt) + { + WARN0("can't set type for alternate result"); + continue; + } + result->type = get_kartitem(word2); + } + else if (fastcmp(word, "AMOUNT")) + { + if (result->isalt) + { + WARN0("can't set amount for alternate result"); + continue; + } + result->amount = get_number(word2); + } + else if (fastcmp(word, "ISALT")) + { + result->isalt = (boolean)(atoi(word2) || word2[0] == 'T' || word2[0] == 'Y'); + } + else if (fastcmp(word, "FLAGS")) + { + result->flags = get_number(word2); + } + else if (fastncmp(word, "ODDS", 4)) + { + UINT8 oddstable; + if (fastcmp(word+4, "RACE")) + oddstable = ODDS_RACE; + else if (fastcmp(word+4, "BATTLE")) + oddstable = ODDS_BATTLE; + else if (fastcmp(word+4, "SPECIAL")) + oddstable = ODDS_SPECIAL; + else + { + WARN("unknown word '%s'", word); + continue; + } + + UINT8 *odds = result->odds[oddstable]; + tmp = strtok(word2,","); + for (UINT8 i = 0; i <= MAXODDS; i++) + { + if (i == oddstablemax[oddstable]) + { + WARN("odds table %s is limited to %d odds", word+4, oddstablemax[oddstable]); + continue; + } + odds[i] = atoi(tmp); + if ((tmp = strtok(NULL,",")) == NULL) + break; + } + } + else if (fastncmp(word, "UNIQUEODDS", 10)) + { + UINT8 oddstable; + if (fastcmp(word+10, "RACE")) + oddstable = ODDS_RACE; + else if (fastcmp(word+10, "BATTLE")) + oddstable = ODDS_BATTLE; + else if (fastcmp(word+10, "SPECIAL")) + oddstable = ODDS_SPECIAL; + + else + { + WARN("unknown word '%s'", word); + continue; + } + + result->unique_odds[oddstable] = get_useoddsfunc(word2); + } + else if (fastcmp(word, "BGONETIME") || fastcmp(word, "BASEBGONE")) + { + result->basebgone = atoi(word2) * TICRATE; + } + else + WARN("unknown word '%s'", word); + } + while (!myfeof(f)); // finish when the line is empty + + Z_Free(s); +} +#undef WARN +#undef WARN0 + // // // @@ -4192,6 +4381,29 @@ preciptype_t get_precip(const char *word) return PRECIP_NONE; } +kartitemtype_e get_kartitem(const char *word) +{ // Returns the value of KITEM_ enumerations + kartitemtype_e i; + if (fastncmp("KITEM_",word,6)) + word += 6; // take off the KITEM_ + i = DEH_FindKartItem(word); + if (i == MAXKARTITEMS) + deh_warning("Couldn't find kartitem named 'KITEM_%s'", word); + return i; +} + +useoddsfunc_f *get_useoddsfunc(const char *word) +{ // Returns the value of KO_ enumerations + size_t i; + if (fastncmp("KO_",word,3)) + word += 3; // take off the KO_ + for (i = 0; USEODDS_FUNCS[i].name; i++) + if (fasticmp(word, USEODDS_FUNCS[i].name)) + return USEODDS_FUNCS[i].func; + deh_warning("Couldn't find odds function named 'KO_%s'",word); + return NULL; +} + /// \todo Make ANY of this completely over-the-top math craziness obey the order of operations. static fixed_t op_mul(fixed_t a, fixed_t b) { return a*b; } static fixed_t op_div(fixed_t a, fixed_t b) { return a/b; } diff --git a/src/deh_soc.h b/src/deh_soc.h index 8dc8882e8..143c21a3d 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -36,6 +36,7 @@ #include "fastcmp.h" #include "lua_script.h" // Reluctantly included for LUA_EvalMath #include "d_clisrv.h" +#include "k_items.h" #ifdef HWRENDER #include "hardware/hw_light.h" @@ -62,6 +63,8 @@ menudrawer_f *get_menudrawer(const char *word); //INT16 get_gametype(const char *word); //powertype_t get_power(const char *word); skincolornum_t get_skincolor(const char *word); +kartitemtype_e get_kartitem(const char *word); +useoddsfunc_f *get_useoddsfunc(const char *word); void readwipes(MYFILE *f); void readmaincfg(MYFILE *f); @@ -91,6 +94,8 @@ void readcupheader(MYFILE *f, cupheader_t *cup); void readfollower(MYFILE *f); preciptype_t get_precip(const char *word); void readweather(MYFILE *f, INT32 num); +void readkartitem(MYFILE *f, kartitem_t *item); +void readkartresult(MYFILE *f, kartresult_t *result); #ifdef __cplusplus } // extern "C" diff --git a/src/deh_tables.c b/src/deh_tables.c index 73f00bb07..3797b52eb 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -40,6 +40,7 @@ strbuf_t *statenames; strbuf_t *mobjnames; strbuf_t *skincolornames; strbuf_t *menunames; +strbuf_t *kartitemnames; UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway. const char *DEH_MobjtypeName(mobjtype_t i) @@ -62,6 +63,11 @@ const char *DEH_MenutypeName(menutype_t i) return strbuf_get(menunames, menudefs[i].info.nameofs); } +const char *DEH_KartItemName(kartitemtype_e i) +{ + return strbuf_get(kartitemnames, kartitems[i].info.nameofs); +} + mobjtype_t DEH_FindMobjtype(const char *word) { mobjtype_t i; @@ -114,6 +120,19 @@ menutype_t DEH_FindMenutype(const char *word) return MAXMENUTYPES; } +kartitemtype_e DEH_FindKartItem(const char *word) +{ + kartitemtype_e i; + UINT32 hash = HASH32(word, strlen(word)); + for (i = 0; i < numkartitems; i++) { + if (hash != kartitems[i].info.namehash) + continue; + if (fastcmp(word, DEH_KartItemName(i))) + return i; + } + return MAXKARTITEMS; +} + struct flickytypes_s FLICKYTYPES[] = { {"BLUEBIRD", MT_FLICKY_01}, // Flicky (Flicky) {"RABBIT", MT_FLICKY_02}, // Pocky (1) @@ -837,6 +856,12 @@ struct menu_drawer_s const MENU_DRAWERS[] = { { NULL, NULL } }; +struct odds_func_s const USEODDS_FUNCS[] = { + { "ALTINVINODDS", &KO_AltInvinOdds }, + { "SPBRACEODDS", &KO_SPBRaceOdds }, + { NULL, NULL } +}; + struct int_const_s const INT_CONST[] = { // If a mod removes some variables here, // please leave the names in-tact and just set @@ -1533,6 +1558,20 @@ struct int_const_s const INT_CONST[] = { {"KITEMBLINKMODE_MASHED",KITEMBLINKMODE_MASHED}, {"KITEMBLINKMODE_KARMA",KITEMBLINKMODE_KARMA}, + // kartitemflags_e + {"KIF_ANIMATED",KIF_ANIMATED}, + {"KIF_DARKBG",KIF_DARKBG}, + {"KIF_COLPATCH2PLAYER",KIF_COLPATCH2PLAYER}, + + // kartresultflags_e + {"KRF_INDIRECTITEM",KRF_INDIRECTITEM}, + {"KRF_POWERITEM",KRF_POWERITEM}, + {"KRF_COOLDOWNONSTART",KRF_COOLDOWNONSTART}, + {"KRF_NOTNEAREND",KRF_NOTNEAREND}, + {"KRF_NOTFORBOTTOM",KRF_NOTFORBOTTOM}, + {"KRF_UNIQUE",KRF_UNIQUE}, + {"KRF_RUNNERAUGMENT",KRF_RUNNERAUGMENT}, + // kartspinoutflags_t {"KSPIN_THRUST",KSPIN_THRUST}, {"KSPIN_IFRAMES",KSPIN_IFRAMES}, diff --git a/src/deh_tables.h b/src/deh_tables.h index 2366001cb..5aba111c0 100644 --- a/src/deh_tables.h +++ b/src/deh_tables.h @@ -19,6 +19,7 @@ #include "lua_script.h" #include "strbuf.h" #include "m_menu.h" // menutype_t +#include "k_items.h" #ifdef __cplusplus extern "C" { @@ -30,17 +31,20 @@ extern strbuf_t *statenames; extern strbuf_t *mobjnames; extern strbuf_t *skincolornames; extern strbuf_t *menunames; +extern strbuf_t *kartitemnames; extern UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway. const char *DEH_MobjtypeName(mobjtype_t i); const char *DEH_StateName(statenum_t i); const char *DEH_SkincolorName(skincolornum_t i); const char *DEH_MenutypeName(menutype_t i); +const char *DEH_KartItemName(kartitemtype_e i); mobjtype_t DEH_FindMobjtype(const char *word); statenum_t DEH_FindState(const char *word); skincolornum_t DEH_FindSkincolor(const char *word); menutype_t DEH_FindMenutype(const char *word); +kartitemtype_e DEH_FindKartItem(const char *word); struct flickytypes_s { const char *name; @@ -67,6 +71,11 @@ struct menu_drawer_s { menudrawer_f *drawer; }; +struct odds_func_s { + const char *name; + useoddsfunc_f *func; +}; + struct int_const_s { const char *n; // has to be able to hold both fixed_t and angle_t, so drastic measure!! @@ -97,6 +106,7 @@ extern const char *const KARTHUD_LIST[]; extern const char *const HUDITEMS_LIST[]; extern struct menu_routine_s const MENU_ROUTINES[]; extern struct menu_drawer_s const MENU_DRAWERS[]; +extern struct odds_func_s const USEODDS_FUNCS[]; extern struct int_const_s const INT_CONST[]; diff --git a/src/dehacked.c b/src/dehacked.c index ae6b46781..3c98a1ee2 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -376,6 +376,22 @@ INT32 DEH_ReadFreeslot(const char *type, const char *word, INT32 *out) *out = nummenutypes++; return 0; } + else if (fastcmp(type, "KITEM")) + { + *out = DEH_FindKartItem(word); + if (*out != MAXKARTITEMS) + return 0; // already allocated + + if (numkartitems == MAXKARTITEMS) { + CONS_Alert(CONS_WARNING, "Ran out of free kartitem slots!\n"); + return -1; + } + CONS_Printf("Kartitem KITEM_%s allocated.\n",word); + DEH_Link(word, &kartitems[numkartitems].info, &kartitemnames); + K_RegisterItem(numkartitems); + *out = numkartitems++; + return 0; + } return -2; } @@ -750,6 +766,27 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) ignorelines(f); } } + else if (fastcmp(word, "KARTITEM")) + { + if (i == 0 && word2[0] != '0') // If word2 isn't a number + i = get_kartitem(word2); // find a kartitem by name + if (i >= 1 && i < numkartitems) + readkartitem(f, &kartitems[i]); + else + { + // zero-based, but let's start at 1 + deh_warning("KartItem number %d out of range (1 - %d)", i, numkartitems-1); + ignorelines(f); + } + } + else if (fastncmp(word, "KARTRESULT", 10)) + { + boolean alternate = fastcmp(word, "KARTRESULTALT"); + kartresult_t *result = K_GetKartResultAlt(word2, alternate); + if (result == NULL) + result = K_RegisterResult(word2, alternate); + readkartresult(f, result); + } else if (fastcmp(word, "WEATHER") || fastcmp(word, "PRECIP") || fastcmp(word, "PRECIPITATION")) { if (i == 0 && word2[0] != '0') // If word2 isn't a number diff --git a/src/info.c b/src/info.c index 9f25274a6..28513667c 100644 --- a/src/info.c +++ b/src/info.c @@ -117,6 +117,12 @@ static const char *hardcodemenus = #undef _ ; +static const char *hardcodeitems = +#define _(name, ...) #name "\0" +#include "info/kartitems.h" +#undef _ +; + void P_ResetData(INT32 flags) { INT32 i; @@ -239,8 +245,17 @@ void P_ResetData(INT32 flags) if (init) { memset(kartitems, 0, sizeof(kartitems)); - K_InitializeItems(); numkartitems = KITEM_FIRSTFREESLOT; + + if (kartitemnames) + Z_Free(kartitemnames); + kartitemnames = strbuf_alloc(); + name = hardcodeitems; + for (i = 0; i < KITEM_FIRSTFREESLOT; i++, name += strlen(name)+1) + { + DEH_Link(name, &kartitems[i].info, &kartitemnames); + K_RegisterItem(i); + } } } diff --git a/src/info/kartitems.h b/src/info/kartitems.h new file mode 100644 index 000000000..6bff304ca --- /dev/null +++ b/src/info/kartitems.h @@ -0,0 +1,21 @@ +_(NONE) +_(SNEAKER) +_(ROCKETSNEAKER) +_(INVINCIBILITY) +_(BANANA) +_(EGGMAN) +_(ORBINAUT) +_(JAWZ) +_(MINE) +_(BALLHOG) +_(SPB) +_(GROW) +_(SHRINK) +_(THUNDERSHIELD) +_(HYUDORO) +_(POGOSPRING) +_(KITCHENSINK) +_(SUPERRING) +_(LANDMINE) +_(BUBBLESHIELD) +_(FLAMESHIELD) diff --git a/src/k_hud.c b/src/k_hud.c index 72b5a1cb3..4675392d4 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -657,6 +657,9 @@ patch_t *K_GetCachedItemPatch(SINT8 type, boolean tiny, UINT8 amount) kartitem_t *item = &kartitems[type]; kartitemgraphics_t *graphics = &item->graphics[tiny ? 1 : 0]; + if (graphics->numpatches == 0) + return missingpat; + if (item->flags & KIF_ANIMATED) return graphics->patches[(leveltime % (graphics->numpatches*3)) / 3]; else diff --git a/src/k_items.c b/src/k_items.c index 91f8a7892..503b1d783 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -27,6 +27,7 @@ #include "p_setup.h" #include "s_sound.h" #include "typedef.h" +#include "deh_tables.h" #include "k_battle.h" #include "k_boss.h" @@ -42,8 +43,9 @@ kartitem_t kartitems[MAXKARTITEMS] = {0}; kartresult_t kartresults[MAXKARTRESULTS] = {0}; UINT8 numkartitems = 0; UINT8 numkartresults = 0; +UINT8 oddstablemax[MAXODDSTABLES] = { MAXODDS, 2, 4 }; -static CV_PossibleValue_t kartdebugitem_cons_t[MAXKARTITEMS] = {0}; +static CV_PossibleValue_t kartdebugitem_cons_t[MAXKARTITEMS] = {{0, "NONE"}, {0, NULL}}; consvar_t cv_kartdebugitem = CVAR_INIT ("kartdebugitem", "NONE", CV_NETVAR|CV_CHEAT, kartdebugitem_cons_t, NULL); static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}}; consvar_t cv_kartdebugamount = CVAR_INIT ("kartdebugamount", "1", CV_NETVAR|CV_CHEAT, kartdebugamount_cons_t, NULL); @@ -66,31 +68,25 @@ static CV_PossibleValue_t kartitemvariant_cons_t[] = {{0, "Legacy"}, {1, "Altern #define REALMAXBATTLEPROB 10 #define MAXBATTLEPROBABILITY (REALMAXBATTLEPROB * BATTLEODDSMUL) -static UINT8 oddstablelen[MAXODDSTABLES] = { MAXODDS, 2, 4 }; - -static void K_RegisterItem(const char *name, kartitemflags_e flags) +void K_RegisterItem(kartitemtype_e itemtype) { - kartitem_t *item = &kartitems[numkartitems++]; - - item->name = name; - item->flags = flags; - - kartdebugitem_cons_t[numkartitems - 1].strvalue = name; - kartdebugitem_cons_t[numkartitems - 1].value = numkartitems - 1; - kartdebugitem_cons_t[numkartitems].strvalue = NULL; - kartdebugitem_cons_t[numkartitems].value = 0; + kartdebugitem_cons_t[itemtype].strvalue = DEH_KartItemName(itemtype); + kartdebugitem_cons_t[itemtype].value = itemtype; + kartdebugitem_cons_t[itemtype + 1].strvalue = NULL; + kartdebugitem_cons_t[itemtype + 1].value = 0; } -static kartresult_t *K_RegisterResult(const char *name, kartitemtype_e type, UINT8 amount, UINT8 flags) +kartresult_t *K_RegisterResult(const char *name, boolean alternate) { kartresult_t *result = &kartresults[numkartresults]; - result->flags = flags; - // check if a result with this name already exists - kartresult_t *prev = K_GetKartResult(name); - if (prev != NULL) + if (alternate) { - // link to it as an alternate item + // link to the normal result + kartresult_t *prev = K_GetKartResult(name); + if (prev == NULL) + I_Error("K_RegisterResult: Result %s doesn't exist, can't create alt result", name); + result->cvar = prev->cvar; result->type = prev->type; result->amount = prev->amount; @@ -100,8 +96,8 @@ static kartresult_t *K_RegisterResult(const char *name, kartitemtype_e type, UIN kartitem_t *item = &kartitems[result->type]; if (item->altcvar == NULL) { - char *cvname = malloc(strlen(item->name)+1 + 8); - sprintf(cvname, "altitem_%s", item->name); + char *cvname = malloc(strlen(DEH_KartItemName(result->type))+1 + 8); + sprintf(cvname, "altitem_%s", DEH_KartItemName(result->type)); strlwr(cvname); consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &item->altcvar); @@ -115,12 +111,11 @@ static kartresult_t *K_RegisterResult(const char *name, kartitemtype_e type, UIN } else { - result->type = type; - result->amount = amount; - result->isalt = false; - consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &result->cvar); - var->name = name; + char *cvname = Z_StrDup(name); + strlwr(cvname); + + var->name = cvname; var->defaultvalue = "On"; var->flags = CV_NETVAR|CV_CHEAT; var->PossibleValue = CV_OnOff; @@ -132,323 +127,17 @@ static kartresult_t *K_RegisterResult(const char *name, kartitemtype_e type, UIN return result; } -kartresult_t *K_GetKartResult(const char *name) +kartresult_t *K_GetKartResultAlt(const char *name, boolean alternate) { for (UINT8 i = 0; i < numkartresults; i++) { - if (!strcmp(name, kartresults[i].cvar->name)) + if (!stricmp(name, kartresults[i].cvar->name) && kartresults[i].isalt == alternate) return &kartresults[i]; } return NULL; } -// Unique odds, per-item basis - -// Alt. Invincibility's odds. -static useoddsfunc_f K_AltInvinOdds; - -// SPB odds (Race) -static useoddsfunc_f K_SPBRaceOdds; - -void K_InitializeItems(void) -{ - numkartitems = 0; - K_RegisterItem("NONE", 0); - K_RegisterItem("SNEAKER", 0); - K_RegisterItem("ROCKETSNEAKER", 0); - K_RegisterItem("INVINCIBILITY", KIF_ANIMATED|KIF_DARKBG); - K_RegisterItem("BANANA", 0); - K_RegisterItem("EGGMAN", 0); - K_RegisterItem("ORBINAUT", 0); - K_RegisterItem("JAWZ", 0); - K_RegisterItem("MINE", 0); - K_RegisterItem("BALLHOG", 0); - K_RegisterItem("SPB", KIF_DARKBG); - K_RegisterItem("GROW", 0); - K_RegisterItem("SHRINK", 0); - K_RegisterItem("THUNDERSHIELD", KIF_DARKBG); - K_RegisterItem("HYUDORO", 0); - K_RegisterItem("POGOSPRING", 0); - K_RegisterItem("KITCHENSINK", 0); - K_RegisterItem("SUPERRING", 0); - K_RegisterItem("LANDMINE", 0); - K_RegisterItem("BUBBLESHIELD", KIF_DARKBG); - K_RegisterItem("FLAMESHIELD", KIF_DARKBG); - -#define A(item, amount, samount) \ - kartitems[item].graphics[0].numpatches = amount; \ - kartitems[item].graphics[1].numpatches = samount; \ - Z_Malloc(sizeof(const char *) * amount, PU_STATIC, &kartitems[item].graphics[0].patchnames); \ - Z_Malloc(sizeof(const char *) * samount, PU_STATIC, &kartitems[item].graphics[1].patchnames); \ - Z_Calloc(sizeof(patch_t *) * amount, PU_STATIC, &kartitems[item].graphics[0].patches); \ - Z_Calloc(sizeof(patch_t *) * samount, PU_STATIC, &kartitems[item].graphics[1].patches) - - A(KITEM_SNEAKER, 3, 1); - kartitems[KITEM_SNEAKER].graphics[0].patchnames[0] = "K_ITSHOE"; - kartitems[KITEM_SNEAKER].graphics[0].patchnames[1] = "K_ITSHO2"; - kartitems[KITEM_SNEAKER].graphics[0].patchnames[2] = "K_ITSHO3"; - kartitems[KITEM_SNEAKER].graphics[1].patchnames[0] = "K_ISSHOE"; - - A(KITEM_INVINCIBILITY, 7, 6); - kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[0] = "K_ITINV1"; - kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[1] = "K_ITINV2"; - kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[2] = "K_ITINV3"; - kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[3] = "K_ITINV4"; - kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[4] = "K_ITINV5"; - kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[5] = "K_ITINV6"; - kartitems[KITEM_INVINCIBILITY].graphics[0].patchnames[6] = "K_ITINV7"; - kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[0] = "K_ISINV1"; - kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[1] = "K_ISINV2"; - kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[2] = "K_ISINV3"; - kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[3] = "K_ISINV4"; - kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[4] = "K_ISINV5"; - kartitems[KITEM_INVINCIBILITY].graphics[1].patchnames[5] = "K_ISINV6"; - - A(KITEM_BANANA, 4, 1); - kartitems[KITEM_BANANA].graphics[0].patchnames[0] = "K_ITBANA"; - kartitems[KITEM_BANANA].graphics[0].patchnames[1] = "K_ITBAN2"; - kartitems[KITEM_BANANA].graphics[0].patchnames[2] = "K_ITBAN3"; - kartitems[KITEM_BANANA].graphics[0].patchnames[3] = "K_ITBAN4"; - kartitems[KITEM_BANANA].graphics[1].patchnames[0] = "K_ISBANA"; - - A(KITEM_ORBINAUT, 4, 1); - kartitems[KITEM_ORBINAUT].graphics[0].patchnames[0] = "K_ITORB1"; - kartitems[KITEM_ORBINAUT].graphics[0].patchnames[1] = "K_ITORB2"; - kartitems[KITEM_ORBINAUT].graphics[0].patchnames[2] = "K_ITORB3"; - kartitems[KITEM_ORBINAUT].graphics[0].patchnames[3] = "K_ITORB4"; - kartitems[KITEM_ORBINAUT].graphics[1].patchnames[0] = "K_ISORBN"; - - A(KITEM_JAWZ, 2, 1); - kartitems[KITEM_JAWZ].graphics[0].patchnames[0] = "K_ITJAWZ"; - kartitems[KITEM_JAWZ].graphics[0].patchnames[1] = "K_ITJAW2"; - kartitems[KITEM_JAWZ].graphics[1].patchnames[0] = "K_ISJAWZ"; -#undef A - -#define ONE(item, big, small) \ - kartitems[item].graphics[0].numpatches = 1; \ - kartitems[item].graphics[1].numpatches = 1; \ - Z_Malloc(sizeof(const char *), PU_STATIC, &kartitems[item].graphics[0].patchnames); \ - Z_Malloc(sizeof(const char *), PU_STATIC, &kartitems[item].graphics[1].patchnames); \ - Z_Calloc(sizeof(patch_t *), PU_STATIC, &kartitems[item].graphics[0].patches); \ - Z_Calloc(sizeof(patch_t *), PU_STATIC, &kartitems[item].graphics[1].patches); \ - kartitems[item].graphics[0].patchnames[0] = big; \ - kartitems[item].graphics[1].patchnames[0] = small - - ONE(KITEM_ROCKETSNEAKER, "K_ITRSHE", "K_ISRSHE"); - ONE(KITEM_EGGMAN, "K_ITEGGM", "K_ISEGGM"); - ONE(KITEM_MINE, "K_ITMINE", "K_ISMINE"); - ONE(KITEM_BALLHOG, "K_ITBHOG", "K_ISBHOG"); - ONE(KITEM_SPB, "K_ITSPB", "K_ISSPB"); - ONE(KITEM_GROW, "K_ITGROW", "K_ISGROW"); - ONE(KITEM_SHRINK, "K_ITSHRK", "K_ISSHRK"); - ONE(KITEM_THUNDERSHIELD, "K_ITTHNS", "K_ISTHNS"); - ONE(KITEM_HYUDORO, "K_ITHYUD", "K_ISHYUD"); - ONE(KITEM_POGOSPRING, "K_ITPOGO", "K_ISPOGO"); - ONE(KITEM_KITCHENSINK, "K_ITSINK", "K_ISSINK"); - ONE(KITEM_SUPERRING, "K_ITRING", "K_ISRING"); - ONE(KITEM_LANDMINE, "K_ITLNDM", "K_ISLNDM"); - ONE(KITEM_BUBBLESHIELD, "K_ITBUBS", "K_ISBUBS"); - ONE(KITEM_FLAMESHIELD, "K_ITFLMS", "K_ISFLMS"); -#undef ONE - - numkartresults = 0; - K_RegisterResult("sneaker", KITEM_SNEAKER, 1, 0); - K_RegisterResult("rocketsneaker", KITEM_ROCKETSNEAKER, 1, KRF_POWERITEM); - K_RegisterResult("invincibility", KITEM_INVINCIBILITY, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); - K_RegisterResult("banana", KITEM_BANANA, 1, KRF_NOTNEAREND|KRF_NOTFORBOTTOM); - K_RegisterResult("eggman", KITEM_EGGMAN, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND|KRF_NOTFORBOTTOM); - K_RegisterResult("orbinaut", KITEM_ORBINAUT, 1, 0); - K_RegisterResult("jawz", KITEM_JAWZ, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); - K_RegisterResult("mine", KITEM_MINE, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); - K_RegisterResult("ballhog", KITEM_BALLHOG, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM); - kartresult_t *spb = K_RegisterResult("spb", KITEM_SPB, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_NOTNEAREND|KRF_RUNNERAUGMENT); - K_RegisterResult("grow", KITEM_GROW, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM); - K_RegisterResult("shrink", KITEM_SHRINK, 1, KRF_COOLDOWNONSTART|KRF_INDIRECTITEM|KRF_POWERITEM|KRF_NOTNEAREND); - K_RegisterResult("thundershield", KITEM_THUNDERSHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_UNIQUE); - K_RegisterResult("hyudoro", KITEM_HYUDORO, 1, KRF_COOLDOWNONSTART); - K_RegisterResult("pogospring", KITEM_POGOSPRING, 1, 0); - K_RegisterResult("kitchensink", KITEM_KITCHENSINK, 1, 0); - K_RegisterResult("superring", KITEM_SUPERRING, 1, KRF_NOTNEAREND|KRF_NOTFORBOTTOM); - K_RegisterResult("landmine", KITEM_LANDMINE, 1, 0); - K_RegisterResult("bubbleshield", KITEM_BUBBLESHIELD, 1, KRF_NOTFORBOTTOM|KRF_POWERITEM|KRF_UNIQUE); - K_RegisterResult("flameshield", KITEM_FLAMESHIELD, 1, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_UNIQUE); - - K_RegisterResult("dualsneaker", KITEM_SNEAKER, 2, 0); - K_RegisterResult("triplesneaker", KITEM_SNEAKER, 3, KRF_POWERITEM); - K_RegisterResult("triplebanana", KITEM_BANANA, 3, KRF_POWERITEM|KRF_NOTFORBOTTOM|KRF_NOTNEAREND); - K_RegisterResult("decabanana", KITEM_BANANA, 10, KRF_POWERITEM|KRF_NOTFORBOTTOM|KRF_NOTNEAREND); - K_RegisterResult("tripleorbinaut", KITEM_ORBINAUT, 3, KRF_NOTFORBOTTOM|KRF_POWERITEM); - K_RegisterResult("quadorbinaut", KITEM_ORBINAUT, 4, KRF_NOTFORBOTTOM|KRF_POWERITEM); - K_RegisterResult("dualjawz", KITEM_JAWZ, 2, KRF_NOTFORBOTTOM|KRF_POWERITEM); - - // alt items defined below (it's ass, but this will do until everything is SOCced) - - // It's a power item, yes, but we don't want mashing to lessen - // its chances, so we lie to the game's face. - // Start cooldowns are handled in the odds function; don't set the flag here. - // (Also, PLEASE prevent shitty last lap bagging endings.) - kartresult_t *altinv = K_RegisterResult("invincibility", 0, 0, KRF_NOTNEAREND); - - kartresult_t *altshrink = K_RegisterResult("shrink", 0, 0, KRF_COOLDOWNONSTART|KRF_POWERITEM|KRF_NOTNEAREND); - - kartresult_t *eggmine = K_RegisterResult("eggman", 0, 0, KRF_NOTFORBOTTOM); - -#define O(item, ...) memcpy(K_GetKartResult(#item)->odds[WHICH], (UINT8[MAXODDS]){__VA_ARGS__}, MAXODDS); -#define WHICH ODDS_RACE - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - O(sneaker, 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Sneaker - O(rocketsneaker, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37) // Rocket Sneaker - O(invincibility, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 24, 50, 75, 75) // Invincibility - O(banana, 35, 28, 22, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana - O(eggman, 18, 15, 12, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Eggman Monitor - O(orbinaut, 45, 28, 24, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut - O(jawz, 0, 11, 22, 9, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz - O(mine, 0, 0, 5, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Mine - O(ballhog, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Ballhog - O(spb, 0, 0, 2, 3, 4, 5, 6, 9, 10, 12, 8, 5, 2, 2, 0, 0) // Self-Propelled Bomb - O(grow, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 12, 9, 0, 0, 0) // Grow - O(shrink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 3, 0, 0) // Shrink - O(thundershield, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Thunder Shield - O(hyudoro, 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 0, 0, 0, 0, 0, 0) // Hyudoro - O(pogospring, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Pogo Spring - O(kitchensink, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Kitchen Sink - O(superring, 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Super Ring - O(landmine, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Land Mine - O(bubbleshield, 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Bubble Shield - O(flameshield, 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0) // Flame Shield - O(dualsneaker, 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0) // Sneaker x2 - O(triplesneaker, 0, 0, 0, 0, 0, 0, 0, 7, 35, 45, 60, 56, 52, 4, 0, 0) // Sneaker x3 - O(triplebanana, 0, 7, 7, 7, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x3 - O(decabanana, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Banana x10 - O(tripleorbinaut, 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x3 - O(quadorbinaut, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Orbinaut x4 - O(dualjawz, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // Jawz x2 - memcpy(altinv->odds[WHICH], (UINT8[MAXODDS]) // Alt. Invincibility - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MAXODDS); - memcpy(altshrink->odds[WHICH], (UINT8[MAXODDS]) // Alt. Shrink - { 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MAXODDS); - memcpy(eggmine->odds[WHICH], (UINT8[MAXODDS]) // Egg Mine - { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MAXODDS); - - altinv->unique_odds[WHICH] = K_AltInvinOdds; // Alternate: uses unique odds - spb->unique_odds[WHICH] = K_SPBRaceOdds; -#undef WHICH - -#define WHICH ODDS_BATTLE - // R S - O(sneaker, 2, 1) // Sneaker - O(rocketsneaker, 0, 0) // Rocket Sneaker - O(invincibility, 4, 1) // Invincibility - O(banana, 0, 0) // Banana - O(eggman, 1, 0) // Eggman Monitor - O(orbinaut, 8, 0) // Orbinaut - O(jawz, 8, 1) // Jawz - O(mine, 6, 1) // Mine - O(ballhog, 2, 1) // Ballhog - O(spb, 0, 0) // Self-Propelled Bomb - O(grow, 2, 1) // Grow - O(shrink, 0, 0) // Shrink - O(thundershield, 4, 0) // Thunder Shield - O(hyudoro, 2, 0) // Hyudoro - O(pogospring, 3, 0) // Pogo Spring - O(kitchensink, 0, 0) // Kitchen Sink - O(superring, 0, 0) // Super Ring - O(landmine, 2, 0) // Land Mine - O(bubbleshield, 1, 0) // Bubble Shield - O(flameshield, 1, 0) // Flame Shield - O(dualsneaker, 0, 0) // Sneaker x2 - O(triplesneaker, 0, 1) // Sneaker x3 - O(triplebanana, 0, 0) // Banana x3 - O(decabanana, 1, 1) // Banana x10 - O(tripleorbinaut, 2, 0) // Orbinaut x3 - O(quadorbinaut, 1, 1) // Orbinaut x4 - O(dualjawz, 5, 1) // Jawz x2 - memcpy(altinv->odds[WHICH], (UINT8[MAXODDS]) // Alt. Invincibility - { 0, 0}, MAXODDS); - memcpy(altshrink->odds[WHICH], (UINT8[MAXODDS]) // Alt. Shrink - { 1, 0}, MAXODDS); - memcpy(eggmine->odds[WHICH], (UINT8[MAXODDS]) // Egg Mine - { 2, 0}, MAXODDS); -#undef WHICH - -#define WHICH ODDS_SPECIAL - // M N O P - O(sneaker, 1, 1, 0, 0) // Sneaker - O(rocketsneaker, 0, 0, 0, 0) // Rocket Sneaker - O(invincibility, 0, 0, 0, 0) // Invincibility - O(banana, 0, 0, 0, 0) // Banana - O(eggman, 0, 0, 0, 0) // Eggman Monitor - O(orbinaut, 1, 1, 0, 0) // Orbinaut - O(jawz, 1, 1, 0, 0) // Jawz - O(mine, 0, 0, 0, 0) // Mine - O(ballhog, 0, 0, 0, 0) // Ballhog - O(spb, 0, 0, 0, 1) // Self-Propelled Bomb - O(grow, 0, 0, 0, 0) // Grow - O(shrink, 0, 0, 0, 0) // Shrink - O(thundershield, 0, 0, 0, 0) // Thunder Shield - O(hyudoro, 0, 0, 0, 0) // Hyudoro - O(pogospring, 0, 0, 0, 0) // Pogo Spring - O(kitchensink, 0, 0, 0, 0) // Kitchen Sink - O(superring, 0, 0, 0, 0) // Super Ring - O(landmine, 0, 0, 0, 0) // Land Mine - O(bubbleshield, 0, 0, 0, 0) // Bubble Shield - O(flameshield, 0, 0, 0, 0) // Flame Shield - O(dualsneaker, 0, 1, 1, 0) // Sneaker x2 - O(triplesneaker, 0, 0, 1, 1) // Sneaker x3 - O(triplebanana, 0, 0, 0, 0) // Banana x3 - O(decabanana, 0, 0, 0, 0) // Banana x10 - O(tripleorbinaut, 0, 1, 1, 0) // Orbinaut x3 - O(quadorbinaut, 0, 0, 1, 1) // Orbinaut x4 - O(dualjawz, 0, 0, 1, 1) // Jawz x2 - memcpy(altinv->odds[WHICH], (UINT8[MAXODDS]) // Alt. Invincibility - { 0, 0, 0, 0}, MAXODDS); - memcpy(altshrink->odds[WHICH], (UINT8[MAXODDS]) // Alt. Shrink - { 0, 0, 0, 0}, MAXODDS); - memcpy(eggmine->odds[WHICH], (UINT8[MAXODDS]) // Egg Mine - { 0, 0, 0, 0}, MAXODDS); -#undef WHICH -#undef O - -// Cooldown time table; contains both base (index 0) and current (index 1) -// times. Base times are in seconds, current times are in tics. -#define O(item, v) K_GetKartResult(#item)->basebgone = v; - O(sneaker, 0) // Sneaker - O(rocketsneaker, 0) // Rocket Sneaker - O(invincibility, 0) // Invincibility - O(banana, 0) // Banana - O(eggman, 10) // Eggman Monitor - O(orbinaut, 0) // Orbinaut - O(jawz, 5) // Jawz - O(mine, 0) // Mine - O(ballhog, 10) // Ballhog - O(spb, 20) // Self-Propelled Bomb - O(grow, 3) // Grow - O(shrink, 20) // Shrink - O(thundershield, 0) // Thunder Shield - O(hyudoro, 20) // Hyudoro - O(pogospring, 0) // Pogo Spring - O(kitchensink, 0) // Kitchen Sink - O(superring, 0) // Super Ring - O(landmine, 0) // Land Mine - O(bubbleshield, 5) // Bubble Shield - O(flameshield, 8) // Flame Shield - O(dualsneaker, 0) // Sneaker x2 - O(triplesneaker, 0) // Sneaker x3 - O(triplebanana, 0) // Banana x3 - O(decabanana, 30) // Banana x10 - O(tripleorbinaut, 10) // Orbinaut x3 - O(quadorbinaut, 20) // Orbinaut x4 - O(dualjawz, 10) // Jawz x2 - altinv->basebgone = 0; // Alt. Invincibility - altshrink->basebgone = 5; // Alt. Shrink - eggmine->basebgone = 0; // Egg Mine -#undef O - - CV_RegisterVar(&cv_kartdebugitem); - CV_RegisterVar(&cv_kartdebugamount); -} - void K_SetupItemOdds(void) { // Update the item's variant based on the cvar's value. @@ -457,7 +146,7 @@ void K_SetupItemOdds(void) kartitem_t *item = &kartitems[i]; if (item->altcvar != NULL && item->altenabled != !!item->altcvar->value) { - CONS_Printf("KITEM_%s: updating variant to %s\n", item->name, item->altcvar->string); + CONS_Printf("KITEM_%s: updating variant to %s\n", DEH_KartItemName(i), item->altcvar->string); item->altenabled = !!item->altcvar->value; } } @@ -761,55 +450,6 @@ void K_KartHandleUniqueCooldown(void) } } -// Unique odds functions, for REAL this time - -// Alt. Invin. odds -static INT32 K_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result) -{ - odds = K_KartGetInvincibilityOdds(roulette->clusterDist); - - // Special case: if you're SERIOUSLY far behind before the cooldown finishes, ignore it and start forcing, - if (odds >= INVFORCEODDS) - { - result->forceme = 3; // Take priority over SPBs - } - else if (K_InStartCooldown()) - { - odds = 0; - } - - odds *= BASEODDSMUL; - - return odds; -} - -// SPB odds -static INT32 K_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result) -{ - if (!K_LegacyOddsMode() && roulette->firstDist < (UINT32)ENDDIST) // No SPB near the end of the race - { - odds = 0; - } - else if (K_LegacyOddsMode() && roulette->pexiting > 0) - { - odds = 0; - } - - // No forced SPB in 1v1s, it has to be randomly rolled - if (roulette->pingame <= 2) - { - result->forceme = 0; - } - else if (K_RaceForceSPB(roulette->playerpos, roulette->pdis) - && spbplace == -1 && !indirectitemcooldown) - { - // Force SPB onto 2nd if they get too far behind. - result->forceme = 2; - } - - return odds; -} - static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result) { INT32 newodds; @@ -855,7 +495,7 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result) else oddstable = ODDS_SPECIAL; - I_Assert(roulette->pos < oddstablelen[oddstable]); // DO NOT allow positions past the bounds of the table + I_Assert(roulette->pos < oddstablemax[oddstable]); // DO NOT allow positions past the bounds of the table if (gametyperules & GTR_BATTLEODDS) oddsmul = BATTLEODDSMUL; @@ -1788,6 +1428,55 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) player->roulettetype = KROULETTETYPE_NORMAL; // This too } +// Unique odds functions, for REAL this time + +// Alt. Invin. odds +INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result) +{ + odds = K_KartGetInvincibilityOdds(roulette->clusterDist); + + // Special case: if you're SERIOUSLY far behind before the cooldown finishes, ignore it and start forcing, + if (odds >= INVFORCEODDS) + { + result->forceme = 3; // Take priority over SPBs + } + else if (K_InStartCooldown()) + { + odds = 0; + } + + odds *= BASEODDSMUL; + + return odds; +} + +// SPB odds +INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result) +{ + if (!K_LegacyOddsMode() && roulette->firstDist < (UINT32)ENDDIST) // No SPB near the end of the race + { + odds = 0; + } + else if (K_LegacyOddsMode() && roulette->pexiting > 0) + { + odds = 0; + } + + // No forced SPB in 1v1s, it has to be randomly rolled + if (roulette->pingame <= 2) + { + result->forceme = 0; + } + else if (K_RaceForceSPB(roulette->playerpos, roulette->pdis) + && spbplace == -1 && !indirectitemcooldown) + { + // Force SPB onto 2nd if they get too far behind. + result->forceme = 2; + } + + return odds; +} + /** \brief Returns true or false if the player is shrunk. \param player (player_t) Player to test shrunken status for diff --git a/src/k_items.h b/src/k_items.h index 99cbe4afa..57aa70f0c 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -33,28 +33,9 @@ extern "C" { typedef enum { KITEM_SAD = -1, - KITEM_NONE = 0, - KITEM_SNEAKER, - KITEM_ROCKETSNEAKER, - KITEM_INVINCIBILITY, - KITEM_BANANA, - KITEM_EGGMAN, - KITEM_ORBINAUT, - KITEM_JAWZ, - KITEM_MINE, - KITEM_BALLHOG, - KITEM_SPB, - KITEM_GROW, - KITEM_SHRINK, - KITEM_THUNDERSHIELD, - KITEM_HYUDORO, - KITEM_POGOSPRING, - KITEM_KITCHENSINK, - KITEM_SUPERRING, - KITEM_LANDMINE, - KITEM_BUBBLESHIELD, - KITEM_FLAMESHIELD, - +#define _(name, ...) KITEM_##name, +#include "info/kartitems.h" +#undef _ KITEM_FIRSTFREESLOT, KITEM_LASTFREESLOT = 126, MAXKARTITEMS @@ -91,16 +72,21 @@ typedef enum // free: 1<<7 } ATTRPACK kartresultflags_e; +// Unique useodds function +typedef INT32 (useoddsfunc_f)(INT32 odds, const kartroulette_t *roulette, kartresult_t *result); + +#define MAXITEMPATCHES 10 + struct kartitemgraphics_t { UINT8 numpatches; - const char **patchnames; - patch_t **patches; + char *patchnames[MAXITEMPATCHES]; + patch_t *patches[MAXITEMPATCHES]; }; struct kartitem_t { - const char *name; + dehinfo_t info; kartitemgraphics_t graphics[2]; kartitemflags_e flags; @@ -112,11 +98,6 @@ struct kartitem_t // int (*resultOverrideFunc)(player_t *p); }; -// Unique useodds function: -// uoddsfunc(result, playerpos, pdis, newodds, pos, pingame, pexiting, firstDist, ourDist, clusterDist, mashed, spbrush, bot, rival, inBottom) -// FIXME: This is fucking gross; is there not a better way to do this? -typedef INT32 (useoddsfunc_f)(INT32 odds, const kartroulette_t *roulette, kartresult_t *result); - struct kartresult_t { // this block is shared by all item variants @@ -160,9 +141,12 @@ extern kartitem_t kartitems[MAXKARTITEMS]; extern UINT8 numkartitems; extern kartresult_t kartresults[MAXKARTRESULTS]; extern UINT8 numkartresults; +extern UINT8 oddstablemax[MAXODDSTABLES]; -kartresult_t *K_GetKartResult(const char *name); -void K_InitializeItems(void); +void K_RegisterItem(kartitemtype_e itemtype); +kartresult_t *K_RegisterResult(const char *name, boolean alternate); +kartresult_t *K_GetKartResultAlt(const char *name, boolean alternate); +#define K_GetKartResult(name) K_GetKartResultAlt(name, false) boolean K_ItemResultEnabled(const kartresult_t *result); boolean K_IsKartItemAlternate(kartitemtype_e itemtype); void K_SetupItemOdds(void); @@ -190,6 +174,9 @@ boolean K_IsShrunkMode(const player_t *player, boolean legacy); void K_AltShrinkIFrames(player_t *player); void K_AltShrinkPityIncrease(player_t *player); +INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result); +INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_kart.c b/src/k_kart.c index 6b031a7e8..6167e5df7 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -257,6 +257,8 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartusepwrlv); CV_RegisterVar(&cv_votetime); + CV_RegisterVar(&cv_kartdebugitem); + CV_RegisterVar(&cv_kartdebugamount); CV_RegisterVar(&cv_kartdebugshrink); CV_RegisterVar(&cv_kartdebugdistribution); CV_RegisterVar(&cv_kartdebughuddrop); From 4986bde9ab235722350399f4d1bc2432109adfe4 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 8 Nov 2025 02:09:34 +0100 Subject: [PATCH 22/76] Restore the item toggle menu back to working order --- src/deh_soc.c | 11 +++-- src/deh_tables.c | 1 + src/k_items.h | 1 + src/m_menu.c | 106 +++++++++++++++++++++++++++++++++-------------- src/m_menu.h | 1 + 5 files changed, 85 insertions(+), 35 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index b83845184..8ae9869d5 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -4076,12 +4076,10 @@ void readkartitem(MYFILE *f, kartitem_t *item) if (fastcmp(word, "BIGPATCHES") || fastcmp(word, "SMALLPATCHES")) { kartitemgraphics_t *graphics = &item->graphics[word[0] == 'B' ? 0 : 1]; - UINT8 i; tmp = strtok(word2,","); - for (i = 0; i < MAXITEMPATCHES; i++) + for (graphics->numpatches = 0; graphics->numpatches < MAXITEMPATCHES;) { - graphics->numpatches++; - graphics->patchnames[i] = Z_StrDup(tmp); + graphics->patchnames[graphics->numpatches++] = Z_StrDup(tmp); if ((tmp = strtok(NULL,",")) == NULL) break; } @@ -4179,6 +4177,7 @@ void readkartresult(MYFILE *f, kartresult_t *result) } UINT8 *odds = result->odds[oddstable]; + memset(odds, 0, sizeof(*odds)*MAXODDS); tmp = strtok(word2,","); for (UINT8 i = 0; i <= MAXODDS; i++) { @@ -4214,6 +4213,10 @@ void readkartresult(MYFILE *f, kartresult_t *result) { result->basebgone = atoi(word2) * TICRATE; } + else if (fastcmp(word, "DISPLAYNAME")) + { + result->displayname = Z_StrDup(word2); + } else WARN("unknown word '%s'", word); } diff --git a/src/deh_tables.c b/src/deh_tables.c index 3797b52eb..e5b1cbea1 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -815,6 +815,7 @@ struct menu_routine_s const MENU_ROUTINES[] = { { "RESETCONTROLS", &MR_ResetControls }, { "CHANGECONTROL", &MR_ChangeControl }, { "ASSIGNJOYSTICK", &MR_AssignJoystick }, + { "SETUPMONITORTOGGLES", &MR_SetupMonitorToggles }, { "HANDLEMONITORTOGGLES", &MR_HandleMonitorToggles }, { "RESTARTAUDIO", &MR_RestartAudio }, { "ENTERVIEWSERVER", &MR_EnterViewServer }, diff --git a/src/k_items.h b/src/k_items.h index 57aa70f0c..cfb884d6a 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -105,6 +105,7 @@ struct kartresult_t kartitemtype_e type; UINT8 amount; + char *displayname; boolean isalt; // is alt (i salt) kartresultflags_e flags; UINT8 forceme; // This item is being forced into a player's item slot for one reason or another. Higher value = higher priority. diff --git a/src/m_menu.c b/src/m_menu.c index 8f4abb59f..4fb8f5cda 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -8317,6 +8317,52 @@ INT32 MR_CameraSetup(INT32 arg) static tic_t shitsfree = 0; +INT32 MR_SetupMonitorToggles(INT32 choice) +{ + (void)choice; + const INT32 height = 4; + menu_t *menudef = &menudefs[MN_OP_MONITORTOGGLE]; + + // in the interest of not rushing the implementation of menu frames... + // leak the memory. every single time. + // (i dont think this actually leaks anything lol) + // (unless someone adds menuitems to this menu for some stupid reason) + menudef->numitems = 0; + + for (UINT8 i = 0; i <= numkartresults; i++) + { + kartresult_t *result = i < 3 ? &kartresults[i] : i == 3 ? NULL : &kartresults[i - 1]; + if (result != NULL && result->isalt) + continue; + + menudef->menuitems = Z_Realloc(menudef->menuitems, sizeof(menuitem_t)*(menudef->numitems+1), PU_STATIC, NULL); + menuitem_t *item = menudef->menuitems + menudef->numitems++; + + if (result == NULL) + { + item->argument = 1; + item->patch = ""; + } + else + { + item->argument = 0; + item->patch = result->cvar->name; + } + } + + while (menudef->numitems % height != 0) + { + menudef->menuitems = Z_Realloc(menudef->menuitems, sizeof(menuitem_t)*(menudef->numitems+1), PU_STATIC, NULL); + menuitem_t *item = menudef->menuitems + menudef->numitems++; + + item->argument = 2; + item->patch = ""; + } + + menudef->x = 136 - FixedMul(max(0, min((menudef->numitems - 1) / height, 7)), TICRATE*FRACUNIT/2); + return true; +} + void MD_DrawMonitorToggles(void) { const INT32 edges = 4; @@ -8327,7 +8373,11 @@ void MD_DrawMonitorToggles(void) INT32 leftdraw, rightdraw, totaldraw; INT32 x = currentMenu->x, y = currentMenu->y+(spacing/4); INT32 onx = 0, ony = 0; - INT32 i, translucent, drawnum, arg; + INT32 i, translucent; + const kartresult_t *result = K_GetKartResult(currentMenu->menuitems[itemOn].patch); + const char *displayname; + if (result != NULL && kartitems[result->type].altcvar != NULL && kartitems[result->type].altcvar->value == 1) + result = K_GetKartResultAlt(result->cvar->name, true); M_DrawMenuTitle(); @@ -8366,39 +8416,32 @@ void MD_DrawMonitorToggles(void) continue; } - arg = currentMenu->menuitems[thisitem].argument; - if (arg == 255) + switch (currentMenu->menuitems[thisitem].argument) { + case 2: V_DrawScaledPatch(x, y, V_TRANSLUCENT, W_CachePatchName("K_ISBG", PU_CACHE)); continue; - } - else if (arg == 0) - { + case 1: V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISTOGL", PU_CACHE)); continue; } - else if (arg <= 0 && arg > numkartresults) - { - continue; - } - kartresult_t *result = &kartresults[arg - 1]; + kartresult_t *result = K_GetKartResult(currentMenu->menuitems[thisitem].patch); kartitem_t *item = &kartitems[result->type]; boolean enabled = K_ItemResultEnabled(result); translucent = enabled ? 0 : V_TRANSLUCENT; - drawnum = item->flags & KIF_ANIMATED ? 0 : CLAMP(result->amount, 0, item->graphics[1].numpatches - 1); if (enabled) V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); else V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBGD", PU_CACHE)); - if (drawnum > 1) + if (result->amount > item->graphics[1].numpatches && !(item->flags & KIF_ANIMATED)) { V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISMUL", PU_CACHE)); V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); - V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|translucent, va("x%d", drawnum)); + V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|translucent, va("x%d", result->amount)); } else V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); @@ -8408,10 +8451,11 @@ void MD_DrawMonitorToggles(void) y = currentMenu->y+(spacing/4); } + switch (currentMenu->menuitems[itemOn].argument) { - arg = currentMenu->menuitems[itemOn].argument; - if (arg == 255) + case 2: { + displayname = "---"; V_DrawScaledPatch(onx-1, ony-2, V_TRANSLUCENT, W_CachePatchName("K_ITBG", PU_CACHE)); if (shitsfree) { @@ -8422,31 +8466,33 @@ void MD_DrawMonitorToggles(void) trans = (10-shitsfree)< 0 && arg <= numkartresults) + default: { - kartresult_t *result = &kartresults[arg - 1]; + displayname = result->displayname; kartitem_t *item = &kartitems[result->type]; boolean enabled = K_ItemResultEnabled(result); translucent = enabled ? 0 : V_TRANSLUCENT; - drawnum = item->flags & KIF_ANIMATED ? 0 : CLAMP(result->amount, 0, item->graphics[0].numpatches - 1); if (enabled) V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); else V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE)); - if (drawnum > 1) + if (result->amount > item->graphics[0].numpatches && !(item->flags & KIF_ANIMATED)) { V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITMUL", PU_CACHE)); V_DrawScaledPatch(onx-1, ony-2, translucent, K_GetCachedItemPatch(result->type, false, result->amount)); V_DrawScaledPatch(onx+27, ony+39, translucent, W_CachePatchName("K_ITX", PU_CACHE)); - V_DrawKartString(onx+37, ony+34, translucent, va("%d", drawnum)); + V_DrawKartString(onx+37, ony+34, translucent, va("%d", result->amount)); } else V_DrawScaledPatch(onx-1, ony-2, translucent, K_GetCachedItemPatch(result->type, false, result->amount)); @@ -8456,25 +8502,23 @@ void MD_DrawMonitorToggles(void) translucent = item->altcvar->value == 1 ? 0 : V_TRANSLUCENT; V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName("K_ALTITM", PU_CACHE)); } + break; } } if (renderisnewtic && shitsfree) shitsfree--; - V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, MENUCAPS|highlightflags, va("* %s *", currentMenu->menuitems[itemOn].text)); + V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, MENUCAPS|highlightflags, va("* %s *", displayname)); } INT32 MR_HandleMonitorToggles(INT32 choice) { - const INT32 width = 6, height = 4; + const INT32 height = 4; + const INT32 width = currentMenu->numitems/height; INT32 column = itemOn/height, row = itemOn%height; INT16 next; - - kartresult_t *result = NULL; - INT32 arg = currentMenu->menuitems[itemOn].argument; - if (arg > 0 && arg <= numkartresults) - result = &kartresults[arg - 1]; + kartresult_t *result = K_GetKartResult(currentMenu->menuitems[itemOn].patch); switch (choice) { @@ -8523,7 +8567,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) break; case KEY_ENTER: - if (choice == 255) + if (currentMenu->menuitems[itemOn].argument == 2) { //S_StartSound(NULL, sfx_s26d); if (!shitsfree) @@ -8532,7 +8576,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) S_StartSound(NULL, sfx_itfree); } } - else if (choice == 0) + else if (currentMenu->menuitems[itemOn].argument == 1) { INT32 v = K_ItemResultEnabled(K_GetKartResult("sneaker")) ? 1 : 0; S_StartSound(NULL, sfx_s1b4); diff --git a/src/m_menu.h b/src/m_menu.h index 4942956b7..5cf612164 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -323,6 +323,7 @@ INT32 MR_HandleMusicTest(INT32 choice); INT32 MR_ResetControls(INT32 choice); INT32 MR_ChangeControl(INT32 arg); INT32 MR_AssignJoystick(INT32 arg); +INT32 MR_SetupMonitorToggles(INT32 choice); INT32 MR_HandleMonitorToggles(INT32 choice); INT32 MR_RestartAudio(INT32 choice); INT32 MR_EnterViewServer(INT32 choice); From a2e2759c0a0bcf9681126941f05e4afd2db57c00 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Fri, 7 Nov 2025 21:24:04 -0500 Subject: [PATCH 23/76] Port various old item hooks from custom v1 builds --- src/k_kart.c | 995 +++++++++++++++++++++++----------------------- src/lua_hook.h | 10 + src/lua_hooklib.c | 112 ++++++ src/p_mobj.c | 3 + 4 files changed, 629 insertions(+), 491 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 6167e5df7..549182b07 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4683,6 +4683,7 @@ static void K_DoHyudoroSteal(player_t *player) INT32 stealplayer = -1; // The player that's getting stolen from INT32 prandom = 0; boolean sink = P_RandomChance(FRACUNIT/64); + boolean force_sink = false; INT32 hyu = hyudorotime; for (i = 0; i < MAXPLAYERS; i++) @@ -4708,7 +4709,18 @@ static void K_DoHyudoroSteal(player_t *player) prandom = P_RandomFixed(); S_StartSound(player->mo, sfx_s3k92); - if (sink && numplayers > 0 && K_ItemResultEnabled(K_GetKartResult("kitchensink"))) // BEHOLD THE KITCHEN SINK + if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from + { + stealplayer = playerswappable[numplayers-1]; + } + else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player + { + stealplayer = playerswappable[prandom%(numplayers-1)]; + } + + force_sink = LUA_HookKartHyudoro(player, &stealplayer, sink); + + if (force_sink || (sink && numplayers > 0 && K_ItemResultEnabled(K_GetKartResult("kitchensink")))) // BEHOLD THE KITCHEN SINK { player->hyudorotimer = hyu; player->stealingtimer = stealtime; @@ -4727,14 +4739,6 @@ static void K_DoHyudoroSteal(player_t *player) player->stealingtimer = stealtime; return; } - else if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from - { - stealplayer = playerswappable[numplayers-1]; - } - else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player - { - stealplayer = playerswappable[prandom%(numplayers-1)]; - } if (stealplayer > -1) // Now here's where we do the stealing, has to be done here because we still know the player we're stealing from { @@ -4811,6 +4815,9 @@ void K_DoSneaker(player_t *player, INT32 type) const fixed_t intendedboost = K_GetSneakerBoostSpeed(); const tic_t sneakerduration = (type == SNEAKERTYPE_WATERPANEL) ? waterpaneltime : sneakertime; + if (LUA_HookKartSneaker(player, type)) + return; + if (player->floorboost == 0 || player->floorboost == 3) { K_SneakerPanelStackSound(player); @@ -10536,506 +10543,512 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else { - switch (player->itemtype) + boolean force = false; + boolean override = LUA_HookPlayerItem(player, player->itemtype, HOLDING_ITEM, &force); + + if (!override) { - case KITEM_SNEAKER: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO) - { - K_DoSneaker(player, SNEAKERTYPE_SNEAKER); - K_PlayBoostTaunt(player->mo); - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_ROCKETSNEAKER: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO - && player->rocketsneakertimer == 0) - { - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - K_PlayBoostTaunt(player->mo); - S_StartSound(player->mo, sfx_s3k3a); - - //K_DoSneaker(player, SNEAKERTYPE_ROCKETSNEAKER); - - player->rocketsneakertimer = (itemtime*3); - player->itemamount--; - K_UpdateHnextList(player, true); - - for (moloop = 0; moloop < 2; moloop++) + switch (player->itemtype) + { + case KITEM_SNEAKER: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO) { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER); - K_MatchGenericExtraFlags(mo, player->mo); - mo->flags |= MF_NOCLIPTHING; - mo->angle = player->mo->angle; - mo->threshold = 10; - mo->movecount = moloop%2; - mo->movedir = mo->lastlook = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_INVINCIBILITY: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage in Legacy, so you're free to waste it if you have multiple - { - K_DoInvincibility(player, K_GetInvincibilityTime(player)); - K_PlayPowerGloatSound(player->mo); - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_BANANA: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - INT32 moloop; - mobj_t *mo; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown - { - player->itemamount--; - - mobj_t *mo = K_ThrowKartItem(player, false, MT_BANANA, -1, 0); - mo->color = player->skincolor; - - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_EGGMAN: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - player->itemamount--; - player->itemflags |= IF_EGGMANOUT; - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); - if (mo) - { - K_FlipFromObject(mo, player->mo); - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); - } - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_ORBINAUT: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - angle_t newangle; - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s3k3a); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->angle = newangle; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = mo->lastlook = moloop+1; - mo->color = player->skincolor; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_JAWZ: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - angle_t newangle; - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s3k3a); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->angle = newangle; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = mo->lastlook = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown - { - player->itemamount--; - if (player->throwdir == 1 || player->throwdir == 0) - K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); - else if (player->throwdir == -1) // Throwing backward gives you a dud that doesn't home in - K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0); - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_MINE: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD); - if (mo) - { - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) - { - player->itemamount--; - K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); - K_PlayAttackTaunt(player->mo); - player->itemflags &= ~IF_ITEMOUT; - K_UpdateHnextList(player, true); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_LANDMINE: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowLandMine(player); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_BALLHOG: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SPB: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_SPB, 1, 0); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_GROW: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - if (K_IsLegacyShrunk(player)) - { - // If you're shrunk, then "grow" will just make you normal again... - K_RemoveGrowShrink(player); - } - else - { - // ...in Legacy mode. - // Alt. Shrink's a powerup, so Grow overrides! - if (K_IsAltShrunk(player)) - { - player->growshrinktimer = 0; // Paranoia - } - - K_PlayPowerGloatSound(player->mo); - - K_DoGrowShrink(player, false); - - S_StartSound(player->mo, sfx_kc5a); - } - - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SHRINK: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - if (K_IsKartItemAlternate(KITEM_SHRINK)) - { - K_DoGrowShrink(player, true); - K_AltShrinkIFrames(player); - - S_StartSound(player->mo, sfx_kc46); - } - else - { - K_DoShrink(player); - } - - player->itemamount--; - K_PlayPowerGloatSound(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_THUNDERSHIELD: - if (K_GetShieldFromPlayer(player) != KSHIELD_THUNDER) - { - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k41); - } - - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - K_DoThunderShield(player); - if (player->itemamount > 0) - { - // Why is this a conditional? - // Thunder shield: the only item that allows you to - // activate a mine while you're out of its radius, - // the SAME tic it sets your itemamount to 0 - // ...:dumbestass: + K_DoSneaker(player, SNEAKERTYPE_SNEAKER); + K_PlayBoostTaunt(player->mo); player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_ROCKETSNEAKER: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO + && player->rocketsneakertimer == 0) + { + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + K_PlayBoostTaunt(player->mo); + S_StartSound(player->mo, sfx_s3k3a); + + //K_DoSneaker(player, SNEAKERTYPE_ROCKETSNEAKER); + + player->rocketsneakertimer = (itemtime*3); + player->itemamount--; + K_UpdateHnextList(player, true); + + for (moloop = 0; moloop < 2; moloop++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER); + K_MatchGenericExtraFlags(mo, player->mo); + mo->flags |= MF_NOCLIPTHING; + mo->angle = player->mo->angle; + mo->threshold = 10; + mo->movecount = moloop%2; + mo->movedir = mo->lastlook = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_INVINCIBILITY: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage in Legacy, so you're free to waste it if you have multiple + { + K_DoInvincibility(player, K_GetInvincibilityTime(player)); + K_PlayPowerGloatSound(player->mo); + player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_BANANA: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + INT32 moloop; + mobj_t *mo; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown + { + player->itemamount--; + + mobj_t *mo = K_ThrowKartItem(player, false, MT_BANANA, -1, 0); + mo->color = player->skincolor; + + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_EGGMAN: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + player->itemamount--; + player->itemflags |= IF_EGGMANOUT; + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); + if (mo) + { + K_FlipFromObject(mo, player->mo); + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_ORBINAUT: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + angle_t newangle; + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s3k3a); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->angle = newangle; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = mo->lastlook = moloop+1; + mo->color = player->skincolor; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown + { + player->itemamount--; + K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_JAWZ: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + angle_t newangle; + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s3k3a); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->angle = newangle; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = mo->lastlook = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown + { + player->itemamount--; + if (player->throwdir == 1 || player->throwdir == 0) + K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); + else if (player->throwdir == -1) // Throwing backward gives you a dud that doesn't home in + K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0); + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_MINE: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD); + if (mo) + { + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) + { + player->itemamount--; + K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); + K_PlayAttackTaunt(player->mo); + player->itemflags &= ~IF_ITEMOUT; + K_UpdateHnextList(player, true); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_LANDMINE: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_ThrowLandMine(player); K_PlayAttackTaunt(player->mo); K_BotResetItemConfirm(player, false); } - } - break; - case KITEM_BUBBLESHIELD: - if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) - { - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BUBBLESHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k3f); - if (player->bubblehealth <= 0 || player->bubblehealth > MAXBUBBLEHEALTH) - player->bubblehealth = MAXBUBBLEHEALTH; - } - - if (!HOLDING_ITEM && NO_HYUDORO) - { - if ((buttons & BT_ATTACK && player->itemflags & IF_HOLDREADY) - || (player->bubbleblowup > 0 && player->bubblecool <= bubbletime/2)) // auto + break; + case KITEM_BALLHOG: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { - if (player->bubblecool == 0) - S_StartSound(player->mo, sfx_s3k75); - - if (player->bubblecool < bubbletime && player->bubblehealth > 0) - { - player->bubbleblowup += 3; - player->bubblehealth--; - } - else if (player->bubblecool >= bubbletime) - player->bubbleblowup++; // overcharge bonus - - if (++player->bubblecool >= bubbletime + TICRATE/2) - { - // If you overcharge the Bubble Shield at - // any point, it pops and gives you a boost. - K_PopPlayerShield(player); - - if (onground) - { - // If you're on the ground, you're going to - // immediately feel the effects of this boost. - // Play a sound to let everyone know! - S_StartSoundAtVolume(player->mo, sfx_cdfm57, 170); - K_PlayBoostTaunt(player->mo); - } - - // experiment: don't boost, just give invulnerability - if (cv_kartbubble_boost_allow.value) - { - player->bubbleboost = 7 * sneakertime / 10; - } - player->flashing = 4*TICRATE/7; - } + player->itemamount--; + K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); } - else + break; + case KITEM_SPB: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { - if (player->bubblecool > bubbletime) - player->bubblecool = bubbletime; - - if (player->bubbleblowup > 0) + player->itemamount--; + K_ThrowKartItem(player, true, MT_SPB, 1, 0); + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_GROW: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + if (K_IsLegacyShrunk(player)) { - player->bubbleblowup--; - if (player->bubbleblowup == 0) - K_BotResetItemConfirm(player, false); - if (player->bubblecool < bubbletime) - player->bubblecool++; - } - - if (player->bubbleblowup == 0 && player->bubblecool > 0) - { - player->bubblecool--; - - if (player->bubblecool == 0 && player->bubblehealth <= 0) - K_PopPlayerShield(player); - } - - if (buttons & BT_ATTACK || player->bubblecool > 0) - { - player->itemflags &= ~IF_HOLDREADY; + // If you're shrunk, then "grow" will just make you normal again... + K_RemoveGrowShrink(player); } else { - player->itemflags |= IF_HOLDREADY; + // ...in Legacy mode. + // Alt. Shrink's a powerup, so Grow overrides! + if (K_IsAltShrunk(player)) + { + player->growshrinktimer = 0; // Paranoia + } + + K_PlayPowerGloatSound(player->mo); + + K_DoGrowShrink(player, false); + + S_StartSound(player->mo, sfx_kc5a); + } + + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SHRINK: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + if (K_IsKartItemAlternate(KITEM_SHRINK)) + { + K_DoGrowShrink(player, true); + K_AltShrinkIFrames(player); + + S_StartSound(player->mo, sfx_kc46); + } + else + { + K_DoShrink(player); + } + + player->itemamount--; + K_PlayPowerGloatSound(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_THUNDERSHIELD: + if (K_GetShieldFromPlayer(player) != KSHIELD_THUNDER) + { + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k41); + } + + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + K_DoThunderShield(player); + if (player->itemamount > 0) + { + // Why is this a conditional? + // Thunder shield: the only item that allows you to + // activate a mine while you're out of its radius, + // the SAME tic it sets your itemamount to 0 + // ...:dumbestass: + player->itemamount--; + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); } } - } - break; - case KITEM_FLAMESHIELD: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && K_GetShieldFromPlayer(player) != KSHIELD_FLAME) - { - player->itemamount--; - player->flametimer = (itemtime*3); - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FLAMESHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k3e); - } - break; - case KITEM_HYUDORO: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_DoHyudoroSteal(player); // yes. yes they do. - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_POGOSPRING: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO && player->pogospring == 0) - { - player->itemamount--; - K_PlayBoostTaunt(player->mo); - K_DoPogoSpring(player->mo, 32<pogospring = 1; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SUPERRING: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_AwardScaledPlayerRings(player, ASR_SUPERRING); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_KITCHENSINK: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD); - if (mo) + break; + case KITEM_BUBBLESHIELD: + if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) { - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BUBBLESHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k3f); + if (player->bubblehealth <= 0 || player->bubblehealth > MAXBUBBLEHEALTH) + player->bubblehealth = MAXBUBBLEHEALTH; } - K_BotResetItemConfirm(player, false); - // Woah this could be big, lets get the inside scoop... - K_DirectorForceSwitch(player - players, 1); - } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown - { - player->itemamount--; - K_ThrowKartItem(player, false, MT_SINK, 1, 2); - K_PlayAttackTaunt(player->mo); - player->itemflags &= ~IF_ITEMOUT; - K_UpdateHnextList(player, true); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SAD: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO - && !player->sadtimer) - { - player->sadtimer = stealtime; - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - default: - break; + if (!HOLDING_ITEM && NO_HYUDORO) + { + if ((buttons & BT_ATTACK && player->itemflags & IF_HOLDREADY) + || (player->bubbleblowup > 0 && player->bubblecool <= bubbletime/2)) // auto + { + if (player->bubblecool == 0) + S_StartSound(player->mo, sfx_s3k75); + + if (player->bubblecool < bubbletime && player->bubblehealth > 0) + { + player->bubbleblowup += 3; + player->bubblehealth--; + } + else if (player->bubblecool >= bubbletime) + player->bubbleblowup++; // overcharge bonus + + if (++player->bubblecool >= bubbletime + TICRATE/2) + { + // If you overcharge the Bubble Shield at + // any point, it pops and gives you a boost. + K_PopPlayerShield(player); + + if (onground) + { + // If you're on the ground, you're going to + // immediately feel the effects of this boost. + // Play a sound to let everyone know! + S_StartSoundAtVolume(player->mo, sfx_cdfm57, 170); + K_PlayBoostTaunt(player->mo); + } + + // experiment: don't boost, just give invulnerability + if (cv_kartbubble_boost_allow.value) + { + player->bubbleboost = 7 * sneakertime / 10; + } + player->flashing = 4*TICRATE/7; + } + } + else + { + if (player->bubblecool > bubbletime) + player->bubblecool = bubbletime; + + if (player->bubbleblowup > 0) + { + player->bubbleblowup--; + if (player->bubbleblowup == 0) + K_BotResetItemConfirm(player, false); + if (player->bubblecool < bubbletime) + player->bubblecool++; + } + + if (player->bubbleblowup == 0 && player->bubblecool > 0) + { + player->bubblecool--; + + if (player->bubblecool == 0 && player->bubblehealth <= 0) + K_PopPlayerShield(player); + } + + if (buttons & BT_ATTACK || player->bubblecool > 0) + { + player->itemflags &= ~IF_HOLDREADY; + } + else + { + player->itemflags |= IF_HOLDREADY; + } + } + } + break; + case KITEM_FLAMESHIELD: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && K_GetShieldFromPlayer(player) != KSHIELD_FLAME) + { + player->itemamount--; + player->flametimer = (itemtime*3); + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FLAMESHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k3e); + } + break; + case KITEM_HYUDORO: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_DoHyudoroSteal(player); // yes. yes they do. + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_POGOSPRING: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO && player->pogospring == 0) + { + player->itemamount--; + K_PlayBoostTaunt(player->mo); + K_DoPogoSpring(player->mo, 32<pogospring = 1; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SUPERRING: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_AwardScaledPlayerRings(player, ASR_SUPERRING); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_KITCHENSINK: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD); + if (mo) + { + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + + // Woah this could be big, lets get the inside scoop... + K_DirectorForceSwitch(player - players, 1); + } + else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown + { + player->itemamount--; + K_ThrowKartItem(player, false, MT_SINK, 1, 2); + K_PlayAttackTaunt(player->mo); + player->itemflags &= ~IF_ITEMOUT; + K_UpdateHnextList(player, true); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SAD: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO + && !player->sadtimer) + { + player->sadtimer = stealtime; + player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + default: + break; + } } } } diff --git a/src/lua_hook.h b/src/lua_hook.h index 8e715193a..674d752fb 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -40,6 +40,7 @@ automatically. X (MobjFuse),/* when mobj->fuse runs out */\ X (MobjThinker),/* P_MobjThinker, P_SceneryThinker */\ X (BossThinker),/* P_GenericBossThinker */\ + X (MobjScaleChange),/*SRB2KART*/\ X (ShouldDamage),/* P_DamageMobj (Should mobj take damage?) */\ X (ShouldSpin),/* P_DamageMobj (Should mobj take spinout damage?) */\ X (ShouldExplode),/* P_DamageMobj (Should mobj take explosion damage?) */\ @@ -86,6 +87,9 @@ automatically. X (BotJoin),\ X (GPRankPoints),/* K_CalculateGPRankPoints */\ X (AddonLoaded),\ + X (PlayerItem),/*SRB2KART*/\ + X (KartHyudoro),/*SRB2KART*/\ + X (KartSneaker),/*SRB2KART*/\ #define STRING_HOOK_LIST(X) \ X (SpecialExecute),\ @@ -171,6 +175,12 @@ int LUA_HookGPRankPoints(UINT8 position, UINT8 numplayers, INT16 *points); int LUA_HookShouldJingleContinue(player_t *, const char *musname); int LUA_HookMusicChange(const char *oldname, struct MusicChange *); +// Item shit +boolean LUA_HookPlayerItem(player_t *player, SINT8 itemType, boolean wasHoldingItem, boolean *force); +boolean LUA_HookKartHyudoro(player_t *player, INT32 *target, boolean sink); // SRB2Kart: Hook for K_DoHyudoroSteal and overriding its results. +boolean LUA_HookMobjScaleChange(mobj_t *target, fixed_t newscale, fixed_t oldscale); // SRB2Kart: Hook for P_SetScale. +boolean LUA_HookKartSneaker(player_t *player, int type); // SRB2Kart: Hook for K_DoSneaker. + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index c1d059fb1..c7b4f7e73 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -1260,3 +1260,115 @@ int LUA_HookMusicChange(const char *oldname, struct MusicChange *param) return hook.status; } + +// Allows to both override default behavior and force action. I don't like it but thats how it works +// in CEP and neptune. Fuckal whyyyyyyyyy +typedef struct { + boolean override; + boolean force; +} TrueForce_State; + +static void res_trueforce(Hook_State *hook) +{ + TrueForce_State *state = (TrueForce_State*)hook->userdata; + + if (lua_isboolean(gL, -2)) + state->override = lua_toboolean(gL, -2); + + if (lua_isboolean(gL, -1)) + state->force = lua_toboolean(gL, -1); +}; + +boolean LUA_HookPlayerItem(player_t *player, SINT8 itemType, boolean wasHoldingItem, boolean *force) +{ + Hook_State hook; + TrueForce_State state = {0}; + + if (prepare_hook(&hook, 0, HOOK(PlayerItem))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + lua_pushinteger(gL, itemType); + lua_pushboolean(gL, wasHoldingItem); + + hook.userdata = &state; + + call_hooks(&hook, 2, res_trueforce); + } + + *force = state.force; + return state.override; +} + +typedef struct { + INT32 *target; + boolean force_sink; +} KartHyudoro_State; + +static void res_karthyudoro(Hook_State *hook) +{ + KartHyudoro_State *state = (KartHyudoro_State*)hook->userdata; + + if (lua_isnumber(gL, -2)) + *state->target = lua_tonumber(gL, -2); + else if (lua_isuserdata(gL, -2)) + { + player_t *player = *(player_t**)luaL_checkudata(gL, -2, META_PLAYER); + *state->target = player - players; + } + + if (lua_isboolean(gL, -1)) + state->force_sink = lua_toboolean(gL, -1); +} + +boolean LUA_HookKartHyudoro(player_t *player, INT32 *target, boolean sink) +{ + Hook_State hook; + KartHyudoro_State state = {0}; + state.target = target; + + if (prepare_hook(&hook, 0, HOOK(KartHyudoro))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + if (*target >= 0) + LUA_PushUserdata(gL, &players[*target], META_PLAYER); + else + lua_pushnil(gL); + lua_pushboolean(gL, sink); + + hook.userdata = &state; + + call_hooks(&hook, 2, res_karthyudoro); + } + + return state.force_sink; + +} + +boolean LUA_HookMobjScaleChange(mobj_t *target, fixed_t newscale, fixed_t oldscale) +{ + Hook_State hook; + if (prepare_mobj_hook(&hook, false, MOBJ_HOOK(MobjScaleChange), target->type)) + { + LUA_PushUserdata(gL, target, META_MOBJ); + lua_pushfixed(gL, newscale); + lua_pushfixed(gL, oldscale); + + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +boolean LUA_HookKartSneaker(player_t *player, int type) +{ + Hook_State hook; + if (prepare_hook(&hook, false, HOOK(KartSneaker))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + // Type of sneaker: 0 - perfect/panel, 1 - regular sneaker, 2 - rocket sneaker. + lua_pushinteger(gL, type); + + call_hooks(&hook, 1, res_true); + } + + return hook.status; +} diff --git a/src/p_mobj.c b/src/p_mobj.c index fe4ffc362..e758a3f98 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6194,6 +6194,9 @@ void P_SetScale2(mobj_t *mobj, fixed_t newscale, boolean compat) oldscale = mobj->scale; //keep for adjusting stuff below + if (LUA_HookMobjScaleChange(mobj, newscale, oldscale) || P_MobjWasRemoved(mobj)) + return; + mobj->scale = newscale; if (compat) From 0d3e7bc033da683cf5057ac03180da5afa017da3 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Fri, 7 Nov 2025 22:52:39 -0500 Subject: [PATCH 24/76] Unsquish visually when unsetting squish --- src/lua_playerlib.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index bda1fee17..bd35f12e6 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -1667,8 +1667,16 @@ static int player_set(lua_State *L) plr->growcancel = luaL_checkinteger(L, 3); break; case player_squishedtimer: - plr->squishedtimer = luaL_checkinteger(L, 3); + { + // Unsquish for the ease of Lua programmers + INT16 squishtimer = (INT16)luaL_checkinteger(L, 3); + + if (squishtimer == 0) + plr->mo->spriteyscale = FRACUNIT; + + plr->squishedtimer = squishtimer; break; + } case player_rocketsneakertimer: plr->rocketsneakertimer = luaL_checkinteger(L, 3); break; @@ -2455,8 +2463,14 @@ static int kartstuff_set(lua_State *L) plr->growshrinktimer = CLAMP(i, INT16_MIN, INT16_MAX); break; case k_squishedtimer: + { + // Unsquish for the ease of Lua programmers + if (i == 0) + plr->mo->spriteyscale = FRACUNIT; + plr->squishedtimer = CLAMP(i, INT16_MIN, INT16_MAX); break; + } case k_rocketsneakertimer: plr->rocketsneakertimer = CLAMP(i, 0, UINT16_MAX); break; From c16bb035ed7d3c7d63c55a620d0f72dfc64a8aa1 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Fri, 7 Nov 2025 22:54:27 -0500 Subject: [PATCH 25/76] prep potential hide from roulette flag --- src/k_items.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_items.h b/src/k_items.h index cfb884d6a..9170dd452 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -57,6 +57,7 @@ typedef enum KIF_ANIMATED = 1<<0, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT) KIF_DARKBG = 1<<1, // use dark item roulette BG KIF_COLPATCH2PLAYER = 1<<2, // colourize patch to player + KIF_HIDEFROMROULETTE = 1<<3, // TODO: don't show this item in the roulette (inversion of xItem's showInRoulette item flag) } ATTRPACK kartitemflags_e; // flags relevant to item rolls and usage From 79aaff6265cd1744226fee46404a4f0af3e81d63 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Sat, 8 Nov 2025 02:23:09 -0500 Subject: [PATCH 26/76] Fix forceme oversight --- src/k_items.c | 78 ++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/src/k_items.c b/src/k_items.c index 503b1d783..72e77ef0f 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -1334,7 +1334,47 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } // SPECIAL CASE No. 6: + // In battle, an SPB is forced onto players to target the "most wanted" player + if (K_BattleForceSPB(player) + && spbplace == -1 && !indirectitemcooldown && !dontforcespb + && K_ItemResultEnabled(K_GetKartResult("spb"))) + { + K_AwardPlayerItem(player, K_GetKartResult("spb")); + player->itemblink = TICRATE; + player->itemblinkmode = KITEMBLINKMODE_KARMA; + player->itemroulette = KROULETTE_DISABLED; + player->roulettetype = KROULETTETYPE_NORMAL; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_itrolk); + return; + } + + // NOW that we're done with most of those specialized cases, we can move onto the REAL item roulette tables. + // Initializes existing spawnchance values + memset(spawnchance, 0, sizeof(spawnchance)); + + // Split into another function for a debug function below + useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); + + kartroulette_t roulette = { + .pdis = pdis, + .playerpos = player->position, + .pos = useodds, + .ourDist = player->distancetofinish, + .clusterDist = player->distancefromcluster, + .mashed = mashed, + .spbrush = spbrush, + .bot = player->bot, + .rival = player->bot && player->botvars.rival, + .inBottom = K_IsPlayerLosing(player), + }; + + K_KartGetItemOdds(&roulette, spawnchance); + + // SPECIAL CASE No. 7: // Item forcing; the item with the highest "forceme" priority is the one given. + // Have to do it AFTER the KartGetItemOdds call, + // or forceme persists into *other* players' rolls! { kartresult_t *forceresult; UINT8 bestforce = 0; @@ -1361,44 +1401,6 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } } - // SPECIAL CASE No. 7: - // In battle, an SPB is forced onto players to target the "most wanted" player - if (K_BattleForceSPB(player) - && spbplace == -1 && !indirectitemcooldown && !dontforcespb - && K_ItemResultEnabled(K_GetKartResult("spb"))) - { - K_AwardPlayerItem(player, K_GetKartResult("spb")); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_KARMA; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolk); - return; - } - - // NOW that we're done with all of those specialized cases, we can move onto the REAL item roulette tables. - // Initializes existing spawnchance values - memset(spawnchance, 0, sizeof(spawnchance)); - - // Split into another function for a debug function below - useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); - - kartroulette_t roulette = { - .pdis = pdis, - .playerpos = player->position, - .pos = useodds, - .ourDist = player->distancetofinish, - .clusterDist = player->distancefromcluster, - .mashed = mashed, - .spbrush = spbrush, - .bot = player->bot, - .rival = player->bot && player->botvars.rival, - .inBottom = K_IsPlayerLosing(player), - }; - - K_KartGetItemOdds(&roulette, spawnchance); - for (i = 0; i < numkartresults; i++) { totalspawnchance += spawnchance[i]; From 187826bb01fb5a464033b1dd6f7f1ebd06d8e25f Mon Sep 17 00:00:00 2001 From: Anonimus Date: Sat, 8 Nov 2025 02:44:44 -0500 Subject: [PATCH 27/76] Fix bgone purgatory --- src/k_items.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_items.c b/src/k_items.c index 72e77ef0f..fae1ac4d7 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -285,7 +285,7 @@ static void K_AwardPlayerItem(player_t *player, kartresult_t *result) } else if (result->basebgone > 0) // Item cooldowns { - result->bgone = result->basebgone * TICRATE; + result->bgone = result->basebgone; } player->itemtype = result->type; @@ -443,7 +443,7 @@ void K_KartHandleUniqueCooldown(void) // If this item has a cooldown, force-apply the cooldown // preeemptively for the entire time the item is being held // by a player. - kartresults[i].bgone = kartresults[i].basebgone * TICRATE; + kartresults[i].bgone = kartresults[i].basebgone; } } } From 6f8b4697301ac5cfc1ae1e9ef8e9203a22255d6b Mon Sep 17 00:00:00 2001 From: Anonimus Date: Sat, 8 Nov 2025 02:44:57 -0500 Subject: [PATCH 28/76] Netsynch item force priorities --- src/p_saveg.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/p_saveg.c b/src/p_saveg.c index 9ed9194e5..1eaa68c7b 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4189,6 +4189,7 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) // hyubgone //SYNC(kartresults[i].basebgone); SYNC(kartresults[i].bgone); + SYNC(kartresults[i].forceme); } SYNC(mapreset); From 8e1cd1e6fd0caeb473d86849519c401f6b4c94e6 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 8 Nov 2025 14:47:52 +0100 Subject: [PATCH 29/76] Simplify bubble debris code and fix the earrape Turns out MF_BOUNCE mobjs play their activesound when they touch a wall! --- src/d_main.cpp | 2 +- src/info/mobjs.h | 2 +- src/k_kart.c | 7 +++--- src/k_kart.h | 2 +- src/p_mobj.c | 62 ++++++++++++++---------------------------------- 5 files changed, 25 insertions(+), 50 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 3218325af..623bfde7a 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -94,7 +94,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x06a13ab736e848f8 +#define ASSET_HASH_MAIN_PK3 0xa489b285be78e8d4 #define ASSET_HASH_MAPPATCH_PK3 0x7d1f6b96dd119296 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE diff --git a/src/info/mobjs.h b/src/info/mobjs.h index c44760310..207456333 100644 --- a/src/info/mobjs.h +++ b/src/info/mobjs.h @@ -618,7 +618,7 @@ _(THUNDERSHIELD) // Shields _(BUBBLESHIELD) _(FLAMESHIELD) _(BUBBLESHIELDTRAP) -_(BUBBLESHLD_DEBRIS) +_(BUBBLESHIELD_DEBRIS) _(SINK) // Kitchen Sink Stuff _(SINK_SHIELD) diff --git a/src/k_kart.c b/src/k_kart.c index 549182b07..2f6ebe903 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5381,7 +5381,7 @@ static void K_BreakBubbleShield(player_t* player) FixedMul(FixedMul(mul_vec.x, shieldrad), FSIN(FixedAngle(randang))), player->shieldtracer->z + ((player->shieldtracer->height + scalediff) / 4) + FixedMul(mul_vec.y * flip, shieldrad), - MT_BUBBLESHLD_DEBRIS); + MT_BUBBLESHIELD_DEBRIS); if (!P_MobjWasRemoved(shard)) { @@ -5400,7 +5400,8 @@ static void K_BreakBubbleShield(player_t* player) shard->momy = mom.y + player->mo->momy; shard->momz = mom.z + player->mo->momz; - shard->extravalue1 = i % 5; // 20% chance to play the shard sound + shard->fuse = 3*TICRATE; + shard->extravalue1 = i % 6; // 16% of shards play the death sound } } @@ -5451,7 +5452,7 @@ void K_PopPlayerShield(player_t *player) } // Returns true is the bubble is actively in defense mode (inflating or inflated) -boolean K_IsBubbleDefending(player_t *player) +boolean K_IsBubbleDefending(const player_t *player) { if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) return false; return (player->bubbleblowup); diff --git a/src/k_kart.h b/src/k_kart.h index 1d4f09766..979263fe9 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -264,7 +264,7 @@ void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source); void K_UpdateHnextList(player_t *player, boolean clean); void K_DropHnextList(player_t *player); void K_PopPlayerShield(player_t *player); -boolean K_IsBubbleDefending(player_t *player); +boolean K_IsBubbleDefending(const player_t *player); void K_RemoveBubbleHealth(player_t *player, INT16 sub); boolean K_CheckBubbleChip(const mobj_t *mobj); void K_RepairOrbitChain(mobj_t *orbit); diff --git a/src/p_mobj.c b/src/p_mobj.c index e758a3f98..d97df99dd 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7414,6 +7414,19 @@ static void P_MobjSceneryThink(mobj_t *mobj) return; } break; + case MT_BUBBLESHIELD_DEBRIS: + mobj->rollangle += ANG1 * 4; + + if (P_IsObjectOnGround(mobj) && mobj->health > 0) + { + // "Despawn" and play the glass landing sound. + if (mobj->extravalue1 == 0) + S_StartSoundAtVolume(mobj, mobj->info->deathsound, 192); + P_KillMobj(mobj, NULL, NULL, DMG_NORMAL); + mobj->fuse = TICRATE; + } + + goto dofuse; case MT_SMOLDERING: if (leveltime % 2 == 0) { @@ -7853,6 +7866,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) } /* FALLTHRU */ default: + dofuse: if (mobj->fuse) { // Scenery object fuse! Very basic! mobj->fuse--; @@ -9227,18 +9241,17 @@ static boolean P_MobjRegularThink(mobj_t *mobj) for (i = 0; i < 2; i++) { angle_t a = mobj->angle + ((i & 1) ? ANGLE_180 : 0); - fixed_t ws = player->mo->scale/4 + scale/4; mobj_t *wave; wave = P_SpawnMobj( - (player->mo->x - player->mo->momx) + P_ReturnThrustX(NULL, a, mobj->radius - (21*ws)), - (player->mo->y - player->mo->momy) + P_ReturnThrustY(NULL, a, mobj->radius - (21*ws)), + (player->mo->x - player->mo->momx) + P_ReturnThrustX(NULL, a, mobj->radius - (5*scale)), + (player->mo->y - player->mo->momy) + P_ReturnThrustY(NULL, a, mobj->radius - (5*scale)), (player->mo->z - player->mo->momz), MT_THOK); wave->colorized = true; wave->color = SKINCOLOR_BLUE; wave->flags &= ~(MF_NOCLIPHEIGHT|MF_NOGRAVITY); - P_SetScale(wave, (wave->destscale = ws)); + P_SetScale(wave, (wave->destscale = scale/3)); P_SetMobjState(wave, S_SPLISH1); @@ -9309,45 +9322,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->flags |= MF_NOCLIP|MF_NOCLIPTHING; break; } - case MT_BUBBLESHLD_DEBRIS: - { - statenum_t curstate; - curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states))); - - if (curstate != S_INVISIBLE) - { - mobj->rollangle += ANG1 * 4; - } - else - { - mobj->rollangle = 0; - } - - if ((P_IsObjectOnGround(mobj)) && (!mobj->threshold)) - { - mobj->threshold = (TICRATE / 2); - - if (curstate != S_INVISIBLE) - { - // "Despawn" and play the glass landing sound. - if (mobj->extravalue1 == 0) - S_StartSoundAtVolume(mobj, mobj->info->activesound, 128); - P_SetMobjState(mobj, S_INVISIBLE); - } - } - - if (mobj->threshold) - { - mobj->threshold--; - - if (!mobj->threshold) - { - P_RemoveMobj(mobj); - } - } - - break; - } case MT_FLAMESHIELD: { statenum_t curstate; @@ -11370,7 +11344,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) P_SetTarget(&spawn->target, mobj); break; } - case MT_BUBBLESHLD_DEBRIS: + case MT_BUBBLESHIELD_DEBRIS: { statenum_t shardset[] = {S_BUBBLEDEBRIS_1, S_BUBBLEDEBRIS_2, S_BUBBLEDEBRIS_3, S_BUBBLEDEBRIS_4, S_BUBBLEDEBRIS_5}; P_SetMobjState(mobj, shardset[P_RandomRange(0, 4)]); From 7bd225da17bc1b9acd9d546a72e0814346194247 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 8 Nov 2025 16:24:09 +0100 Subject: [PATCH 30/76] Move forceme into kartroulette_t --- src/k_hud.c | 4 ++-- src/k_items.c | 30 +++++++++++++++--------------- src/k_items.h | 10 ++++++---- src/p_saveg.c | 1 - 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 4675392d4..114bef827 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5622,13 +5622,13 @@ static void K_drawDistributionDebugger(void) { kartresult_t *result = &kartresults[i]; - if (itemodds[i] <= 0 && !result->forceme) // At the very least display forced items; that info's also important. + if (itemodds[i] <= 0 && roulette.forceme[i] == 0) // At the very least display forced items; that info's also important. continue; V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(result->type, true, 0)); if (result->isalt) V_DrawScaledPatch(x+2, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, W_CachePatchName("K_ALTITS", PU_CACHE)); - V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, result->forceme ? va("\x85" "FRC(%d)" "\x80 ", result->forceme) : va("%d", itemodds[i])); + V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, roulette.forceme[i] ? va("\x85" "FRC(%d)" "\x80 ", roulette.forceme[i]) : va("%d", itemodds[i])); if (result->amount > 1) V_DrawString(x+24, y+31, V_SPLITSCREEN|V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", result->amount)); diff --git a/src/k_items.c b/src/k_items.c index fae1ac4d7..f442bce9d 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -450,7 +450,7 @@ void K_KartHandleUniqueCooldown(void) } } -static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result) +static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 *forceme) { INT32 newodds; INT32 i; @@ -500,9 +500,6 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result) if (gametyperules & GTR_BATTLEODDS) oddsmul = BATTLEODDSMUL; - // Reset forceme - result->forceme = 0; - // TODO: braaap (make a separate table for the current level!) newodds = result->odds[oddstable][roulette->pos]; @@ -584,7 +581,7 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result) useoddsfunc_f *uoddsfunc = result->unique_odds[oddstable]; //CONS_Printf("Running..."); - newodds = uoddsfunc(newodds, roulette, result); + newodds = uoddsfunc(newodds, roulette, result, forceme); //CONS_Printf("OK!\n"); } else @@ -732,9 +729,12 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result) void K_KartGetItemOdds(kartroulette_t *roulette, INT32 outodds[static MAXKARTRESULTS]) { + // Reset forceme + memset(roulette->forceme, 0, sizeof(roulette->forceme)); + for (UINT8 i = 0; i < numkartresults; i++) { - outodds[i] = GetItemOdds(roulette, &kartresults[i]); + outodds[i] = GetItemOdds(roulette, &kartresults[i], &roulette->forceme[i]); } }; @@ -1373,17 +1373,15 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // SPECIAL CASE No. 7: // Item forcing; the item with the highest "forceme" priority is the one given. - // Have to do it AFTER the KartGetItemOdds call, - // or forceme persists into *other* players' rolls! { kartresult_t *forceresult; UINT8 bestforce = 0; for (i = 0; i < numkartresults; i++) { - if ((kartresults[i].forceme) && (kartresults[i].forceme > bestforce)) + if (roulette.forceme[i] && roulette.forceme[i] > bestforce) { - bestforce = kartresults[i].forceme; + bestforce = roulette.forceme[i]; forceresult = &kartresults[i]; } } @@ -1433,14 +1431,15 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // Unique odds functions, for REAL this time // Alt. Invin. odds -INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result) +INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme) { + (void)result; odds = K_KartGetInvincibilityOdds(roulette->clusterDist); // Special case: if you're SERIOUSLY far behind before the cooldown finishes, ignore it and start forcing, if (odds >= INVFORCEODDS) { - result->forceme = 3; // Take priority over SPBs + *forceme = 3; // Take priority over SPBs } else if (K_InStartCooldown()) { @@ -1453,8 +1452,9 @@ INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t * } // SPB odds -INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result) +INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme) { + (void)result; if (!K_LegacyOddsMode() && roulette->firstDist < (UINT32)ENDDIST) // No SPB near the end of the race { odds = 0; @@ -1467,13 +1467,13 @@ INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *r // No forced SPB in 1v1s, it has to be randomly rolled if (roulette->pingame <= 2) { - result->forceme = 0; + *forceme = 0; } else if (K_RaceForceSPB(roulette->playerpos, roulette->pdis) && spbplace == -1 && !indirectitemcooldown) { // Force SPB onto 2nd if they get too far behind. - result->forceme = 2; + *forceme = 2; } return odds; diff --git a/src/k_items.h b/src/k_items.h index 9170dd452..24ab0dc5a 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -74,7 +74,7 @@ typedef enum } ATTRPACK kartresultflags_e; // Unique useodds function -typedef INT32 (useoddsfunc_f)(INT32 odds, const kartroulette_t *roulette, kartresult_t *result); +typedef INT32 (useoddsfunc_f)(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme); #define MAXITEMPATCHES 10 @@ -109,7 +109,6 @@ struct kartresult_t char *displayname; boolean isalt; // is alt (i salt) kartresultflags_e flags; - UINT8 forceme; // This item is being forced into a player's item slot for one reason or another. Higher value = higher priority. UINT8 odds[MAXODDSTABLES][MAXODDS]; // Functions that tell the game to use unique means of rolling these items. @@ -133,6 +132,9 @@ struct kartroulette_t boolean rival; boolean inBottom; + // output: which results are being forced into a player's item slot for one reason or another. Higher value = higher priority. + UINT8 forceme[MAXKARTRESULTS]; + // extras for useodds functions UINT8 pingame; UINT8 pexiting; @@ -176,8 +178,8 @@ boolean K_IsShrunkMode(const player_t *player, boolean legacy); void K_AltShrinkIFrames(player_t *player); void K_AltShrinkPityIncrease(player_t *player); -INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result); -INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, kartresult_t *result); +useoddsfunc_f KO_AltInvinOdds; +useoddsfunc_f KO_SPBRaceOdds; #ifdef __cplusplus } // extern "C" diff --git a/src/p_saveg.c b/src/p_saveg.c index 1eaa68c7b..9ed9194e5 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4189,7 +4189,6 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) // hyubgone //SYNC(kartresults[i].basebgone); SYNC(kartresults[i].bgone); - SYNC(kartresults[i].forceme); } SYNC(mapreset); From ed861e5c1b853a87cf5cabea8e3c32c19d22e1b8 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 8 Nov 2025 16:30:40 +0100 Subject: [PATCH 31/76] Untested Changes Theory --- src/deh_lua.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deh_lua.c b/src/deh_lua.c index ad40a5c2e..3af0b3e89 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -439,7 +439,7 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) } return luaL_error(L, "karthud '%s' could not be found.\n", word); } - else if (mathlib && fastncmp("KITEM_",word,6)) { + else if (fastncmp("KITEM_",word,6)) { p = word+6; if (fastcmp(p, "LIGHTNINGSHIELD")) { From 66c894558fcd0db8e835710a1de4ed00590582fc Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 8 Nov 2025 17:23:19 +0100 Subject: [PATCH 32/76] Move held item thinker code into k_items.c --- src/k_items.c | 863 +++++++++++++++++++++++++++++++++++++++++++++++++- src/k_items.h | 13 +- src/k_kart.c | 853 +------------------------------------------------ src/k_kart.h | 1 + 4 files changed, 871 insertions(+), 859 deletions(-) diff --git a/src/k_items.c b/src/k_items.c index f442bce9d..6549d65ae 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -28,16 +28,18 @@ #include "s_sound.h" #include "typedef.h" #include "deh_tables.h" +#include "lua_hook.h" +#include "z_zone.h" #include "k_battle.h" #include "k_boss.h" #include "k_bot.h" #include "k_kart.h" #include "k_waypoint.h" - +#include "k_director.h" #include "k_cluster.hpp" #include "k_items.h" -#include "z_zone.h" +#include "k_collide.h" kartitem_t kartitems[MAXKARTITEMS] = {0}; kartresult_t kartresults[MAXKARTRESULTS] = {0}; @@ -1479,6 +1481,863 @@ INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, const kartresul return odds; } +static void K_DoGrowShrink(player_t *player, boolean shrinking) +{ + player->mo->scalespeed = mapobjectscale/TICRATE; + player->mo->destscale = FixedMul(mapobjectscale, (shrinking) ? SHRINK_SCALE : GROW_SCALE); + + if (K_PlayerShrinkCheat(player) == true) + { + player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); + } + + if ((shrinking) && (ALTSHRINKTIME > 0)) + { + player->growshrinktimer = ALTSHRINKTIME * -TICRATE; + } + else + { + player->growshrinktimer = (gametyperules & GTR_CLOSERPLAYERS ? 8 : 12) * ((shrinking) ? -TICRATE : TICRATE); + } + + if (player->invincibilitytimer > 0) + { + ; // invincibility has priority in P_RestoreMusic, no point in starting here + } + else if (P_IsLocalPlayer(player) == true) + { + S_ChangeMusicSpecial((shrinking) ? "kshrnk" : "kgrow"); + } + else //used to be "if (P_IsDisplayPlayer(player) == false)" + { + if (!shrinking) + { + S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); + } + } + + P_RestoreMusic(player); +} + +static void K_DoShrink(player_t *user) +{ + INT32 i; + mobj_t *mobj, *next; + + S_StartSound(user->mo, sfx_kc46); // Sound the BANG! + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; + if (&players[i] == user) + continue; + if (players[i].position < user->position) + { + //P_FlashPal(&players[i], PAL_NUKE, 10); + + // Grow should get taken away. + if (players[i].growshrinktimer > 0) + K_RemoveGrowShrink(&players[i]); + else + { + // Start shrinking! + K_DropItems(&players[i]); + players[i].growshrinktimer = -(15*TICRATE); + + if (players[i].mo && !P_MobjWasRemoved(players[i].mo)) + { + players[i].mo->scalespeed = mapobjectscale/TICRATE; + players[i].mo->destscale = FixedMul(mapobjectscale, SHRINK_SCALE); + + if (K_PlayerShrinkCheat(&players[i]) == true) + { + players[i].mo->destscale = FixedMul(players[i].mo->destscale, SHRINK_SCALE); + } + + S_StartSound(players[i].mo, sfx_kc59); + } + } + } + } + + // kill everything in the kitem list while we're at it: + for (mobj = kitemcap; mobj; mobj = next) + { + next = mobj->itnext; + + // check if the item is being held by a player behind us before removing it. + // check if the item is a "shield" first, bc i'm p sure thrown items keep the player that threw em as target anyway + + if (mobj->type == MT_BANANA_SHIELD || mobj->type == MT_JAWZ_SHIELD || + mobj->type == MT_SSMINE_SHIELD || mobj->type == MT_EGGMANITEM_SHIELD || + mobj->type == MT_SINK_SHIELD || mobj->type == MT_ORBINAUT_SHIELD) + { + if (mobj->target && mobj->target->player) + { + if (mobj->target->player->position > user->position) + continue; // this guy's behind us, don't take his stuff away! + } + } + + mobj->destscale = 0; + mobj->flags &= ~(MF_SOLID|MF_SHOOTABLE|MF_SPECIAL); + mobj->flags |= MF_NOCLIPTHING; // Just for safety + + if (mobj->type == MT_SPB) + spbplace = -1; + } +} + +static void K_DoHyudoroSteal(player_t *player) +{ + INT32 i, numplayers = 0; + INT32 playerswappable[MAXPLAYERS]; + INT32 stealplayer = -1; // The player that's getting stolen from + INT32 prandom = 0; + boolean sink = P_RandomChance(FRACUNIT/64); + boolean force_sink = false; + INT32 hyu = hyudorotime; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && players[i].mo && players[i].mo->health > 0 && players[i].playerstate == PST_LIVE + && player != &players[i] && !players[i].exiting && !players[i].spectator // Player in-game + + // Can steal from this player + && (gametype == GT_RACE //&& players[i].position < player->position) + || ((gametyperules & GTR_BUMPERS) && players[i].bumper > 0)) + + // Has an item + && (players[i].itemtype + && players[i].itemamount + && !(players[i].itemflags & IF_ITEMOUT) + && !players[i].itemblink)) + { + playerswappable[numplayers] = i; + numplayers++; + } + } + + prandom = P_RandomFixed(); + S_StartSound(player->mo, sfx_s3k92); + + if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from + { + stealplayer = playerswappable[numplayers-1]; + } + else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player + { + stealplayer = playerswappable[prandom%(numplayers-1)]; + } + + force_sink = LUA_HookKartHyudoro(player, &stealplayer, sink); + + if (force_sink || (sink && numplayers > 0 && K_ItemResultEnabled(K_GetKartResult("kitchensink")))) // BEHOLD THE KITCHEN SINK + { + player->hyudorotimer = hyu; + player->stealingtimer = stealtime; + + player->itemtype = KITEM_KITCHENSINK; + player->itemamount = 1; + K_UnsetItemOut(player); + + // Woah this could be big, lets get the inside scoop... + K_DirectorForceSwitch(player - players, 1); + return; + } + else if ((gametype == GT_RACE && player->position == 1) || numplayers == 0) // No-one can be stolen from? Oh well... + { + player->hyudorotimer = hyu; + player->stealingtimer = stealtime; + return; + } + + if (stealplayer > -1) // Now here's where we do the stealing, has to be done here because we still know the player we're stealing from + { + player->hyudorotimer = hyu; + player->stealingtimer = stealtime; + players[stealplayer].stolentimer = stealtime; + + player->itemtype = players[stealplayer].itemtype; + player->itemamount = players[stealplayer].itemamount; + K_UnsetItemOut(player); + + players[stealplayer].itemtype = KITEM_NONE; + players[stealplayer].itemamount = 0; + K_UnsetItemOut(&players[stealplayer]); + + if (P_IsDisplayPlayer(&players[stealplayer]) && !r_splitscreen) + S_StartSound(NULL, sfx_s3k92); + } +} + +#define THUNDERRADIUS 320 + +// Rough size of the outer-rim sprites, after scaling. +// (The hitbox is already pretty strict due to only 1 active frame, +// we don't need to have it disjointedly small too...) +#define THUNDERSPRITE 80 + +void K_DoThunderShield(player_t *player) +{ + mobj_t *mo; + int i = 0; + fixed_t sx; + fixed_t sy; + angle_t an; + + S_StartSound(player->mo, sfx_zio3); + K_ThunderShieldAttack(player->mo, (THUNDERRADIUS + THUNDERSPRITE) * FRACUNIT); + + // spawn vertical bolt + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); + P_SetTarget(&mo->target, player->mo); + P_SetMobjState(mo, S_LZIO11); + mo->color = SKINCOLOR_TEAL; + mo->scale = player->mo->scale*3 + (player->mo->scale/2); + + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); + P_SetTarget(&mo->target, player->mo); + P_SetMobjState(mo, S_LZIO21); + mo->color = SKINCOLOR_CYAN; + mo->scale = player->mo->scale*3 + (player->mo->scale/2); + + // spawn horizontal bolts; + for (i=0; i<7; i++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); + mo->angle = P_RandomRange(0, 359)*ANG1; + mo->fuse = P_RandomRange(20, 50); + P_SetTarget(&mo->target, player->mo); + P_SetMobjState(mo, S_KLIT1); + } + + // spawn the radius thing: + an = ANGLE_22h; + for (i=0; i<15; i++) + { + sx = player->mo->x + FixedMul((player->mo->scale*THUNDERRADIUS), FINECOSINE((an*i)>>ANGLETOFINESHIFT)); + sy = player->mo->y + FixedMul((player->mo->scale*THUNDERRADIUS), FINESINE((an*i)>>ANGLETOFINESHIFT)); + mo = P_SpawnMobj(sx, sy, player->mo->z, MT_THOK); + mo->angle = an*i; + mo->extravalue1 = THUNDERRADIUS; // Used to know whether we should teleport by radius or something. + mo->scale = player->mo->scale*3; + P_SetTarget(&mo->target, player->mo); + P_SetMobjState(mo, S_KSPARK1); + } +} + +#undef THUNDERRADIUS +#undef THUNDERSPRITE + +#define MAXSHARDCOUNT 40 +#define SHARDROT (360 * FRACUNIT / 40) +void K_BreakBubbleShield(player_t* player) +{ + if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) + return; + + const INT32 flip = P_MobjFlip(player->mo); + const fixed_t scalediff = player->shieldtracer->scale - mapobjectscale; + const fixed_t shieldrad = player->shieldtracer->radius; + + for (INT32 i = 0; i < MAXSHARDCOUNT; i++) + { + vector2_t mul_vec = { FRACUNIT, 0 }; + + fixed_t randang = SHARDROT * (i + 1); + fixed_t randzang = P_RandomRange(10, 120) * FRACUNIT; + fixed_t move_magnitude = + FixedMul((P_RandomRange(4, 16) * FRACUNIT), mapobjectscale + (scalediff / 4)); + + FV2_Rotate(&mul_vec, randzang); + + // Do shitty initial 3D rotations around the shield's radius. + mobj_t *shard = P_SpawnMobj( + player->shieldtracer->x + + FixedMul(FixedMul(mul_vec.x, shieldrad), FCOS(FixedAngle(randang))), + player->shieldtracer->y + + FixedMul(FixedMul(mul_vec.x, shieldrad), FSIN(FixedAngle(randang))), + player->shieldtracer->z + ((player->shieldtracer->height + scalediff) / 4) + + FixedMul(mul_vec.y * flip, shieldrad), + MT_BUBBLESHIELD_DEBRIS); + + if (!P_MobjWasRemoved(shard)) + { + //CONS_Printf(M_GetText("randzang: %d, randang: %d\n"), randzang / FRACUNIT, randang / FRACUNIT); + + mul_vec.x = FixedMul(move_magnitude, mul_vec.x); + mul_vec.y = FixedMul(move_magnitude, mul_vec.y * flip); + + vector3_t mom = { + .x = FixedMul(mul_vec.x, FCOS(FixedAngle(randang))), + .y = FixedMul(mul_vec.x, FSIN(FixedAngle(randang))), + .z = mul_vec.y, + }; + + shard->momx = mom.x + player->mo->momx; + shard->momy = mom.y + player->mo->momy; + shard->momz = mom.z + player->mo->momz; + + shard->fuse = 3*TICRATE; + shard->extravalue1 = i % 6; // 16% of shards play the death sound + } + } + + S_StartSound(player->mo, sfx_kc41); +} +#undef MAXSHARDCOUNT +#undef SHARDROT + +static void K_ThrowLandMine(player_t *player) +{ + mobj_t *landMine; + mobj_t *throwmo; + + landMine = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_LANDMINE); + K_FlipFromObject(landMine, player->mo); + landMine->threshold = 10; + + if (landMine->info->seesound) + S_StartSound(player->mo, landMine->info->seesound); + + P_SetTarget(&landMine->target, player->mo); + + P_SetScale(landMine, player->mo->scale); + landMine->destscale = player->mo->destscale; + + landMine->angle = player->mo->angle; + + landMine->momz = (30 * mapobjectscale * P_MobjFlip(player->mo)) + player->mo->momz; + landMine->color = player->skincolor; + + throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM); + P_SetTarget(&throwmo->target, player->mo); + // Ditto: + if (player->mo->eflags & MFE_VERTICALFLIP) + { + throwmo->z -= player->mo->height; + throwmo->eflags |= MFE_VERTICALFLIP; + } + + throwmo->movecount = 0; // above player +} + +void K_PlayerItemThink(player_t *player, boolean onground) +{ + const UINT16 buttons = K_GetKartButtons(player); + const boolean ATTACK_IS_DOWN = ((buttons & BT_ATTACK) && !(player->oldcmd.buttons & BT_ATTACK)); + const boolean HOLDING_ITEM = (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)); + const boolean NO_HYUDORO = (player->stealingtimer == 0 && player->stolentimer == 0); + + boolean force = false; + if (LUA_HookPlayerItem(player, player->itemtype, HOLDING_ITEM, &force)) + return; + + switch (player->itemtype) + { + case KITEM_SNEAKER: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO) + { + K_DoSneaker(player, SNEAKERTYPE_SNEAKER); + K_PlayBoostTaunt(player->mo); + player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_ROCKETSNEAKER: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO + && player->rocketsneakertimer == 0) + { + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + K_PlayBoostTaunt(player->mo); + S_StartSound(player->mo, sfx_s3k3a); + + //K_DoSneaker(player, SNEAKERTYPE_ROCKETSNEAKER); + + player->rocketsneakertimer = (itemtime*3); + player->itemamount--; + K_UpdateHnextList(player, true); + + for (moloop = 0; moloop < 2; moloop++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER); + K_MatchGenericExtraFlags(mo, player->mo); + mo->flags |= MF_NOCLIPTHING; + mo->angle = player->mo->angle; + mo->threshold = 10; + mo->movecount = moloop%2; + mo->movedir = mo->lastlook = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_INVINCIBILITY: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage in Legacy, so you're free to waste it if you have multiple + { + K_DoInvincibility(player, K_GetInvincibilityTime(player)); + K_PlayPowerGloatSound(player->mo); + player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_BANANA: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + INT32 moloop; + mobj_t *mo; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown + { + player->itemamount--; + + mobj_t *mo = K_ThrowKartItem(player, false, MT_BANANA, -1, 0); + mo->color = player->skincolor; + + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_EGGMAN: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + player->itemamount--; + player->itemflags |= IF_EGGMANOUT; + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); + if (mo) + { + K_FlipFromObject(mo, player->mo); + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_ORBINAUT: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + angle_t newangle; + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s3k3a); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->angle = newangle; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = mo->lastlook = moloop+1; + mo->color = player->skincolor; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown + { + player->itemamount--; + K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_JAWZ: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + angle_t newangle; + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s3k3a); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->angle = newangle; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = mo->lastlook = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown + { + player->itemamount--; + if (player->throwdir == 1 || player->throwdir == 0) + K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); + else if (player->throwdir == -1) // Throwing backward gives you a dud that doesn't home in + K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0); + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_MINE: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD); + if (mo) + { + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) + { + player->itemamount--; + K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); + K_PlayAttackTaunt(player->mo); + player->itemflags &= ~IF_ITEMOUT; + K_UpdateHnextList(player, true); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_LANDMINE: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_ThrowLandMine(player); + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_BALLHOG: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SPB: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_ThrowKartItem(player, true, MT_SPB, 1, 0); + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_GROW: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + if (K_IsLegacyShrunk(player)) + { + // If you're shrunk, then "grow" will just make you normal again... + K_RemoveGrowShrink(player); + } + else + { + // ...in Legacy mode. + // Alt. Shrink's a powerup, so Grow overrides! + if (K_IsAltShrunk(player)) + { + player->growshrinktimer = 0; // Paranoia + } + + K_PlayPowerGloatSound(player->mo); + + K_DoGrowShrink(player, false); + + S_StartSound(player->mo, sfx_kc5a); + } + + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SHRINK: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + if (K_IsKartItemAlternate(KITEM_SHRINK)) + { + K_DoGrowShrink(player, true); + K_AltShrinkIFrames(player); + + S_StartSound(player->mo, sfx_kc46); + } + else + { + K_DoShrink(player); + } + + player->itemamount--; + K_PlayPowerGloatSound(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_THUNDERSHIELD: + if (K_GetShieldFromPlayer(player) != KSHIELD_THUNDER) + { + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k41); + } + + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + K_DoThunderShield(player); + if (player->itemamount > 0) + { + // Why is this a conditional? + // Thunder shield: the only item that allows you to + // activate a mine while you're out of its radius, + // the SAME tic it sets your itemamount to 0 + // ...:dumbestass: + player->itemamount--; + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + } + break; + case KITEM_BUBBLESHIELD: + if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) + { + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BUBBLESHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k3f); + if (player->bubblehealth <= 0 || player->bubblehealth > MAXBUBBLEHEALTH) + player->bubblehealth = MAXBUBBLEHEALTH; + } + + if (!HOLDING_ITEM && NO_HYUDORO) + { + if ((buttons & BT_ATTACK && player->itemflags & IF_HOLDREADY) + || (player->bubbleblowup > 0 && player->bubblecool <= bubbletime/2)) // auto + { + if (player->bubblecool == 0) + S_StartSound(player->mo, sfx_s3k75); + + if (player->bubblecool < bubbletime && player->bubblehealth > 0) + { + player->bubbleblowup += 3; + player->bubblehealth--; + } + else if (player->bubblecool >= bubbletime) + player->bubbleblowup++; // overcharge bonus + + if (++player->bubblecool >= bubbletime + TICRATE/2) + { + // If you overcharge the Bubble Shield at + // any point, it pops and gives you a boost. + K_PopPlayerShield(player); + + if (onground) + { + // If you're on the ground, you're going to + // immediately feel the effects of this boost. + // Play a sound to let everyone know! + S_StartSoundAtVolume(player->mo, sfx_cdfm57, 170); + K_PlayBoostTaunt(player->mo); + } + + // experiment: don't boost, just give invulnerability + if (cv_kartbubble_boost_allow.value) + { + player->bubbleboost = 7 * sneakertime / 10; + } + player->flashing = 4*TICRATE/7; + } + } + else + { + if (player->bubblecool > bubbletime) + player->bubblecool = bubbletime; + + if (player->bubbleblowup > 0) + { + player->bubbleblowup--; + if (player->bubbleblowup == 0) + K_BotResetItemConfirm(player, false); + if (player->bubblecool < bubbletime) + player->bubblecool++; + } + + if (player->bubbleblowup == 0 && player->bubblecool > 0) + { + player->bubblecool--; + + if (player->bubblecool == 0 && player->bubblehealth <= 0) + K_PopPlayerShield(player); + } + + if (buttons & BT_ATTACK || player->bubblecool > 0) + { + player->itemflags &= ~IF_HOLDREADY; + } + else + { + player->itemflags |= IF_HOLDREADY; + } + } + } + break; + case KITEM_FLAMESHIELD: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && K_GetShieldFromPlayer(player) != KSHIELD_FLAME) + { + player->itemamount--; + player->flametimer = (itemtime*3); + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FLAMESHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k3e); + } + break; + case KITEM_HYUDORO: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_DoHyudoroSteal(player); // yes. yes they do. + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_POGOSPRING: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO && player->pogospring == 0) + { + player->itemamount--; + K_PlayBoostTaunt(player->mo); + K_DoPogoSpring(player->mo, 32<pogospring = 1; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SUPERRING: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_AwardScaledPlayerRings(player, ASR_SUPERRING); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_KITCHENSINK: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD); + if (mo) + { + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + + // Woah this could be big, lets get the inside scoop... + K_DirectorForceSwitch(player - players, 1); + } + else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown + { + player->itemamount--; + K_ThrowKartItem(player, false, MT_SINK, 1, 2); + K_PlayAttackTaunt(player->mo); + player->itemflags &= ~IF_ITEMOUT; + K_UpdateHnextList(player, true); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SAD: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO + && !player->sadtimer) + { + player->sadtimer = stealtime; + player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + default: + break; + } +} + /** \brief Returns true or false if the player is shrunk. \param player (player_t) Player to test shrunken status for diff --git a/src/k_items.h b/src/k_items.h index 24ab0dc5a..f94ca21d8 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -166,21 +166,22 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); INT32 K_GetRollingRouletteItem(player_t *player); void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); +useoddsfunc_f KO_AltInvinOdds; +useoddsfunc_f KO_SPBRaceOdds; + +void K_DoThunderShield(player_t *player); +void K_BreakBubbleShield(player_t* player); +void K_PlayerItemThink(player_t *player, boolean onground); + boolean K_IsShrunk(const player_t *player); INT16 K_GetShrinkTime(player_t *player); fixed_t K_AccomodateShrinkScaling(fixed_t x); - boolean K_IsShrunkMode(const player_t *player, boolean legacy); - #define K_IsLegacyShrunk(player) (K_IsShrunkMode(player, true)) #define K_IsAltShrunk(player) (K_IsShrunkMode(player, false)) - void K_AltShrinkIFrames(player_t *player); void K_AltShrinkPityIncrease(player_t *player); -useoddsfunc_f KO_AltInvinOdds; -useoddsfunc_f KO_SPBRaceOdds; - #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_kart.c b/src/k_kart.c index 85b2b75c7..892345c9a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3297,7 +3297,7 @@ void K_FlipPlayer(player_t *player, mobj_t *inflictor, mobj_t *source) P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); } -static void K_RemoveGrowShrink(player_t *player) +void K_RemoveGrowShrink(player_t *player) { if (player->mo && !P_MobjWasRemoved(player->mo)) { @@ -4585,65 +4585,6 @@ void K_PuntMine(mobj_t *origMine, mobj_t *punter) mine->flags &= ~MF_NOCLIPTHING; } -#define THUNDERRADIUS 320 - -// Rough size of the outer-rim sprites, after scaling. -// (The hitbox is already pretty strict due to only 1 active frame, -// we don't need to have it disjointedly small too...) -#define THUNDERSPRITE 80 - -static void K_DoThunderShield(player_t *player) -{ - mobj_t *mo; - int i = 0; - fixed_t sx; - fixed_t sy; - angle_t an; - - S_StartSound(player->mo, sfx_zio3); - K_ThunderShieldAttack(player->mo, (THUNDERRADIUS + THUNDERSPRITE) * FRACUNIT); - - // spawn vertical bolt - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); - P_SetTarget(&mo->target, player->mo); - P_SetMobjState(mo, S_LZIO11); - mo->color = SKINCOLOR_TEAL; - mo->scale = player->mo->scale*3 + (player->mo->scale/2); - - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); - P_SetTarget(&mo->target, player->mo); - P_SetMobjState(mo, S_LZIO21); - mo->color = SKINCOLOR_CYAN; - mo->scale = player->mo->scale*3 + (player->mo->scale/2); - - // spawn horizontal bolts; - for (i=0; i<7; i++) - { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); - mo->angle = P_RandomRange(0, 359)*ANG1; - mo->fuse = P_RandomRange(20, 50); - P_SetTarget(&mo->target, player->mo); - P_SetMobjState(mo, S_KLIT1); - } - - // spawn the radius thing: - an = ANGLE_22h; - for (i=0; i<15; i++) - { - sx = player->mo->x + FixedMul((player->mo->scale*THUNDERRADIUS), FINECOSINE((an*i)>>ANGLETOFINESHIFT)); - sy = player->mo->y + FixedMul((player->mo->scale*THUNDERRADIUS), FINESINE((an*i)>>ANGLETOFINESHIFT)); - mo = P_SpawnMobj(sx, sy, player->mo->z, MT_THOK); - mo->angle = an*i; - mo->extravalue1 = THUNDERRADIUS; // Used to know whether we should teleport by radius or something. - mo->scale = player->mo->scale*3; - P_SetTarget(&mo->target, player->mo); - P_SetMobjState(mo, S_KSPARK1); - } -} - -#undef THUNDERRADIUS -#undef THUNDERSPRITE - static void K_FlameDashLeftoverSmoke(mobj_t *src) { UINT8 i; @@ -4673,89 +4614,6 @@ static void K_FlameDashLeftoverSmoke(mobj_t *src) } } -static void K_DoHyudoroSteal(player_t *player) -{ - INT32 i, numplayers = 0; - INT32 playerswappable[MAXPLAYERS]; - INT32 stealplayer = -1; // The player that's getting stolen from - INT32 prandom = 0; - boolean sink = P_RandomChance(FRACUNIT/64); - boolean force_sink = false; - INT32 hyu = hyudorotime; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].mo && players[i].mo->health > 0 && players[i].playerstate == PST_LIVE - && player != &players[i] && !players[i].exiting && !players[i].spectator // Player in-game - - // Can steal from this player - && (gametype == GT_RACE //&& players[i].position < player->position) - || ((gametyperules & GTR_BUMPERS) && players[i].bumper > 0)) - - // Has an item - && (players[i].itemtype - && players[i].itemamount - && !(players[i].itemflags & IF_ITEMOUT) - && !players[i].itemblink)) - { - playerswappable[numplayers] = i; - numplayers++; - } - } - - prandom = P_RandomFixed(); - S_StartSound(player->mo, sfx_s3k92); - - if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from - { - stealplayer = playerswappable[numplayers-1]; - } - else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player - { - stealplayer = playerswappable[prandom%(numplayers-1)]; - } - - force_sink = LUA_HookKartHyudoro(player, &stealplayer, sink); - - if (force_sink || (sink && numplayers > 0 && K_ItemResultEnabled(K_GetKartResult("kitchensink")))) // BEHOLD THE KITCHEN SINK - { - player->hyudorotimer = hyu; - player->stealingtimer = stealtime; - - player->itemtype = KITEM_KITCHENSINK; - player->itemamount = 1; - K_UnsetItemOut(player); - - // Woah this could be big, lets get the inside scoop... - K_DirectorForceSwitch(player - players, 1); - return; - } - else if ((gametype == GT_RACE && player->position == 1) || numplayers == 0) // No-one can be stolen from? Oh well... - { - player->hyudorotimer = hyu; - player->stealingtimer = stealtime; - return; - } - - if (stealplayer > -1) // Now here's where we do the stealing, has to be done here because we still know the player we're stealing from - { - player->hyudorotimer = hyu; - player->stealingtimer = stealtime; - players[stealplayer].stolentimer = stealtime; - - player->itemtype = players[stealplayer].itemtype; - player->itemamount = players[stealplayer].itemamount; - K_UnsetItemOut(player); - - players[stealplayer].itemtype = KITEM_NONE; - players[stealplayer].itemamount = 0; - K_UnsetItemOut(&players[stealplayer]); - - if (P_IsDisplayPlayer(&players[stealplayer]) && !r_splitscreen) - S_StartSound(NULL, sfx_s3k92); - } -} - // Handle these else where to reduce code duplication between panels and sneakers static void K_SneakerPanelStackSound(player_t *player) { @@ -4849,76 +4707,6 @@ void K_DoSneaker(player_t *player, INT32 type) player->boostangle = player->mo->angle; } -static void K_DoShrink(player_t *user) -{ - INT32 i; - mobj_t *mobj, *next; - - S_StartSound(user->mo, sfx_kc46); // Sound the BANG! - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; - if (&players[i] == user) - continue; - if (players[i].position < user->position) - { - //P_FlashPal(&players[i], PAL_NUKE, 10); - - // Grow should get taken away. - if (players[i].growshrinktimer > 0) - K_RemoveGrowShrink(&players[i]); - else - { - // Start shrinking! - K_DropItems(&players[i]); - players[i].growshrinktimer = -(15*TICRATE); - - if (players[i].mo && !P_MobjWasRemoved(players[i].mo)) - { - players[i].mo->scalespeed = mapobjectscale/TICRATE; - players[i].mo->destscale = FixedMul(mapobjectscale, SHRINK_SCALE); - - if (K_PlayerShrinkCheat(&players[i]) == true) - { - players[i].mo->destscale = FixedMul(players[i].mo->destscale, SHRINK_SCALE); - } - - S_StartSound(players[i].mo, sfx_kc59); - } - } - } - } - - // kill everything in the kitem list while we're at it: - for (mobj = kitemcap; mobj; mobj = next) - { - next = mobj->itnext; - - // check if the item is being held by a player behind us before removing it. - // check if the item is a "shield" first, bc i'm p sure thrown items keep the player that threw em as target anyway - - if (mobj->type == MT_BANANA_SHIELD || mobj->type == MT_JAWZ_SHIELD || - mobj->type == MT_SSMINE_SHIELD || mobj->type == MT_EGGMANITEM_SHIELD || - mobj->type == MT_SINK_SHIELD || mobj->type == MT_ORBINAUT_SHIELD) - { - if (mobj->target && mobj->target->player) - { - if (mobj->target->player->position > user->position) - continue; // this guy's behind us, don't take his stuff away! - } - } - - mobj->destscale = 0; - mobj->flags &= ~(MF_SOLID|MF_SHOOTABLE|MF_SPECIAL); - mobj->flags |= MF_NOCLIPTHING; // Just for safety - - if (mobj->type == MT_SPB) - spbplace = -1; - } -} - void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) { const fixed_t vscale = mapobjectscale + (mo->scale - mapobjectscale); @@ -4978,40 +4766,6 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) S_StartSound(mo, (sound == 1 ? sfx_kc2f : sfx_kpogos)); } -static void K_ThrowLandMine(player_t *player) -{ - mobj_t *landMine; - mobj_t *throwmo; - - landMine = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_LANDMINE); - K_FlipFromObject(landMine, player->mo); - landMine->threshold = 10; - - if (landMine->info->seesound) - S_StartSound(player->mo, landMine->info->seesound); - - P_SetTarget(&landMine->target, player->mo); - - P_SetScale(landMine, player->mo->scale); - landMine->destscale = player->mo->destscale; - - landMine->angle = player->mo->angle; - - landMine->momz = (30 * mapobjectscale * P_MobjFlip(player->mo)) + player->mo->momz; - landMine->color = player->skincolor; - - throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM); - P_SetTarget(&throwmo->target, player->mo); - // Ditto: - if (player->mo->eflags & MFE_VERTICALFLIP) - { - throwmo->z -= player->mo->height; - throwmo->eflags |= MFE_VERTICALFLIP; - } - - throwmo->movecount = 0; // above player -} - void K_DoInvincibility(player_t *player, tic_t time) { if (!player->invincibilitytimer) @@ -5059,44 +4813,6 @@ void K_DoInvincibility(player_t *player, tic_t time) P_RestoreMusic(player); } -static void K_DoGrowShrink(player_t *player, boolean shrinking) -{ - player->mo->scalespeed = mapobjectscale/TICRATE; - player->mo->destscale = FixedMul(mapobjectscale, (shrinking) ? SHRINK_SCALE : GROW_SCALE); - - if (K_PlayerShrinkCheat(player) == true) - { - player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); - } - - if ((shrinking) && (ALTSHRINKTIME > 0)) - { - player->growshrinktimer = ALTSHRINKTIME * -TICRATE; - } - else - { - player->growshrinktimer = (gametyperules & GTR_CLOSERPLAYERS ? 8 : 12) * ((shrinking) ? -TICRATE : TICRATE); - } - - if (player->invincibilitytimer > 0) - { - ; // invincibility has priority in P_RestoreMusic, no point in starting here - } - else if (P_IsLocalPlayer(player) == true) - { - S_ChangeMusicSpecial((shrinking) ? "kshrnk" : "kgrow"); - } - else //used to be "if (P_IsDisplayPlayer(player) == false)" - { - if (!shrinking) - { - S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); - } - } - - P_RestoreMusic(player); -} - void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source) { mobj_t *cachenext; @@ -5348,65 +5064,6 @@ void K_DropHnextList(player_t *player) } } -#define MAXSHARDCOUNT 40 -#define SHARDROT (360 * FRACUNIT / 40) -static void K_BreakBubbleShield(player_t* player) -{ - if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) - return; - - const INT32 flip = P_MobjFlip(player->mo); - const fixed_t scalediff = player->shieldtracer->scale - mapobjectscale; - const fixed_t shieldrad = player->shieldtracer->radius; - - for (INT32 i = 0; i < MAXSHARDCOUNT; i++) - { - vector2_t mul_vec = { FRACUNIT, 0 }; - - fixed_t randang = SHARDROT * (i + 1); - fixed_t randzang = P_RandomRange(10, 120) * FRACUNIT; - fixed_t move_magnitude = - FixedMul((P_RandomRange(4, 16) * FRACUNIT), mapobjectscale + (scalediff / 4)); - - FV2_Rotate(&mul_vec, randzang); - - // Do shitty initial 3D rotations around the shield's radius. - mobj_t *shard = P_SpawnMobj( - player->shieldtracer->x + - FixedMul(FixedMul(mul_vec.x, shieldrad), FCOS(FixedAngle(randang))), - player->shieldtracer->y + - FixedMul(FixedMul(mul_vec.x, shieldrad), FSIN(FixedAngle(randang))), - player->shieldtracer->z + ((player->shieldtracer->height + scalediff) / 4) - + FixedMul(mul_vec.y * flip, shieldrad), - MT_BUBBLESHIELD_DEBRIS); - - if (!P_MobjWasRemoved(shard)) - { - //CONS_Printf(M_GetText("randzang: %d, randang: %d\n"), randzang / FRACUNIT, randang / FRACUNIT); - - mul_vec.x = FixedMul(move_magnitude, mul_vec.x); - mul_vec.y = FixedMul(move_magnitude, mul_vec.y * flip); - - vector3_t mom = { - .x = FixedMul(mul_vec.x, FCOS(FixedAngle(randang))), - .y = FixedMul(mul_vec.x, FSIN(FixedAngle(randang))), - .z = mul_vec.y, - }; - - shard->momx = mom.x + player->mo->momx; - shard->momy = mom.y + player->mo->momy; - shard->momz = mom.z + player->mo->momz; - - shard->fuse = 3*TICRATE; - shard->extravalue1 = i % 6; // 16% of shards play the death sound - } - } - - S_StartSound(player->mo, sfx_kc41); -} -#undef MAXSHARDCOUNT -#undef SHARDROT - // For getting hit! void K_PopPlayerShield(player_t *player) { @@ -10541,513 +10198,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else { - boolean force = false; - boolean override = LUA_HookPlayerItem(player, player->itemtype, HOLDING_ITEM, &force); - - if (!override) - { - switch (player->itemtype) - { - case KITEM_SNEAKER: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO) - { - K_DoSneaker(player, SNEAKERTYPE_SNEAKER); - K_PlayBoostTaunt(player->mo); - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_ROCKETSNEAKER: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO - && player->rocketsneakertimer == 0) - { - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - K_PlayBoostTaunt(player->mo); - S_StartSound(player->mo, sfx_s3k3a); - - //K_DoSneaker(player, SNEAKERTYPE_ROCKETSNEAKER); - - player->rocketsneakertimer = (itemtime*3); - player->itemamount--; - K_UpdateHnextList(player, true); - - for (moloop = 0; moloop < 2; moloop++) - { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER); - K_MatchGenericExtraFlags(mo, player->mo); - mo->flags |= MF_NOCLIPTHING; - mo->angle = player->mo->angle; - mo->threshold = 10; - mo->movecount = moloop%2; - mo->movedir = mo->lastlook = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_INVINCIBILITY: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage in Legacy, so you're free to waste it if you have multiple - { - K_DoInvincibility(player, K_GetInvincibilityTime(player)); - K_PlayPowerGloatSound(player->mo); - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_BANANA: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - INT32 moloop; - mobj_t *mo; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown - { - player->itemamount--; - - mobj_t *mo = K_ThrowKartItem(player, false, MT_BANANA, -1, 0); - mo->color = player->skincolor; - - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_EGGMAN: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - player->itemamount--; - player->itemflags |= IF_EGGMANOUT; - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); - if (mo) - { - K_FlipFromObject(mo, player->mo); - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); - } - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_ORBINAUT: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - angle_t newangle; - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s3k3a); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->angle = newangle; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = mo->lastlook = moloop+1; - mo->color = player->skincolor; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_JAWZ: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - angle_t newangle; - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s3k3a); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->angle = newangle; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = mo->lastlook = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown - { - player->itemamount--; - if (player->throwdir == 1 || player->throwdir == 0) - K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); - else if (player->throwdir == -1) // Throwing backward gives you a dud that doesn't home in - K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0); - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_MINE: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD); - if (mo) - { - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) - { - player->itemamount--; - K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); - K_PlayAttackTaunt(player->mo); - player->itemflags &= ~IF_ITEMOUT; - K_UpdateHnextList(player, true); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_LANDMINE: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowLandMine(player); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_BALLHOG: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SPB: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_SPB, 1, 0); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_GROW: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - if (K_IsLegacyShrunk(player)) - { - // If you're shrunk, then "grow" will just make you normal again... - K_RemoveGrowShrink(player); - } - else - { - // ...in Legacy mode. - // Alt. Shrink's a powerup, so Grow overrides! - if (K_IsAltShrunk(player)) - { - player->growshrinktimer = 0; // Paranoia - } - - K_PlayPowerGloatSound(player->mo); - - K_DoGrowShrink(player, false); - - S_StartSound(player->mo, sfx_kc5a); - } - - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SHRINK: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - if (K_IsKartItemAlternate(KITEM_SHRINK)) - { - K_DoGrowShrink(player, true); - K_AltShrinkIFrames(player); - - S_StartSound(player->mo, sfx_kc46); - } - else - { - K_DoShrink(player); - } - - player->itemamount--; - K_PlayPowerGloatSound(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_THUNDERSHIELD: - if (K_GetShieldFromPlayer(player) != KSHIELD_THUNDER) - { - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k41); - } - - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - K_DoThunderShield(player); - if (player->itemamount > 0) - { - // Why is this a conditional? - // Thunder shield: the only item that allows you to - // activate a mine while you're out of its radius, - // the SAME tic it sets your itemamount to 0 - // ...:dumbestass: - player->itemamount--; - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - } - break; - case KITEM_BUBBLESHIELD: - if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) - { - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BUBBLESHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k3f); - if (player->bubblehealth <= 0 || player->bubblehealth > MAXBUBBLEHEALTH) - player->bubblehealth = MAXBUBBLEHEALTH; - } - - if (!HOLDING_ITEM && NO_HYUDORO) - { - if ((buttons & BT_ATTACK && player->itemflags & IF_HOLDREADY) - || (player->bubbleblowup > 0 && player->bubblecool <= bubbletime/2)) // auto - { - if (player->bubblecool == 0) - S_StartSound(player->mo, sfx_s3k75); - - if (player->bubblecool < bubbletime && player->bubblehealth > 0) - { - player->bubbleblowup += 3; - player->bubblehealth--; - } - else if (player->bubblecool >= bubbletime) - player->bubbleblowup++; // overcharge bonus - - if (++player->bubblecool >= bubbletime + TICRATE/2) - { - // If you overcharge the Bubble Shield at - // any point, it pops and gives you a boost. - K_PopPlayerShield(player); - - if (onground) - { - // If you're on the ground, you're going to - // immediately feel the effects of this boost. - // Play a sound to let everyone know! - S_StartSoundAtVolume(player->mo, sfx_cdfm57, 170); - K_PlayBoostTaunt(player->mo); - } - - // experiment: don't boost, just give invulnerability - if (cv_kartbubble_boost_allow.value) - { - player->bubbleboost = 7 * sneakertime / 10; - } - player->flashing = 4*TICRATE/7; - } - } - else - { - if (player->bubblecool > bubbletime) - player->bubblecool = bubbletime; - - if (player->bubbleblowup > 0) - { - player->bubbleblowup--; - if (player->bubbleblowup == 0) - K_BotResetItemConfirm(player, false); - if (player->bubblecool < bubbletime) - player->bubblecool++; - } - - if (player->bubbleblowup == 0 && player->bubblecool > 0) - { - player->bubblecool--; - - if (player->bubblecool == 0 && player->bubblehealth <= 0) - K_PopPlayerShield(player); - } - - if (buttons & BT_ATTACK || player->bubblecool > 0) - { - player->itemflags &= ~IF_HOLDREADY; - } - else - { - player->itemflags |= IF_HOLDREADY; - } - } - } - break; - case KITEM_FLAMESHIELD: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && K_GetShieldFromPlayer(player) != KSHIELD_FLAME) - { - player->itemamount--; - player->flametimer = (itemtime*3); - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FLAMESHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k3e); - } - break; - case KITEM_HYUDORO: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_DoHyudoroSteal(player); // yes. yes they do. - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_POGOSPRING: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO && player->pogospring == 0) - { - player->itemamount--; - K_PlayBoostTaunt(player->mo); - K_DoPogoSpring(player->mo, 32<pogospring = 1; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SUPERRING: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_AwardScaledPlayerRings(player, ASR_SUPERRING); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_KITCHENSINK: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD); - if (mo) - { - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); - } - K_BotResetItemConfirm(player, false); - - // Woah this could be big, lets get the inside scoop... - K_DirectorForceSwitch(player - players, 1); - } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown - { - player->itemamount--; - K_ThrowKartItem(player, false, MT_SINK, 1, 2); - K_PlayAttackTaunt(player->mo); - player->itemflags &= ~IF_ITEMOUT; - K_UpdateHnextList(player, true); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SAD: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO - && !player->sadtimer) - { - player->sadtimer = stealtime; - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - default: - break; - } - } + K_PlayerItemThink(player, onground); } } } diff --git a/src/k_kart.h b/src/k_kart.h index 979263fe9..eb37c70a1 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -300,6 +300,7 @@ void K_SpawnWaterTrail(mobj_t *mobj); boolean K_ItemMobjAllowedtoWaterRun(mobj_t *item); void K_SetTireGrease(player_t *player, tic_t tics); boolean K_SlopeResistance(const player_t *player); +void K_RemoveGrowShrink(player_t *player); void K_SquishPlayer(player_t *player, mobj_t *inflictor, mobj_t *source); void K_ApplyTripWire(player_t *player, tripwirestate_t state); fixed_t K_GetKartSpeedFromStat(UINT8 kartspeed, boolean karmabomb); From d7f3a88d9fdd9926f646ca97bb7b963e537e1bdd Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 8 Nov 2025 17:34:29 +0100 Subject: [PATCH 33/76] Clean up more alt shrink code --- src/g_game.c | 2 +- src/k_items.c | 73 ++++++++++++++------------------------------------- src/k_items.h | 13 +++------ src/k_kart.c | 16 +++++------ 4 files changed, 33 insertions(+), 71 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 88a3841ed..5300fe241 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2866,7 +2866,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) // Keep Shrink status, remove Grow status // Alt. Shrink is a powerup, so don't keep that. - if (K_IsLegacyShrunk(&players[player])) + if (players[player].growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)) growshrinktimer = players[player].growshrinktimer; else growshrinktimer = 0; diff --git a/src/k_items.c b/src/k_items.c index 6549d65ae..b1e072919 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -1481,6 +1481,10 @@ INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, const kartresul return odds; } +#define ALTSHRINK_EPSILON (320 * FRACUNIT) +#define NEIGHBOR_IFRAMES (TICRATE / 2) +#define BASE_IFRAMES (2 * TICRATE) + static void K_DoGrowShrink(player_t *player, boolean shrinking) { player->mo->scalespeed = mapobjectscale/TICRATE; @@ -1500,6 +1504,16 @@ static void K_DoGrowShrink(player_t *player, boolean shrinking) player->growshrinktimer = (gametyperules & GTR_CLOSERPLAYERS ? 8 : 12) * ((shrinking) ? -TICRATE : TICRATE); } + if (shrinking) + { + // Find neighbors + INT32 n = K_CountNeighboringPlayers(player, ALTSHRINK_EPSILON, &(vector3_t){}); + + // For every neighbor, add some iframes for a clean breakaway. + UINT32 iframes = BASE_IFRAMES + n * NEIGHBOR_IFRAMES; + player->flashing = iframes > UINT16_MAX ? UINT16_MAX : iframes; + } + if (player->invincibilitytimer > 0) { ; // invincibility has priority in P_RestoreMusic, no point in starting here @@ -2100,7 +2114,7 @@ void K_PlayerItemThink(player_t *player, boolean onground) if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; - if (K_IsLegacyShrunk(player)) + if (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)) { // If you're shrunk, then "grow" will just make you normal again... K_RemoveGrowShrink(player); @@ -2130,7 +2144,6 @@ void K_PlayerItemThink(player_t *player, boolean onground) if (K_IsKartItemAlternate(KITEM_SHRINK)) { K_DoGrowShrink(player, true); - K_AltShrinkIFrames(player); S_StartSound(player->mo, sfx_kc46); } @@ -2338,60 +2351,14 @@ void K_PlayerItemThink(player_t *player, boolean onground) } } -/** \brief Returns true or false if the player is shrunk. - - \param player (player_t) Player to test shrunken status for - - \return Is this player shrunk? -*/ -boolean K_IsShrunk(const player_t *player) +INT16 K_GetShrinkTime(const player_t *player) { - return (player->growshrinktimer < 0); + return player->growshrinktimer * -1; } -INT16 K_GetShrinkTime(player_t *player) +boolean K_IsAltShrunk(const player_t *player) { - return (player->growshrinktimer * -1); -} - -fixed_t K_AccomodateShrinkScaling(fixed_t x) -{ - return FixedDiv(x, SHRINK_SCALE); -} - -/** \brief Depending on the Shrink type set, this returns true or false if the player is shrunk. - - \param player (player_t) Player to test shrunken status for - \param legacy (boolean) Legacy shrink? - - \return void -*/ -boolean K_IsShrunkMode(const player_t *player, boolean legacy) -{ - boolean shrunk = K_IsShrunk(player); - boolean legacytype = (!K_IsKartItemAlternate(KITEM_SHRINK)); - - if (legacy) - { - return ((shrunk) && (legacytype)); - } - - return ((shrunk) && (!legacytype)); -} - -#define ALTSHRINK_EPSILON (320 * FRACUNIT) -#define NEIGHBOR_IFRAMES (TICRATE / 2) -#define BASE_IFRAMES (2 * TICRATE) - -void K_AltShrinkIFrames(player_t *player) -{ - vector3_t tempclusterpoint; - - // Find neighbors - INT32 N = K_CountNeighboringPlayers(player, ALTSHRINK_EPSILON, &tempclusterpoint); - - // For every neighbor, add some iframes for a clean breakaway. - player->flashing = (UINT16)min((INT32)(UINT16_MAX), BASE_IFRAMES + (NEIGHBOR_IFRAMES * N)); + return player->growshrinktimer < 0 && K_IsKartItemAlternate(KITEM_SHRINK); } #define PITY_SHRINKINCREASE_BASE (3) @@ -2400,7 +2367,7 @@ void K_AltShrinkIFrames(player_t *player) void K_AltShrinkPityIncrease(player_t *player) { // Increase your shrink timer by a little bit for every player you run into. - INT32 shrinktime = (UINT32)(K_GetShrinkTime(player)); + INT32 shrinktime = K_GetShrinkTime(player); fixed_t dimin = FRACUNIT; diff --git a/src/k_items.h b/src/k_items.h index f94ca21d8..b6967531d 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -171,17 +171,12 @@ useoddsfunc_f KO_SPBRaceOdds; void K_DoThunderShield(player_t *player); void K_BreakBubbleShield(player_t* player); -void K_PlayerItemThink(player_t *player, boolean onground); - -boolean K_IsShrunk(const player_t *player); -INT16 K_GetShrinkTime(player_t *player); -fixed_t K_AccomodateShrinkScaling(fixed_t x); -boolean K_IsShrunkMode(const player_t *player, boolean legacy); -#define K_IsLegacyShrunk(player) (K_IsShrunkMode(player, true)) -#define K_IsAltShrunk(player) (K_IsShrunkMode(player, false)) -void K_AltShrinkIFrames(player_t *player); +INT16 K_GetShrinkTime(const player_t *player); +boolean K_IsAltShrunk(const player_t *player); void K_AltShrinkPityIncrease(player_t *player); +void K_PlayerItemThink(player_t *player, boolean onground); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_kart.c b/src/k_kart.c index 892345c9a..2f1c87158 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1568,7 +1568,7 @@ static void K_RespawnChecker(player_t *player) } else if (player->respawn == 1) { - if (K_IsShrunk(player)) + if (player->growshrinktimer < 0) { player->mo->scalespeed = mapobjectscale/TICRATE; player->mo->destscale = (6*mapobjectscale)/8; @@ -2638,8 +2638,8 @@ static void K_GetKartBoostPower(player_t *player) if (K_IsAltShrunk(player)) // Alt. Shrink { - fixed_t shrinkspeed = K_AccomodateShrinkScaling(SHRINKSPEEDBOOST); - fixed_t shrinkaccel = K_AccomodateShrinkScaling(SHRINKACCELBOOST); + fixed_t shrinkspeed = FixedDiv(SHRINKSPEEDBOOST, SHRINK_SCALE); + fixed_t shrinkaccel = FixedDiv(SHRINKACCELBOOST, SHRINK_SCALE); K_DoBoost(player, FixedMul(shrinkspeed, GROW_SCALE), FixedMul(shrinkaccel, GROW_SCALE), SHRINKSTACKABLE, SHRINKSTACKABLE); // + 45% top speed, + 200% acceleration } @@ -3303,7 +3303,7 @@ void K_RemoveGrowShrink(player_t *player) { if (player->growshrinktimer > 0) // Play Shrink noise S_StartSound(player->mo, sfx_kc59); - else if (K_IsShrunk(player)) // Play Grow noise + else if (player->growshrinktimer < 0) // Play Grow noise S_StartSound(player->mo, sfx_kc5a); K_KartResetPlayerColor(player); @@ -3331,7 +3331,7 @@ void K_SquishPlayer(player_t *player, mobj_t *inflictor, mobj_t *source) player->squishedtimer = TICRATE; // Reduce Shrink timer for Legacy Shrink - if (K_IsLegacyShrunk(player)) + if (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)) { player->growshrinktimer += TICRATE; if (player->growshrinktimer >= 0) @@ -7066,7 +7066,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { if (player->growshrinktimer > 0) player->growshrinktimer--; - if (K_IsShrunk(player)) + if (player->growshrinktimer < 0) player->growshrinktimer++; // Back to normal @@ -7400,7 +7400,7 @@ void K_KartResetPlayerColor(player_t *player) if (player->growshrinktimer % 5 == 0) { player->mo->colorized = true; - player->mo->color = (K_IsShrunk(player) ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE); + player->mo->color = player->growshrinktimer < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE; fullbright = true; goto finalise; } @@ -10221,7 +10221,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->itemtype == KITEM_SPB || player->itemtype == KITEM_SHRINK - || K_IsLegacyShrunk(player)) + || (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK))) indirectitemcooldown = 20*TICRATE; if (player->hyudorotimer > 0) From a7f50a597e290d483ef72ca6e80651aaeb7daf8d Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sat, 8 Nov 2025 12:28:20 -0500 Subject: [PATCH 34/76] Implement check indicators for altshrink and Flameshield --- src/k_hud.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 114bef827..b284fe485 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -187,7 +187,7 @@ static patch_t *kp_itemx; static patch_t *kp_sadface[2]; -static patch_t *kp_check[6]; +static patch_t *kp_check[11]; static patch_t *kp_eggnum[4]; @@ -548,11 +548,12 @@ void K_LoadKartHUDGraphics(void) // CHECK indicators sprintf(buffer, "K_CHECKx"); - for (i = 0; i < 6; i++) + for (i = 0; i < 10; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_check[i], "%s", buffer); } + HU_UpdatePatch(&kp_check[10], "K_CHECKA"); // Eggman warning numbers sprintf(buffer, "K_EGGNx"); @@ -3219,7 +3220,15 @@ static void K_drawKartPlayerCheck(void) pnum++; // white frames } - if (checkplayer->itemtype == KITEM_GROW || checkplayer->growshrinktimer > 0) + if (checkplayer->itemtype == KITEM_FLAMESHIELD || checkplayer->flametimer > 0) + { + pnum += 8; + } + else if (K_IsAltShrunk(checkplayer) && (checkplayer->itemtype == KITEM_SHRINK || checkplayer->growshrinktimer < 0)) + { + pnum += 6; + } + else if (checkplayer->itemtype == KITEM_GROW || checkplayer->growshrinktimer > 0) { pnum += 4; } From 528a4640a0eeca5f5d0a4e339d7c525367511769 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sat, 8 Nov 2025 16:31:25 -0500 Subject: [PATCH 35/76] implement KIF_HIDEFROMROULETTE --- src/deh_tables.c | 1 + src/k_items.c | 3 +++ src/k_items.h | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index e5b1cbea1..d55eb8ce3 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1563,6 +1563,7 @@ struct int_const_s const INT_CONST[] = { {"KIF_ANIMATED",KIF_ANIMATED}, {"KIF_DARKBG",KIF_DARKBG}, {"KIF_COLPATCH2PLAYER",KIF_COLPATCH2PLAYER}, + {"KIF_HIDEFROMROULETTE",KIF_HIDEFROMROULETTE}, // kartresultflags_e {"KRF_INDIRECTITEM",KRF_INDIRECTITEM}, diff --git a/src/k_items.c b/src/k_items.c index b1e072919..ee7c576aa 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -920,6 +920,9 @@ INT32 K_GetRollingRouletteItem(player_t *player) { kartresult_t *result = &kartresults[i]; + if (kartitems[result->type].flags & KIF_HIDEFROMROULETTE) + continue; + for (j = 0; j < numseen; j++) if (seen[j] == result->type) break; diff --git a/src/k_items.h b/src/k_items.h index b6967531d..0278d3af3 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -57,7 +57,7 @@ typedef enum KIF_ANIMATED = 1<<0, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT) KIF_DARKBG = 1<<1, // use dark item roulette BG KIF_COLPATCH2PLAYER = 1<<2, // colourize patch to player - KIF_HIDEFROMROULETTE = 1<<3, // TODO: don't show this item in the roulette (inversion of xItem's showInRoulette item flag) + KIF_HIDEFROMROULETTE = 1<<3, // don't show this item in the roulette (inversion of xItem's showInRoulette item flag) } ATTRPACK kartitemflags_e; // flags relevant to item rolls and usage From 2546729b0c2efc56e89d6fa097512f10aa4789a1 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 8 Nov 2025 23:43:40 +0100 Subject: [PATCH 36/76] Custom item papersprites --- src/hardware/hw_main.c | 8 +++ src/info/sprites.h | 3 - src/k_hud.c | 11 ++-- src/k_items.c | 25 ++++++++ src/k_items.h | 8 ++- src/k_kart.c | 45 -------------- src/k_kart.h | 4 -- src/lua_hudlib.c | 3 + src/p_mobj.c | 138 +++++++++++++---------------------------- src/r_things.cpp | 69 +++++++++++++++++++++ src/r_things.h | 1 + 11 files changed, 159 insertions(+), 156 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 1def2ef49..f0c745a74 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -50,6 +50,7 @@ #include "../k_kart.h" #include "../r_fps.h" #include "../r_plane.h" // R_FlatDimensionsFromLumpSize +#include "../k_items.h" #define R_FAKEFLOORS #define HWPRECIP @@ -4789,6 +4790,13 @@ static void HWR_ProjectSprite(mobj_t *thing) sprdef = &((skin_t *)thing->skin)->sprites[thing->sprite2]; #ifdef ROTSPRITE sprinfo = &((skin_t *)thing->skin)->sprinfo[thing->sprite2]; +#endif + } + else if (thing->sprite == SPR_ITEM) + { + sprdef = &kartitems[thing->threshold > 0 && thing->threshold < numkartitems ? thing->threshold : 0].spritedef; +#ifdef ROTSPRITE + sprinfo = &spriteinfo[SPR_UNKN]; #endif } else diff --git a/src/info/sprites.h b/src/info/sprites.h index 89595c2a4..3f87570ef 100644 --- a/src/info/sprites.h +++ b/src/info/sprites.h @@ -493,9 +493,6 @@ _(ARRO) // player arrows _(ITEM) // base item _(ITMO) // Multi-Orbinaut _(ITMI) // Invincibility -_(ITSN) // Multi-Sneaker -_(ITBA) // Multi-Banana -_(ITJA) // Multi-Jawz _(ITMN) _(WANT) diff --git a/src/k_hud.c b/src/k_hud.c index b284fe485..46a1be440 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -544,6 +544,8 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&item->graphics[0].patches[j], item->graphics[0].patchnames[j]); for (j = 0; j < item->graphics[1].numpatches; j++) HU_UpdatePatch(&item->graphics[1].patches[j], item->graphics[1].patchnames[j]); + + R_AddKartItemSprites(item); } // CHECK indicators @@ -1182,7 +1184,6 @@ static void K_drawKartItem(void) patch_t *localbg; boolean dark = false; INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... - INT32 numberdisplaymin = 2; fixed_t itembar = -1, flamebar = -1; UINT16 localcolor = SKINCOLOR_NONE; SINT8 colormode = TC_RAINBOW; @@ -1301,11 +1302,7 @@ static void K_drawKartItem(void) localpatch = kp_nodraw; // diagnose underflows if (stplyr->itemtype > 0 && stplyr->itemtype < numkartitems) - { - kartitem_t *item = &kartitems[stplyr->itemtype]; - numberdisplaymin = item->flags & KIF_ANIMATED ? 2 : item->graphics[tiny ? 1 : 0].numpatches + 1; - dark = item->flags & KIF_DARKBG; - } + dark = kartitems[stplyr->itemtype].flags & KIF_DARKBG; if ((stplyr->itemflags & IF_ITEMOUT) && !(leveltime & 1)) localpatch = kp_nodraw; @@ -1373,7 +1370,7 @@ static void K_drawKartItem(void) } // Then, the numbers: - if (stplyr->itemamount >= numberdisplaymin && !stplyr->itemroulette) + if (stplyr->itemamount >= K_GetItemNumberDisplayMin(stplyr->itemtype, tiny) && !stplyr->itemroulette) { localbg = K_getItemMulPatch(tiny); V_DrawMappedPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), localbg, colormap); // flip this graphic for p2 and p4 in split and shift it. diff --git a/src/k_items.c b/src/k_items.c index ee7c576aa..7381bdfb3 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -170,6 +170,31 @@ boolean K_IsKartItemAlternate(kartitemtype_e itemtype) return itemtype >= 0 && itemtype < numkartitems && kartitems[itemtype].altenabled; } +UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny) +{ + if (type <= 0 || type >= numkartitems) + return 1; + + return kartitems[type].flags & KIF_ANIMATED ? 2 : kartitems[type].graphics[tiny ? 1 : 0].numpatches + 1; +} + +// sprite3 baybeeeee!!!! +// the renderer uses mobj->threshold to determine the item type +void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount) +{ + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; + + if (itemType <= 0 || itemType >= numkartitems) + return; + + UINT8 numpatches = kartitems[itemType].graphics[0].numpatches; + if (kartitems[itemType].flags & KIF_ANIMATED) + part->frame |= (leveltime % (numpatches*3)) / 3; + else + part->frame |= CLAMP(itemCount - 1, 0, numpatches - 1); +} + /** \brief Are the odds in legacy distancing mode? \return true if kartforcelegacyodds is enabled, or the map uses legacy WPs diff --git a/src/k_items.h b/src/k_items.h index 0278d3af3..04d6c9e21 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -16,6 +16,7 @@ #include "doomdef.h" #include "doomtype.h" +#include "r_defs.h" #ifdef __cplusplus extern "C" { @@ -88,8 +89,9 @@ struct kartitemgraphics_t struct kartitem_t { dehinfo_t info; - kartitemgraphics_t graphics[2]; kartitemflags_e flags; + kartitemgraphics_t graphics[2]; + spritedef_t spritedef; consvar_t *altcvar; // if not NULL, an altitem exists boolean altenabled; @@ -151,9 +153,11 @@ void K_RegisterItem(kartitemtype_e itemtype); kartresult_t *K_RegisterResult(const char *name, boolean alternate); kartresult_t *K_GetKartResultAlt(const char *name, boolean alternate); #define K_GetKartResult(name) K_GetKartResultAlt(name, false) +void K_SetupItemOdds(void); boolean K_ItemResultEnabled(const kartresult_t *result); boolean K_IsKartItemAlternate(kartitemtype_e itemtype); -void K_SetupItemOdds(void); +UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny); +void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount); void K_KartHandleUniqueCooldown(void); boolean K_LegacyOddsMode(void); diff --git a/src/k_kart.c b/src/k_kart.c index 7b9a9461f..c94052045 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10492,16 +10492,6 @@ void K_CheckSpectateStatus(boolean considermapreset) } } -UINT8 K_GetInvincibilityItemFrame(void) -{ - return ((leveltime % (7*3)) / 3); -} - -UINT8 K_GetMultItemFrame(UINT8 count, UINT8 max) -{ - return min(count - 1, max); -} - boolean K_IsSPBInGame(void) { UINT8 i; @@ -10679,41 +10669,6 @@ INT32 K_CheckpointThreshold(boolean roundup) return numstarposts; } -void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount) -{ - switch (itemType) - { - case KITEM_SNEAKER: - part->sprite = SPR_ITSN; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 2); - break; - case KITEM_ORBINAUT: - part->sprite = SPR_ITMO; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 3); - break; - case KITEM_BANANA: - part->sprite = SPR_ITBA; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 3); - break; - case KITEM_JAWZ: - part->sprite = SPR_ITJA; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 1); - break; - case KITEM_INVINCIBILITY: - part->sprite = SPR_ITMI; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); - break; - case KITEM_SAD: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; - break; - default: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); - break; - } -} - boolean K_NotFreePlay(void) { UINT8 i; diff --git a/src/k_kart.h b/src/k_kart.h index eb37c70a1..ba69b4834 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -319,8 +319,6 @@ SINT8 K_Sliptiding(const player_t *player); fixed_t K_PlayerBaseFriction(const player_t *player, fixed_t original); void K_MoveKartPlayer(player_t *player, boolean onground); void K_CheckSpectateStatus(boolean considermapreset); -UINT8 K_GetInvincibilityItemFrame(void); -UINT8 K_GetMultItemFrame(UINT8 count, UINT8 max); boolean K_IsSPBInGame(void); // sound stuff for lua @@ -357,8 +355,6 @@ boolean K_UsingLegacyCheckpoints(void); boolean K_UsingPatchedMap(void); INT32 K_CheckpointThreshold(boolean roundup); -void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount); - fixed_t K_BoostRescale(fixed_t value,fixed_t oldmin,fixed_t oldmax,fixed_t newmin,fixed_t newmax); void K_DoBoost(player_t *player, fixed_t speedboost, fixed_t accelboost, boolean stack, boolean visible); void K_ClearBoost(player_t *player); diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index e33b4fb73..335892a6d 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -411,6 +411,9 @@ static int libd_getSpritePatch(lua_State *L) if (i == SPR_PLAY) // Use getSprite2Patch instead! return 0; + if (i == SPR_ITEM) // Use... uhhhhh... + return 0; + sprdef = &sprites[i]; // set frame number diff --git a/src/p_mobj.c b/src/p_mobj.c index 43ee4f500..045cf6074 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4187,40 +4187,8 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) K_UpdateMobjItemOverlay(part, itemType, mobj->movecount); // update number frame - if (K_GetShieldFromItem(itemType) != KSHIELD_NONE) // shields don't stack, so don't show a number - ; - else - { - switch (itemType) - { - case KITEM_SNEAKER: - if (mobj->movecount - 1 > K_GetMultItemFrame(mobj->movecount, 2)) - count = mobj->movecount; - break; - case KITEM_ORBINAUT: // only display the number when the sprite no longer changes - if (mobj->movecount - 1 > K_GetMultItemFrame(mobj->movecount, 3)) - count = mobj->movecount; - break; - case KITEM_BANANA: // only display the number when the sprite no longer changes - if (mobj->movecount - 1 > K_GetMultItemFrame(mobj->movecount, 3)) - count = mobj->movecount; - break; - case KITEM_JAWZ: // only display the number when the sprite no longer changes - if (mobj->movecount - 1 > K_GetMultItemFrame(mobj->movecount, 1)) - count = mobj->movecount; - break; - case KITEM_SUPERRING: // always display the number, and multiply it by 5 - count = mobj->movecount * 5; - break; - case KITEM_SAD: // never display the number - case KITEM_SPB: - break; - default: - if (mobj->movecount > 1) - count = mobj->movecount; - break; - } - } + if (mobj->movecount >= K_GetItemNumberDisplayMin(itemType, false)) + count = mobj->movecount; while (count > 0) { @@ -7626,101 +7594,79 @@ static void P_MobjSceneryThink(mobj_t *mobj) if (!(mobj->renderflags & RF_DONTDRAW)) { - const INT32 numberdisplaymin = ((mobj->target->player->itemtype == KITEM_ORBINAUT) ? 5 : 2); + statenum_t statenum = S_PLAYERARROW_BOX; + kartitemtype_e itemtype = mobj->target->player->itemtype; + UINT8 itemamount = 0; + boolean hide = false; // Set it to use the correct states for its condition if (mobj->target->player->itemroulette) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = K_GetRollingRouletteItem(mobj->target->player) | FF_FULLBRIGHT; - mobj->tracer->renderflags &= ~RF_DONTDRAW; + itemtype = K_GetRollingRouletteItem(mobj->target->player); } else if (mobj->target->player->stealingtimer < 0) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_HYUDORO; - if (leveltime & 2) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_HYUDORO; + hide = leveltime & 2; } else if ((mobj->target->player->stealingtimer > 0) && (leveltime & 2)) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_HYUDORO; - mobj->tracer->renderflags &= ~RF_DONTDRAW; + itemtype = KITEM_HYUDORO; } else if (mobj->target->player->eggmanexplode > 1) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_EGGMAN; - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_EGGMAN; + hide = leveltime & 1; } else if (mobj->target->player->rocketsneakertimer > 1) { //itembar = mobj->target->player->rocketsneakertimer; -- not today satan - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_ROCKETSNEAKER; - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_ROCKETSNEAKER; + hide = leveltime & 1; } else if (mobj->target->player->flametimer > 1) { //itembar = mobj->target->player->flametimer; -- not today satan - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_FLAMESHIELD; - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_FLAMESHIELD; + hide = leveltime & 1; } else if (mobj->target->player->growshrinktimer > 0) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_GROW; - - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_GROW; + hide = leveltime & 1; } else if (mobj->target->player->itemtype && mobj->target->player->itemamount > 0) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); + itemtype = mobj->target->player->itemtype; + itemamount = mobj->target->player->itemamount; + hide = mobj->target->player->itemflags & IF_ITEMOUT && leveltime & 1; + } + else + { + statenum = S_PLAYERARROW; + } - K_UpdateMobjItemOverlay(mobj->tracer, mobj->target->player->itemtype,mobj->target->player->itemamount); - - if (mobj->target->player->itemflags & IF_ITEMOUT) - { - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; - } + P_SetMobjState(mobj, statenum); + if (statenum == S_PLAYERARROW_BOX) + { + mobj->tracer->sprite = SPR_ITEM; + K_UpdateMobjItemOverlay(mobj->tracer, itemtype, itemamount); + mobj->tracer->threshold = itemtype; // sorry, i have to + mobj->tracer->frame &= ~FF_PAPERSPRITE; + if (hide) + mobj->tracer->renderflags |= RF_DONTDRAW; else mobj->tracer->renderflags &= ~RF_DONTDRAW; } else { - P_SetMobjState(mobj, S_PLAYERARROW); P_SetMobjState(mobj->tracer, S_PLAYERARROW_ITEM); + mobj->tracer->renderflags &= ~RF_DONTDRAW; } mobj->tracer->destscale = scale; - if (mobj->target->player->itemamount >= numberdisplaymin + if (mobj->target->player->itemamount >= K_GetItemNumberDisplayMin(mobj->target->player->itemtype, false) && mobj->target->player->itemamount <= 10) // Meh, too difficult to support greater than this; convert this to a decent HUD object and then maybe :V { mobj_t *number = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY); @@ -7736,6 +7682,10 @@ static void P_MobjSceneryThink(mobj_t *mobj) P_SetMobjState(numx, S_PLAYERARROW_X); P_SetScale(numx, mobj->scale); numx->destscale = scale; + + // ugh... overlays.... + number->old_z = number->z += FixedMul(number->state->var2 * FRACUNIT, mobj->scale); + numx->old_z = numx->z += FixedMul(number->state->var2 * FRACUNIT, mobj->scale); } if (K_IsPlayerWanted(mobj->target->player) && mobj->movecount != 1) @@ -8501,12 +8451,10 @@ static boolean P_MobjRegularThink(mobj_t *mobj) || mobj->movecount != part->movecount) // change the capsule properties if the item type or amount is updated P_RefreshItemCapsuleParts(mobj); - // animate invincibility capsules + K_UpdateMobjItemOverlay(part, mobj->threshold, mobj->movecount); + if (mobj->threshold == KITEM_INVINCIBILITY) - { mobj->color = K_RainbowColor(leveltime); - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); - } } break; case MT_ORBINAUT: diff --git a/src/r_things.cpp b/src/r_things.cpp index b94349561..45ae0f379 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -51,6 +51,7 @@ #include "k_color.h" #include "k_kart.h" #include "r_fps.h" +#include "k_items.h" #define MINZ (FRACUNIT*16) #define BASEYCENTER (BASEVIDHEIGHT/2) @@ -283,6 +284,58 @@ void R_AddKartFaces(skin_t *skin) } #undef NUMFACES +// dear god... what a mess... +void R_AddKartItemSprites(kartitem_t *item) +{ + spritedef_t *spritedef = &item->spritedef; + memset(sprtemp, 0xff, sizeof(sprtemp)); + spritename = "ITEM"; + memset(spritedef, 0, sizeof(*spritedef)); + maxframe = spritedef->numframes - 1; + + const kartitemgraphics_t *graphics = &item->graphics[0]; + for (UINT8 i = 0; i < graphics->numpatches; i++) + { + lumpnum_t lumpnum = W_CheckNumForName(graphics->patchnames[i]); + if (lumpnum == LUMPERROR) + I_Error("R_AddKartItemSprites: missing patch %s", graphics->patchnames[i]); + + spritecachedinfo[numspritelumps].width = graphics->patches[i]->width<patches[i]->leftoffset<patches[i]->topoffset<patches[i]->height<numframes && // has been allocated + spritedef->numframes < maxframe) // more frames are defined ? + { + Z_Free(spritedef->spriteframes); + spritedef->spriteframes = NULL; + } + + // allocate this sprite's frames + if (!spritedef->spriteframes) + spritedef->spriteframes = + static_cast(Z_Malloc(maxframe * sizeof (*spritedef->spriteframes), PU_STATIC, NULL)); + + spritedef->numframes = maxframe; + memcpy(spritedef->spriteframes, sprtemp, maxframe*sizeof (spriteframe_t)); +} + // Install a single sprite, given its identifying name (4 chars) // // (originally part of R_AddSpriteDefs) @@ -1787,6 +1840,22 @@ static void R_ProjectSprite(mobj_t *thing) sprdef = &sprites[thing->sprite]; #ifdef ROTSPRITE sprinfo = &spriteinfo[thing->sprite]; +#endif + frame = thing->frame&FF_FRAMEMASK; + } + } + else if (thing->sprite == SPR_ITEM) + { + sprdef = &kartitems[thing->threshold > 0 && thing->threshold < numkartitems ? thing->threshold : 0].spritedef; + sprinfo = &spriteinfo[SPR_UNKN]; + + if (frame >= sprdef->numframes) { + CONS_Alert(CONS_ERROR, M_GetText("R_ProjectSprite: invalid kartitem sprite frame %zu\n"), frame); + thing->sprite = states[S_UNKNOWN].sprite; + thing->frame = states[S_UNKNOWN].frame; + sprdef = &sprites[thing->sprite]; +#ifdef ROTSPRITE + sprinfo = &spriteinfo[thing->sprite]; #endif frame = thing->frame&FF_FRAMEMASK; } diff --git a/src/r_things.h b/src/r_things.h index cd101f2ea..3b6175af5 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -32,6 +32,7 @@ extern "C" { #define FEETADJUST (4< Date: Sat, 8 Nov 2025 20:54:44 -0500 Subject: [PATCH 37/76] Display the ALT indicator more consistently in roulettes and item toggles Doesn't account for unique alt item patches... yet --- src/k_hud.c | 28 ++++++++++++++++++++++++++++ src/k_hud.h | 1 + src/lua_hudlib.c | 7 ++++++- src/m_menu.c | 15 +++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/k_hud.c b/src/k_hud.c index 46a1be440..c34a7ac88 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -181,6 +181,7 @@ static patch_t *kp_wantedreticle; static patch_t *kp_minimapdot; static patch_t *kp_itembg[8]; +static patch_t *kp_itemalt[2]; static patch_t *kp_itemtimer[2]; static patch_t *kp_itemmulsticker[4]; static patch_t *kp_itemx; @@ -526,6 +527,8 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_itembg[5], "K_ITBCD"); HU_UpdatePatch(&kp_itembg[6], "K_ISBC"); HU_UpdatePatch(&kp_itembg[7], "K_ISBCD"); + HU_UpdatePatch(&kp_itemalt[0], "K_ALTITM"); + HU_UpdatePatch(&kp_itemalt[1], "K_ALTITS"); HU_UpdatePatch(&kp_itemtimer[0], "K_ITIMER"); HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); @@ -739,6 +742,12 @@ patch_t *K_getItemMulPatch(boolean small) return K_UseColorHud() ? kp_itemmulsticker[2+ofs] : kp_itemmulsticker[ofs]; } +patch_t *K_getItemAltPatch(boolean small) +{ + UINT8 ofs = small ? 1 : 0; + return kp_itemalt[ofs]; +} + // This version of the function was prototyped in Lua by Nev3r ... a HUGE thank you goes out to them! void K_ObjectTracking(trackingResult_t *result, const vector3_t *point, boolean reverse) { @@ -1190,6 +1199,7 @@ static void K_drawKartItem(void) UINT8 *colmap = NULL; UINT8 *colormap = NULL; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff + boolean isalt = false; if (stplyr->itemroulette) { @@ -1268,7 +1278,10 @@ static void K_drawKartItem(void) flamebar = FixedDiv(stplyr->invincibilitycancel, 26); if (leveltime & 1) + { localpatch = K_GetCachedItemPatch(KITEM_INVINCIBILITY, tiny, 0); + isalt = true; + } else localpatch = kp_nodraw; } @@ -1300,6 +1313,8 @@ static void K_drawKartItem(void) localpatch = K_GetCachedItemPatch(stplyr->itemtype, tiny, stplyr->itemamount); if (localpatch == NULL) localpatch = kp_nodraw; // diagnose underflows + else if (kartitems[stplyr->itemtype].altenabled) + isalt = true; if (stplyr->itemtype > 0 && stplyr->itemtype < numkartitems) dark = kartitems[stplyr->itemtype].flags & KIF_DARKBG; @@ -1375,6 +1390,13 @@ static void K_drawKartItem(void) localbg = K_getItemMulPatch(tiny); V_DrawMappedPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), localbg, colormap); // flip this graphic for p2 and p4 in split and shift it. V_DrawFixedPatch(fx<itemamount)); @@ -1387,8 +1409,14 @@ static void K_drawKartItem(void) } } else + { V_DrawFixedPatch(fx<menuitems[itemOn].patch); const char *displayname; + boolean hasalt; // ha, salt! (has alt) if (result != NULL && kartitems[result->type].altcvar != NULL && kartitems[result->type].altcvar->value == 1) result = K_GetKartResultAlt(result->cvar->name, true); @@ -8445,12 +8446,19 @@ void MD_DrawMonitorToggles(void) } else V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); + + if (item->altcvar != NULL && item->altcvar->value == 1) + { + V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ALTITS", PU_CACHE)); + } } x += spacing; y = currentMenu->y+(spacing/4); } + hasalt = false; + switch (currentMenu->menuitems[itemOn].argument) { case 2: @@ -8499,6 +8507,7 @@ void MD_DrawMonitorToggles(void) if (item->altcvar != NULL) { + hasalt = true; translucent = item->altcvar->value == 1 ? 0 : V_TRANSLUCENT; V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName("K_ALTITM", PU_CACHE)); } @@ -8510,6 +8519,12 @@ void MD_DrawMonitorToggles(void) shitsfree--; V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, MENUCAPS|highlightflags, va("* %s *", displayname)); + + if (hasalt) + V_DrawThinString(8, + BASEVIDHEIGHT - 12, + highlightflags | V_ALLOWLOWERCASE | V_SNAPTOBOTTOM | V_SNAPTOLEFT, + "Press BACKSPACE to swap variants"); } INT32 MR_HandleMonitorToggles(INT32 choice) From 8f4c710fd6cc4b2f420f3c67251cdc4a141c32d1 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Sun, 9 Nov 2025 00:29:16 -0500 Subject: [PATCH 38/76] Make the multi-item ALT indicator use separate patches Minimize hardcode offsets --- src/k_hud.c | 13 +++++++------ src/k_hud.h | 2 +- src/lua_hudlib.c | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index c34a7ac88..86cc65795 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -181,7 +181,7 @@ static patch_t *kp_wantedreticle; static patch_t *kp_minimapdot; static patch_t *kp_itembg[8]; -static patch_t *kp_itemalt[2]; +static patch_t *kp_itemalt[4]; static patch_t *kp_itemtimer[2]; static patch_t *kp_itemmulsticker[4]; static patch_t *kp_itemx; @@ -529,6 +529,8 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_itembg[7], "K_ISBCD"); HU_UpdatePatch(&kp_itemalt[0], "K_ALTITM"); HU_UpdatePatch(&kp_itemalt[1], "K_ALTITS"); + HU_UpdatePatch(&kp_itemalt[2], "K_ALTIMM"); + HU_UpdatePatch(&kp_itemalt[3], "K_ALTISM"); HU_UpdatePatch(&kp_itemtimer[0], "K_ITIMER"); HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); @@ -742,9 +744,9 @@ patch_t *K_getItemMulPatch(boolean small) return K_UseColorHud() ? kp_itemmulsticker[2+ofs] : kp_itemmulsticker[ofs]; } -patch_t *K_getItemAltPatch(boolean small) +patch_t *K_getItemAltPatch(boolean small, boolean multimode) { - UINT8 ofs = small ? 1 : 0; + UINT8 ofs = (small ? 1 : 0) + (multimode ? 2 : 0); return kp_itemalt[ofs]; } @@ -1393,8 +1395,7 @@ static void K_drawKartItem(void) if (isalt) { - const INT32 altofs = tiny ? -12 : -25; - V_DrawFixedPatch((fx+altofs)< Date: Sun, 9 Nov 2025 00:30:01 -0500 Subject: [PATCH 39/76] Hash hash hash hash hash --- src/d_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 550cc71d0..335a422e5 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -94,7 +94,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0xa489b285be78e8d4 +#define ASSET_HASH_MAIN_PK3 0x5ed08ba7a6c6257f #define ASSET_HASH_MAPPATCH_PK3 0xd4d4ce4a090d5473 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE From a7508fa92322082ae5262b4c42e96b330ed992ba Mon Sep 17 00:00:00 2001 From: Anonimus Date: Sun, 9 Nov 2025 03:25:12 -0500 Subject: [PATCH 40/76] Expose a bunch of functions to Lua --- src/k_kart.c | 4 ++-- src/k_kart.h | 2 ++ src/lua_baselib.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index bb42a879f..5508f9fbf 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2422,7 +2422,7 @@ UINT16 K_GetInvincibilityTime(player_t *player) #undef LEGACYALTINVINMUL -static fixed_t K_GetInvincibilitySpeed(UINT16 time) +fixed_t K_GetInvincibilitySpeed(UINT16 time) { if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return INVINSPEEDBOOSTLGC; @@ -2431,7 +2431,7 @@ static fixed_t K_GetInvincibilitySpeed(UINT16 time) return Easing_OutCubic(t, 0, INVINSPEEDBOOSTALT); } -static fixed_t K_GetInvincibilityAccel(UINT16 time) +fixed_t K_GetInvincibilityAccel(UINT16 time) { if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return INVINACCELBOOSTLGC; diff --git a/src/k_kart.h b/src/k_kart.h index ba69b4834..2b7f842fe 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -259,6 +259,8 @@ void K_DoSneaker(player_t *player, INT32 type); void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound); fixed_t K_InvincibilityGradient(UINT16 time); UINT16 K_GetInvincibilityTime(player_t *player); +fixed_t K_GetInvincibilitySpeed(UINT16 time); +fixed_t K_GetInvincibilityAccel(UINT16 time); void K_DoInvincibility(player_t *player, tic_t time); void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source); void K_UpdateHnextList(player_t *player, boolean clean); diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 01d70bcd1..c46dbfdfe 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -4043,6 +4043,30 @@ static int lib_kGetItemPatch(lua_State *L) return 1; } +static int lib_kIsKartItemAlternate(lua_State *L) +{ + kartitemtype_e item = (kartitemtype_e)luaL_optinteger(L, 1, KITEM_NONE); + // HUDSAFE + + if (item <= 0 || item >= numkartitems) + return luaL_error(L, "item number %d out of range (0 - %d)", item, numkartitems-1); + + lua_pushboolean(L, K_IsKartItemAlternate(item)); + return 1; +} + +static int lib_kIsAltShrunk(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + + //HUDSAFE + if (!player) + return LUA_ErrInvalid(L, "player_t"); + + lua_pushboolean(L, K_IsAltShrunk(player)); + return 1; +} + static int lib_kGetCollideAngle(lua_State *L) { mobj_t *t1 = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); @@ -4277,6 +4301,33 @@ static int lib_kGetNewSpeed(lua_State *L) return 1; } +static int lib_kInvincibilityGradient(lua_State *L) +{ + UINT16 time = (UINT16)luaL_checkinteger(L, 1); + // HUDSAFE + + lua_pushinteger(L, K_InvincibilityGradient(time)); + return 1; +} + +static int lib_kGetInvincibilitySpeed(lua_State *L) +{ + UINT16 time = (UINT16)luaL_checkinteger(L, 1); + // HUDSAFE + + lua_pushinteger(L, K_GetInvincibilitySpeed(time)); + return 1; +} + +static int lib_kGetInvincibilityAccel(lua_State *L) +{ + UINT16 time = (UINT16)luaL_checkinteger(L, 1); + // HUDSAFE + + lua_pushinteger(L, K_GetInvincibilityAccel(time)); + return 1; +} + static int lib_k3dKartMovement(lua_State *L) { player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); @@ -5302,6 +5353,8 @@ static luaL_Reg lib[] = { {"K_GetKartAccel",lib_kGetKartAccel}, {"K_GetKartFlashing",lib_kGetKartFlashing}, {"K_GetItemPatch",lib_kGetItemPatch}, + {"K_IsKartItemAlternate",lib_kIsKartItemAlternate}, + {"K_IsAltShrunk", lib_kIsAltShrunk}, {"K_SetRaceCountdown",lib_kSetRaceCountdown}, {"K_SetExitCountdown",lib_kSetExitCountdown}, {"K_SetIndirectItemCooldown",lib_kSetIndirectItemCountdown}, @@ -5326,6 +5379,9 @@ static luaL_Reg lib[] = { {"K_BoostChain",lib_kBoostChain}, {"K_ChainOrDeincrementTime",lib_kChainOrDeincrementTime}, {"K_GetNewSpeed", lib_kGetNewSpeed}, + {"K_InvincibilityGradient", lib_kInvincibilityGradient}, + {"K_GetInvincibilitySpeed", lib_kGetInvincibilitySpeed}, + {"K_GetInvincibilityAccel", lib_kGetInvincibilityAccel}, {"K_3dKartMovement", lib_k3dKartMovement}, {"K_MomentumAngle", lib_kMomentumAngle}, {"K_GetKartSpeedFromStat", lib_kGetKartSpeedFromStat}, From f7c1c474e6b7baaaecd74ace72f160f504dc93e5 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sun, 9 Nov 2025 21:42:47 +0100 Subject: [PATCH 41/76] Clean up and consolidate all item cooldown code * The "unique item" flag has moved to kartitem, and split to accomodate shields (item slot only, not paper items) * "BGoneTime" renamed to "CooldownTime" * indirectitemcooldown has been folded into the existing cooldown system, all results with the indirect flag share the highest cooldown time * Added cooldown debugger to kartdebugdistribution * Fixed Thunder Shield not resetting cooldown when killing an SPB --- src/d_main.cpp | 2 +- src/deh_soc.c | 4 +- src/deh_tables.c | 4 +- src/doomstat.h | 2 - src/g_game.c | 2 - src/k_collide.c | 6 ++ src/k_hud.c | 16 +++++ src/k_items.c | 167 ++++++++++++++++++++++++++-------------------- src/k_items.h | 36 +++++----- src/k_kart.c | 6 -- src/lua_baselib.c | 4 +- src/lua_script.c | 8 +-- src/p_mobj.c | 7 -- src/p_saveg.c | 7 +- src/p_setup.c | 6 +- src/p_tick.c | 16 +---- src/p_user.c | 2 +- 17 files changed, 151 insertions(+), 144 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 335a422e5..ec7028b6d 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -94,7 +94,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x5ed08ba7a6c6257f +#define ASSET_HASH_MAIN_PK3 0xfc28316dfbd899a0 #define ASSET_HASH_MAPPATCH_PK3 0xd4d4ce4a090d5473 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE diff --git a/src/deh_soc.c b/src/deh_soc.c index ba8703181..8869c8075 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -4209,9 +4209,9 @@ void readkartresult(MYFILE *f, kartresult_t *result) result->unique_odds[oddstable] = get_useoddsfunc(word2); } - else if (fastcmp(word, "BGONETIME") || fastcmp(word, "BASEBGONE")) + else if (fastcmp(word, "COOLDOWNTIME")) { - result->basebgone = atoi(word2) * TICRATE; + result->basecooldown = atoi(word2) * TICRATE; } else if (fastcmp(word, "DISPLAYNAME")) { diff --git a/src/deh_tables.c b/src/deh_tables.c index d55eb8ce3..f21e75111 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1560,6 +1560,9 @@ struct int_const_s const INT_CONST[] = { {"KITEMBLINKMODE_KARMA",KITEMBLINKMODE_KARMA}, // kartitemflags_e + {"KIF_UNIQUESLOT",KIF_UNIQUESLOT}, + {"KIF_UNIQUEDROP",KIF_UNIQUEDROP}, + {"KIF_UNIQUE",KIF_UNIQUE}, {"KIF_ANIMATED",KIF_ANIMATED}, {"KIF_DARKBG",KIF_DARKBG}, {"KIF_COLPATCH2PLAYER",KIF_COLPATCH2PLAYER}, @@ -1571,7 +1574,6 @@ struct int_const_s const INT_CONST[] = { {"KRF_COOLDOWNONSTART",KRF_COOLDOWNONSTART}, {"KRF_NOTNEAREND",KRF_NOTNEAREND}, {"KRF_NOTFORBOTTOM",KRF_NOTFORBOTTOM}, - {"KRF_UNIQUE",KRF_UNIQUE}, {"KRF_RUNNERAUGMENT",KRF_RUNNERAUGMENT}, // kartspinoutflags_t diff --git a/src/doomstat.h b/src/doomstat.h index 4c350d307..752350643 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -672,8 +672,6 @@ extern boolean comeback; extern SINT8 mostwanted; extern SINT8 battlewanted[4]; extern tic_t wantedcalcdelay; -extern tic_t indirectitemcooldown; -//extern tic_t hyubgone; extern tic_t mapreset; extern boolean thwompsactive; extern UINT8 lastLowestLap; diff --git a/src/g_game.c b/src/g_game.c index 5300fe241..6dac5c1ea 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -309,8 +309,6 @@ SINT8 pickedvote; // What vote the host rolls SINT8 battlewanted[4]; // WANTED players in battle, worth x2 points SINT8 mostwanted; // The "most wanted" (first in line) player. tic_t wantedcalcdelay; // Time before it recalculates WANTED -tic_t indirectitemcooldown; // Cooldown before any more Shrink, SPB, or any other item that works indirectly is awarded -// hyubgone was taken out back. See k_items.c tic_t mapreset; // Map reset delay when enough players have joined an empty game boolean thwompsactive; // Thwomps activate on lap 2 UINT8 lastLowestLap; // Last lowest lap, for activating race lap executors diff --git a/src/k_collide.c b/src/k_collide.c index 234d05528..b104ef38e 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -515,6 +515,12 @@ static inline BlockItReturn_t PIT_ThunderShieldAttack(mobj_t *thing) } #endif + if (thing->type == MT_SPB) // If you destroy a SPB, you don't get the luxury of a cooldown. + { + spbplace = -1; + K_SetIndirectItemCooldown(0); + } + P_DamageMobj(thing, lightningSource, lightningSource, 1, DMG_VOLTAGE|DMG_CANTHURTSELF); return BMIT_CONTINUE; } diff --git a/src/k_hud.c b/src/k_hud.c index 86cc65795..37af8e4d7 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5686,6 +5686,22 @@ static void K_drawDistributionDebugger(void) if (K_LegacyOddsMode()) V_DrawSmallString(70, 0, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, "Legacy Distance Mode"); + // cooldown timer debugging + x = 240; + y = 160; + for (i = 0; i < numkartresults; i++) + { + kartresult_t *result = &kartresults[i]; + if (result->cooldown > 0 && result->isalt == K_IsKartItemAlternate(result->type)) + { + INT32 color = result->flags & KRF_INDIRECTITEM ? V_ORANGEMAP : kartitems[result->type].flags & KIF_UNIQUE ? V_YELLOWMAP : 0; + V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOBOTTOM, K_GetCachedItemPatch(result->type, true, 0)); + if (result->amount > 1) + V_DrawThinString(x+30, y+30, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOBOTTOM|V_6WIDTHSPACE, va("x%d", result->amount)); + V_DrawString(x+11, y+31, V_HUDTRANS|V_SNAPTOBOTTOM|color, va("%u", result->cooldown/TICRATE)); + x -= 32; + } + } } static void K_drawCheckpointDebugger(void) diff --git a/src/k_items.c b/src/k_items.c index 7381bdfb3..9ed92c11b 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -306,14 +306,8 @@ static void K_AwardPlayerItem(player_t *player, kartresult_t *result) return; } - if (result->flags & KRF_INDIRECTITEM) // Indirect items - { - indirectitemcooldown = 20*TICRATE; - } - else if (result->basebgone > 0) // Item cooldowns - { - result->bgone = result->basebgone; - } + if (result->basecooldown > 0) + result->cooldown = result->basecooldown; player->itemtype = result->type; UINT8 itemamount = result->amount; @@ -441,39 +435,95 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist) #undef FRAC_3h -// Assigns general cooldowns to item results ith the KRF_UNIQUE flag. -void K_KartHandleUniqueCooldown(void) +// updates all result cooldown timers, and sets cooldowns for "unique" items +void K_UpdateItemCooldown(void) { - UINT8 i, j; + INT32 i; - UINT8 pingame = 0, pexiting = 0; + // tick down the cooldown timers + for (i = 0; i < numkartresults; i++) + if (kartresults[i].cooldown > 0) + kartresults[i].cooldown--; + // figure out which unique items need their cooldown set + boolean setcooldown[MAXKARTITEMS] = {0}; + + // start by checking each player's item slot (KIF_UNIQUESLOT) + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = &players[i]; + + if (!playeringame[i] || player->spectator || player->exiting > 0) + continue; + + if (gametyperules & GTR_BUMPERS && player->bumper == 0) + continue; + + // special case for legacy shrink + if (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)) + setcooldown[KITEM_SHRINK] = true; + + if (player->itemtype <= 0 || player->itemtype >= numkartitems || player->itemamount <= 0) + continue; + + if (kartitems[player->itemtype].flags & KIF_UNIQUESLOT) + setcooldown[player->itemtype] = true; + } + + // next, check for dropped items (KIF_UNIQUEDROP) + for (thinker_t *th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + mobj_t *mobj = (mobj_t *)th; + + if (P_MobjWasRemoved(mobj)) + continue; + + switch (mobj->type) + { + case MT_FLOATINGITEM: + if (mobj->threshold > 0 && mobj->threshold < numkartitems && kartitems[mobj->threshold].flags & KIF_UNIQUEDROP) + setcooldown[mobj->threshold] = true; + break; + + case MT_SPB: // SPBs in the field also count + setcooldown[KITEM_SPB] = true; + break; + + default: + break; + } + } + + // now it's FINALLY time to set cooldowns! + // unique item cooldowns only apply to race odds, unless it's an indirect item for (i = 0; i < numkartresults; i++) { - if (kartresults[i].flags & KRF_UNIQUE) - { - // TODO: bring this out - for (j = 0; j < MAXPLAYERS; j++) - { - if (!playeringame[j] || players[j].spectator) - continue; + kartresult_t *result = &kartresults[i]; + if ((gametyperules & GTR_RACEODDS || result->flags & KRF_INDIRECTITEM) && setcooldown[result->type]) + result->cooldown = result->basecooldown; + } - if (!(gametyperules & GTR_BUMPERS) || players[j].bumper) - pingame++; + // all indirect items share a single timer, whichever is higher + K_SetIndirectItemCooldown(K_GetIndirectItemCooldown()); +} - if (players[j].exiting) - pexiting++; +tic_t K_GetIndirectItemCooldown(void) +{ + tic_t maxcooldown = 0; + for (UINT8 i = 0; i < numkartresults; i++) + { + if (kartresults[i].flags & KRF_INDIRECTITEM) + maxcooldown = max(maxcooldown, kartresults[i].cooldown); + } + return maxcooldown; +} - if (players[j].itemtype == kartresults[i].type && - players[j].itemamount > 0 && kartresults[i].basebgone > 0) - { - // If this item has a cooldown, force-apply the cooldown - // preeemptively for the entire time the item is being held - // by a player. - kartresults[i].bgone = kartresults[i].basebgone; - } - } - } +void K_SetIndirectItemCooldown(tic_t cooldown) +{ + for (UINT8 i = 0; i < numkartresults; i++) + { + if (kartresults[i].flags & KRF_INDIRECTITEM) + kartresults[i].cooldown = cooldown; } } @@ -624,8 +674,6 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * case KITEM_SHRINK: if (!K_IsKartItemAlternate(KITEM_SHRINK)) { - flags |= KRF_INDIRECTITEM; - if (roulette->pingame-1 <= roulette->pexiting) newodds = 0; } @@ -676,14 +724,9 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * newodds *= multiplier; } - if (result->bgone > 0) + if (result->cooldown > 0) { - // (Replaces hyubgone) This item is on cooldown; don't let it get rolled. - newodds = 0; - } - else if (flags & KRF_INDIRECTITEM && indirectitemcooldown > 0) - { - // Too many items that act indirectly in a match can feel kind of bad. + // This item is on cooldown; don't let it get rolled. newodds = 0; } else if (flags & KRF_COOLDOWNONSTART && K_InStartCooldown()) @@ -701,28 +744,6 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * // This item should not appear for losing players. (Usually items that feel less effective at these positions) newodds = 0; } - else if (flags & KRF_UNIQUE) - { - // TODO: bring this out - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - - if (players[i].exiting) - continue; - - if ((gametyperules & GTR_BUMPERS) && !players[i].bumper) - continue; - - if (players[i].itemtype == result->type && players[i].itemamount > 0) - { - // Can only appear in one player's item slot, and can't be rolled if already held by a player - newodds = 0; - break; - } - } - } else if (flags & KRF_POWERITEM) { // This item is a "power item". This activates "frantic item" toggle related functionality. @@ -770,11 +791,9 @@ void K_KartGetItemOdds(kartroulette_t *roulette, INT32 outodds[static MAXKARTRES UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) { fixed_t oddsfac = max(FRACUNIT, (MAXODDS * FRACUNIT) / 8); - INT32 oddsdiv = ((MAXODDS - 1) * 2); - UINT8 i, j; UINT8 useodds = 0; - UINT8 disttable[oddsdiv]; + UINT8 disttable[(MAXODDS - 1) * 2]; UINT8 distlen = 0; boolean oddsvalid[MAXODDS]; boolean rivalodds = false; @@ -832,7 +851,7 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b for (i = num; i; --i) \ { \ disttable[distlen++] = odds; \ - distlen = min(oddsdiv - 1, distlen); \ + distlen = min(sizeof(disttable) - 1, distlen); \ } if (gametyperules & GTR_BATTLEODDS) // Battle Mode @@ -896,13 +915,13 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b if (pdis == 0) useodds = disttable[0]; - else if (pdis > (UINT32)ACTIVEDISTVAR * ((12 * distlen) / oddsdiv)) - useodds = disttable[distlen-1]; + else if (pdis > (UINT32)ACTIVEDISTVAR * ((12 * distlen) / sizeof(disttable))) + useodds = disttable[max(0, distlen-1)]; else { - for (i = 1; i < (oddsdiv - 1); i++) + for (i = 1; i < (sizeof(disttable) - 1); i++) { - INT32 distcalc = min(distlen-1, (i * distlen) / oddsdiv); + INT32 distcalc = min(distlen-1, (i * distlen) / sizeof(disttable)); if (pdis <= (UINT32)usedistvar * distcalc) { @@ -1366,7 +1385,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // SPECIAL CASE No. 6: // In battle, an SPB is forced onto players to target the "most wanted" player if (K_BattleForceSPB(player) - && spbplace == -1 && !indirectitemcooldown && !dontforcespb + && spbplace == -1 && K_GetKartResult("spb")->cooldown == 0 && !dontforcespb && K_ItemResultEnabled(K_GetKartResult("spb"))) { K_AwardPlayerItem(player, K_GetKartResult("spb")); @@ -1500,7 +1519,7 @@ INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, const kartresul *forceme = 0; } else if (K_RaceForceSPB(roulette->playerpos, roulette->pdis) - && spbplace == -1 && !indirectitemcooldown) + && spbplace == -1 && K_GetKartResult("spb")->cooldown == 0) { // Force SPB onto 2nd if they get too far behind. *forceme = 2; diff --git a/src/k_items.h b/src/k_items.h index 04d6c9e21..330916113 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -52,26 +52,28 @@ typedef enum MAXODDSTABLES } ATTRPACK kartoddstable_e; -// item flags for HUD and usage +// item flags for usage and HUD typedef enum { - KIF_ANIMATED = 1<<0, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT) - KIF_DARKBG = 1<<1, // use dark item roulette BG - KIF_COLPATCH2PLAYER = 1<<2, // colourize patch to player - KIF_HIDEFROMROULETTE = 1<<3, // don't show this item in the roulette (inversion of xItem's showInRoulette item flag) + KIF_UNIQUESLOT = 1<<0, // Prevents the item from being rolled if in a player's item slot + KIF_UNIQUEDROP = 1<<1, // Prevents the item from being rolled if it exists as a dropped item + KIF_UNIQUE = KIF_UNIQUESLOT|KIF_UNIQUEDROP, + + KIF_ANIMATED = 1<<2, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT) + KIF_DARKBG = 1<<3, // use dark item roulette BG + KIF_COLPATCH2PLAYER = 1<<4, // colourize patch to player + KIF_HIDEFROMROULETTE = 1<<5, // don't show this item in the roulette (inversion of xItem's showInRoulette item flag) } ATTRPACK kartitemflags_e; -// flags relevant to item rolls and usage +// result flags relevant to the roulette typedef enum { - KRF_INDIRECTITEM = 1<<0, // This item affects others indirectly. A 20 second cooldown is applied to all other items with this flag when an indirect item is rolled. - KRF_POWERITEM = 1<<1, // This item is a "power item". This activates "frantic item" toggle related functionality. - KRF_COOLDOWNONSTART = 1<<2, // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) - KRF_NOTNEAREND = 1<<3, // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) - KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this item stops appearing for losing players. (Usually items that feel less effective at these positions) - KRF_UNIQUE = 1<<5, // If already in a player's item slot, prevents all other players from obtaining this item from item boxes - KRF_RUNNERAUGMENT = 1<<6, // This item's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) - // free: 1<<7 + KRF_INDIRECTITEM = 1<<0, // This result's item affects others indirectly. All results with this flag share a single cooldown timer. + KRF_POWERITEM = 1<<1, // This result is a "power item". This activates "frantic item" toggle related functionality. + KRF_COOLDOWNONSTART = 1<<2, // This result should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + KRF_NOTNEAREND = 1<<3, // This result should not appear at the end of a race. (Usually trap items that lose their effectiveness) + KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this result stops appearing for losing players. (Usually items that feel less effective at these positions) + KRF_RUNNERAUGMENT = 1<<5, // This result's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) } ATTRPACK kartresultflags_e; // Unique useodds function @@ -117,7 +119,7 @@ struct kartresult_t // If NULL, the functions never execute. useoddsfunc_f *unique_odds[MAXODDSTABLES]; - tic_t basebgone, bgone; + tic_t basecooldown, cooldown; }; // contains all data for a call to K_KartGetItemOdds @@ -159,7 +161,9 @@ boolean K_IsKartItemAlternate(kartitemtype_e itemtype); UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny); void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount); -void K_KartHandleUniqueCooldown(void); +void K_UpdateItemCooldown(void); +tic_t K_GetIndirectItemCooldown(void); +void K_SetIndirectItemCooldown(tic_t cooldown); boolean K_LegacyOddsMode(void); UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos); diff --git a/src/k_kart.c b/src/k_kart.c index 5508f9fbf..d4583f3aa 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -89,7 +89,6 @@ consvar_t cv_karthitemdialog = CVAR_INIT ("karthitemdialog", "On", CV_SAVE, CV_O // encoremode is Encore Mode (duh), bool // comeback is Battle Mode's karma comeback, also bool // battlewanted is an array of the WANTED player nums, -1 for no player in that slot -// indirectitemcooldown is timer before anyone's allowed another Shrink/SPB // mapreset is set when enough players fill an empty server // Cluster point. @@ -10223,11 +10222,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->growshrinktimer <= 0) player->growcancel = -1; - if (player->itemtype == KITEM_SPB - || player->itemtype == KITEM_SHRINK - || (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK))) - indirectitemcooldown = 20*TICRATE; - if (player->hyudorotimer > 0) { INT32 hyu = hyudorotime; diff --git a/src/lua_baselib.c b/src/lua_baselib.c index c46dbfdfe..d52a6bd34 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -4136,7 +4136,7 @@ static int lib_kSetIndirectItemCountdown(lua_State *L) { tic_t c = (tic_t)luaL_checkinteger(L, 1); NOHUD - indirectitemcooldown = c; + K_SetIndirectItemCooldown(c); return 0; } @@ -4145,7 +4145,7 @@ static int lib_kSetHyuCountdown(lua_State *L) { tic_t c = (tic_t)luaL_checkinteger(L, 1); NOHUD - K_GetKartResult("hyudoro")->bgone = c; + K_GetKartResult("hyudoro")->cooldown = c; return 0; } diff --git a/src/lua_script.c b/src/lua_script.c index 3a63dbe32..6175350e4 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -418,7 +418,7 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushboolean(L, itempushing); // hmm... i think this should be a boolean return 1; } else if (fastcmp(word,"hyubgone")) { - lua_pushinteger(L, K_GetKartResult("hyudoro")->bgone); + lua_pushinteger(L, K_GetKartResult("hyudoro")->cooldown); return 1; } else if (fastcmp(word,"encoremode")) { lua_pushboolean(L, encoremode); @@ -433,7 +433,7 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushinteger(L, wantedcalcdelay); return 1; } else if (fastcmp(word,"indirectitemcooldown")) { - lua_pushinteger(L, indirectitemcooldown); + lua_pushinteger(L, K_GetIndirectItemCooldown()); return 1; } else if (fastcmp(word,"thwompsactive")) { lua_pushboolean(L, thwompsactive); @@ -559,9 +559,9 @@ int LUA_WriteGlobals(lua_State *L, const char *word) else if (fastcmp(word,"exitcountdown")) exitcountdown = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"indirectitemcooldown")) - indirectitemcooldown = (tic_t)luaL_checkinteger(L, 2); + K_SetIndirectItemCooldown(luaL_checkinteger(L, 2)); else if (fastcmp(word,"hyubgone")) - K_GetKartResult("hyudoro")->bgone = (tic_t)luaL_checkinteger(L, 2); + K_GetKartResult("hyudoro")->cooldown = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"starttime")) starttime = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"introtime")) diff --git a/src/p_mobj.c b/src/p_mobj.c index 9bff50a2c..c668cbc5b 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -8410,11 +8410,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } - if (mobj->threshold == KITEM_SPB || mobj->threshold == KITEM_SHRINK) - { - indirectitemcooldown = 20*TICRATE; - } - K_UpdateMobjItemOverlay(mobj, mobj->threshold, mobj->movecount); break; } @@ -8683,8 +8678,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->threshold--; break; case MT_SPB: - indirectitemcooldown = 20*TICRATE; - /* FALLTHRU */ case MT_BALLHOG: { mobj_t *ghost = P_SpawnGhostMobj(mobj); diff --git a/src/p_saveg.c b/src/p_saveg.c index 523ec68b1..ddc404ebd 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4266,14 +4266,9 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNC(battlewanted[i]); SYNC(wantedcalcdelay); - SYNC(indirectitemcooldown); for (i = 0; i < numkartresults; i++) - { - // hyubgone - //SYNC(kartresults[i].basebgone); - SYNC(kartresults[i].bgone); - } + SYNC(kartresults[i].cooldown); SYNC(mapreset); diff --git a/src/p_setup.c b/src/p_setup.c index 4fea07c97..1f2477062 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8400,13 +8400,9 @@ static void P_InitGametype(void) numlaps = K_RaceLapCount(gamemap - 1); wantedcalcdelay = wantedfrequency*2; - indirectitemcooldown = 0; for (i = 0; i < numkartresults; i++) - { - // hyubgone - kartresults[i].bgone = 0; - } + kartresults[i].cooldown = 0; mapreset = 0; diff --git a/src/p_tick.c b/src/p_tick.c index a94863a8d..503689286 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -897,21 +897,7 @@ void P_Ticker(boolean run) if (exitcountdown > 1) exitcountdown--; - if (indirectitemcooldown > 0) - indirectitemcooldown--; - - for (i = 0; i < numkartresults; i++) - { - // hyubgone - if (kartresults[i].bgone > 0) - kartresults[i].bgone--; - } - - if (gametyperules & GTR_RACEODDS) - { - // Unique item cooldowns - K_KartHandleUniqueCooldown(); - } + K_UpdateItemCooldown(); K_BossInfoTicker(); diff --git a/src/p_user.c b/src/p_user.c index 1aac175f2..b3e723dc3 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2495,7 +2495,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius) if (mo->type == MT_SPB) // If you destroy a SPB, you don't get the luxury of a cooldown. { spbplace = -1; - indirectitemcooldown = 0; + K_SetIndirectItemCooldown(0); } if (mo->flags & MF_BOSS) //don't OHKO bosses nor players! From 48dce534e293368387ac22d062050c3175832d17 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Wed, 12 Nov 2025 00:38:41 -0500 Subject: [PATCH 42/76] implement item usage cooldown (xItemLib feature) mainly to by used by lua scripts --- src/d_player.h | 36 +++++++++++++++++++----------------- src/k_hud.c | 34 +++++++++++++++++++++++++++++++++- src/k_items.c | 12 +++++++++++- src/k_items.h | 2 ++ src/k_kart.c | 12 +++++++++++- src/lua_baselib.c | 13 +++++++++++++ src/lua_playerlib.c | 16 ++++++++++++++++ 7 files changed, 105 insertions(+), 20 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 03bf29d39..51c46eea6 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -58,14 +58,6 @@ typedef enum PST_REBORN } playerstate_t; -typedef enum -{ - IF_USERINGS = 1, // Have to be not holding the item button to change from using rings to using items (or vice versa) - prevents weirdness - IF_ITEMOUT = 1<<1, // Are you holding an item out? - IF_EGGMANOUT = 1<<2, // Eggman mark held, separate from IF_ITEMOUT so it doesn't stop you from getting items - IF_HOLDREADY = 1<<3, // Hold button-style item is ready to activate -} itemflags_t; - // // Player internal flags // @@ -137,6 +129,14 @@ typedef enum CR_ZOOMTUBE, } carrytype_t; // carry +typedef enum +{ + IF_USERINGS = 1, // Have to be not holding the item button to change from using rings to using items (or vice versa) - prevents weirdness + IF_ITEMOUT = 1<<1, // Are you holding an item out? + IF_EGGMANOUT = 1<<2, // Eggman mark held, separate from IF_ITEMOUT so it doesn't stop you from getting items + IF_HOLDREADY = 1<<3, // Hold button-style item is ready to activate +} itemflags_t; + typedef enum { KROULETTE_DISABLED, KROULETTE_ACTIVE, @@ -611,20 +611,24 @@ struct player_t UINT16 draftleeway; // Leniency timer before removing draft power SINT8 lastdraft; // (-1 to 15) - Last player being drafted - UINT8 tripwireState; // see tripwirestate_t - UINT8 tripwirePass; // see tripwirepass_t + UINT8 tripwireState; // see tripwirestate_t + UINT8 tripwirePass; // see tripwirepass_t UINT16 tripwireLeniency; // When reaching a state that lets you go thru tripwire, you get an extra second leniency after it ends to still go through it. UINT8 tripwireReboundDelay; // When failing Tripwire, brieftly lock out speed-based tripwire pass (anti-cheese) - UINT16 itemroulette; // Used for the roulette when deciding what item to give you (was "pw_kartitem") - UINT16 previtemroulette; // Used determining when to give rings after hitting item boxes + UINT16 itemroulette; // Used for the roulette when deciding what item to give you (was "pw_kartitem") + UINT16 previtemroulette; // Used determining when to give rings after hitting item boxes UINT16 itemblink; // Item flashing after roulette, serves as a mashing indicator. Also prevents item from being stolen. UINT16 itemblinkmode; // Type of flashing: 0 = white (normal), 1 = red (mashing), 2 = rainbow (enhanced items) - UINT8 roulettetype; // Used for the roulette, for deciding type (0 = normal, 1 = better, 2 = eggman mark) + UINT8 roulettetype; // Used for the roulette, for deciding type (0 = normal, 1 = better, 2 = eggman mark) // Item held stuff - SINT8 itemtype; // KITEM_ constant for item number - UINT8 itemamount; // Amount of said item + SINT8 itemtype; // KITEM_ constant for item number + UINT8 itemamount; // Amount of said item + itemflags_t itemflags; // holds IF_flags (see itemstate_t) + tic_t itemusecooldown; // timer for which the player is locked out of using an item or mashing the item roulette (implementation of xItemLib setPlayerItemCooldown) + tic_t itemusecooldownmax; // most recent highest value for itemusecooldown, mainly used for HUD rendering currently + SINT8 throwdir; // Held dir of controls; 1 = forward, 0 = none, -1 = backward (was "player->heldDir") UINT8 sadtimer; // How long you've been sad @@ -788,8 +792,6 @@ struct player_t UINT8 typing_duration; // How long since resumed timer UINT8 kickstartaccel; - - UINT8 itemflags; // holds IF_flags (see itemstate_t) fixed_t outrun; // Milky Way road effect UINT8 outruntime; // Used to bypass the speed cap for fall off diff --git a/src/k_hud.c b/src/k_hud.c index 37af8e4d7..f95d39fb1 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1182,6 +1182,32 @@ static void K_DrawItemBar(INT32 fx, INT32 fy, INT32 fflags, fixed_t itembar, UIN V_DrawFixedFill(x+2*FRACUNIT, y+FRACUNIT, max(0, length - 2*FRACUNIT), FRACUNIT, colors[0]|fflags); // the shine } +// TODO: use an actual patch overlay and clip it instead of using a rect, now that an actual patch can be added for this +static void K_DrawItemCooldown(INT32 fx, INT32 fy, INT32 fflags, tic_t timer, tic_t maxtimer) +{ + const boolean fourp = r_splitscreen > 1; + const INT32 rectTopFour = 13*FRACUNIT; + INT32 rectTop = 10*FRACUNIT; + INT32 rectSize = 40*FRACUNIT; + fixed_t prog = FixedDiv(timer, maxtimer); + INT32 length = min(rectSize, FixedMul(rectSize, prog)); + if (timer > 0 && maxtimer > 0) + { + if (fourp) + { + rectTop = 14*FRACUNIT; + rectSize = 20*FRACUNIT; + length = min(rectSize, FixedMul(rectSize, prog)); + + V_DrawFixedFill(fx + rectTop, fy + rectTopFour + (rectSize - length), rectSize, length, 2|fflags); + } + else + { + V_DrawFixedFill(fx + rectTop, fy + rectTop + (rectSize - length), rectSize, length, 2|fflags); + } + } +} + // see also MT_PLAYERARROW mobjthinker in p_mobj.c static void K_drawKartItem(void) { @@ -1309,7 +1335,7 @@ static void K_drawKartItem(void) } else { - if (stplyr->itemamount <= 0) + if (stplyr->itemamount <= 0 && stplyr->itemusecooldown <= 0) return; localpatch = K_GetCachedItemPatch(stplyr->itemtype, tiny, stplyr->itemamount); @@ -1393,6 +1419,9 @@ static void K_drawKartItem(void) V_DrawMappedPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), localbg, colormap); // flip this graphic for p2 and p4 in split and shift it. V_DrawFixedPatch(fx<itemusecooldown, stplyr->itemusecooldownmax); + if (isalt) { V_DrawFixedPatch(fx<itemusecooldown, stplyr->itemusecooldownmax); // TODO: Some proper check for if an alt has unique graphics if (isalt) diff --git a/src/k_items.c b/src/k_items.c index 9ed92c11b..f59896ab9 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -1253,7 +1253,8 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // Finally, if you get past this check, now you can actually start calculating what item you get. if ((cmd->buttons & BT_ATTACK) && (player->itemroulette >= roulettestop) && (K_RingsActive() || (modeattacking == ATTACKING_NONE)) - && !(player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT|IF_USERINGS))) + && !(player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT|IF_USERINGS)) + && !(player->itemusecooldown)) { // Mashing reduces your chances for the good items mashed = FixedDiv((player->itemroulette)*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT; @@ -1477,6 +1478,15 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) player->roulettetype = KROULETTETYPE_NORMAL; // This too } +void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force) +{ + if ((timer >= player->itemusecooldown) || force) + { + player->itemusecooldown = timer; + player->itemusecooldownmax = max(timer, player->itemusecooldownmax); + } +} + // Unique odds functions, for REAL this time // Alt. Invin. odds diff --git a/src/k_items.h b/src/k_items.h index 330916113..18e4335b9 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -174,6 +174,8 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); INT32 K_GetRollingRouletteItem(player_t *player); void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); +void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force); + useoddsfunc_f KO_AltInvinOdds; useoddsfunc_f KO_SPBRaceOdds; diff --git a/src/k_kart.c b/src/k_kart.c index d4583f3aa..f2028b25a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6953,6 +6953,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->sneakertimer) player->sneakertimer = K_ChainOrDeincrementTime(player, player->sneakertimer, 1, false); + if (player->itemusecooldown) + { + player->itemusecooldown--; + if (player->itemusecooldown <= 0) + { + player->itemusecooldownmax = 0; + } + } + if (player->sneakertimer <= 0) { player->mo->flags2 &= ~MF2_WATERRUN; @@ -10040,7 +10049,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } } // Other items - else + // TODO: how should player->itemusecooldown interact with LUA_HookPlayerItem? + else if (player->itemusecooldown == 0) { // Eggman Monitor exploding if (player->eggmanexplode) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index d52a6bd34..864d7c938 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -5069,6 +5069,16 @@ static int lib_kTerrainHasAffect(lua_State *L) return 1; } +static int lib_kSetPlayerItemCooldown(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + tic_t c = (tic_t)luaL_checkinteger(L, 2); + boolean force = lua_optboolean(L, 3); + if (!player) + return LUA_ErrInvalid(L, "player_t"); + K_SetPlayerItemCooldown(player, c, force); +} + static luaL_Reg lib[] = { {"print", lib_print}, {"chatprint", lib_chatprint}, @@ -5452,6 +5462,9 @@ static luaL_Reg lib[] = { {"K_HandleFootstepParticles", lib_kHandleFootstepParticles}, {"K_UpdateTerrainOverlay", lib_kUpdateTerrainOverlay}, {"K_TerrainHasAffect", lib_kTerrainHasAffect}, + + // k_items + {"K_SetPlayerItemCooldown", lib_kSetPlayerItemCooldown}, {NULL, NULL} }; diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index bd35f12e6..48ec14afd 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -249,6 +249,8 @@ enum player_e player_roulettetype, player_itemtype, player_itemamount, + player_itemusecooldown, + player_itemusecooldownmax, player_throwdir, player_sadtimer, player_rings, @@ -453,6 +455,8 @@ static const char *const player_opt[] = { "roulettetype", "itemtype", "itemamount", + "itemusecooldown", + "itemusecooldownmax", "throwdir", "sadtimer", "rings", @@ -855,6 +859,12 @@ static int player_get(lua_State *L) case player_itemamount: lua_pushinteger(L, plr->itemamount); break; + case player_itemusecooldown: + lua_pushinteger(L, plr->itemusecooldown); + break; + case player_itemusecooldownmax: + lua_pushinteger(L, plr->itemusecooldownmax); + break; case player_throwdir: lua_pushinteger(L, plr->throwdir); break; @@ -1568,6 +1578,12 @@ static int player_set(lua_State *L) case player_itemamount: plr->itemamount = luaL_checkinteger(L, 3); break; + case player_itemusecooldown: + plr->itemusecooldown = luaL_checkinteger(L, 3); + break; + case player_itemusecooldownmax: + plr->itemusecooldownmax = luaL_checkinteger(L, 3); + break; case player_throwdir: plr->throwdir = luaL_checkinteger(L, 3); break; From 40aa53422167d3c8c533fbd4860593c24e72e317 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Tue, 11 Nov 2025 02:38:05 -0500 Subject: [PATCH 43/76] At least explain why WADNAME warps shit themselves --- src/d_main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index ec7028b6d..01e83b0b7 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1860,7 +1860,7 @@ void D_SRB2Main(void) if (WADNAMECHECK(word)) { if (!(pstartmap = wadnamemap)) - I_Error("Bad '%s' level warp.\n" + I_Error("Bad '%s' level warp; pstartmap (%d) != wadnamemap (%d)).\n" #if defined (_WIN32) "Are you using MSDOS 8.3 filenames in Zone Builder?\n" "\n" @@ -1868,7 +1868,7 @@ void D_SRB2Main(void) "Go to the Testing tab and make sure \"Use short paths and file names\" is turned off.\n" "(The option is hidden by default. Check \"Customize parameters\" to show it.)\n" #endif - , word); + , word, pstartmap, wadnamemap); } else { From cfef019cfb6aca106c7058001d2b81592948b702 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 12 Nov 2025 03:20:17 -0500 Subject: [PATCH 44/76] e --- src/f_dscredits.cpp | 2 +- src/f_dscredits.hpp | 2 +- src/f_finale.c | 12 ++++++------ src/k_items.c | 2 +- src/k_items.h | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/f_dscredits.cpp b/src/f_dscredits.cpp index c079d2785..a62ce9d72 100644 --- a/src/f_dscredits.cpp +++ b/src/f_dscredits.cpp @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 2025 by Vivian "toastergrl" Grannell. // Copyright (C) 2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 by "yama". // Copyright (C) 2025 Blankart Team. // // This program is free software distributed under the diff --git a/src/f_dscredits.hpp b/src/f_dscredits.hpp index 562fbeccc..d2d55c000 100644 --- a/src/f_dscredits.hpp +++ b/src/f_dscredits.hpp @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 2025 by Vivian "toastergrl" Grannell. // Copyright (C) 2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 by "yama". // Copyright (C) 2025 Blankart Team. // // This program is free software distributed under the diff --git a/src/f_finale.c b/src/f_finale.c index 6fddafb38..fb673c7aa 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -890,7 +890,7 @@ const char *blancredits[] = { "\"GenericHeroGuy\"", "\"Alug\"", "\"Indev\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "\"minenice\"", "", "\1Support Programming", @@ -899,7 +899,7 @@ const char *blancredits[] = { "", "\1Item Programming", "\"NepDisk\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "", "\1External Programming", "\"Hanicef\"", @@ -922,7 +922,7 @@ const char *blancredits[] = { "", "\1Item Design", "\"NepDisk\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "\"GenericHeroGuy\"", "", "\1Design Support", @@ -937,7 +937,7 @@ const char *blancredits[] = { "\"Jin\"", "\"NepDisk\"", "\"Mayo\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "", "\1New HUD Art", "\"Spee\"", @@ -966,7 +966,7 @@ const char *blancredits[] = { "\1Super Special SIGSEGV Search and Smash Squad", "\"NepDisk\"", "\"GenericHeroGuy\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "\"MotoBugger\"", "\"Rim Jobless\"", "\"Toad The Fungus\"", @@ -982,7 +982,7 @@ const char *blancredits[] = { "\"JonUD\"", "\"MotoBugger\"", "\"Toad The Fungus\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "\"luna\"", "\"swift\"", "\"Rim Jobless\"", diff --git a/src/k_items.c b/src/k_items.c index f59896ab9..b2da75a72 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -1,7 +1,7 @@ // BLANKART //----------------------------------------------------------------------------- // Copyright (C) 2018-2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 by "yama". // Copyright (C) 2025 Blankart Team. // // This program is free software distributed under the diff --git a/src/k_items.h b/src/k_items.h index 18e4335b9..74cc589e5 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -1,7 +1,7 @@ // BLANKART //----------------------------------------------------------------------------- // Copyright (C) 2018-2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 by "yama". // Copyright (C) 2025 Blankart Team. // // This program is free software distributed under the From ad1b01d97b323b25619afdd890a48aa7561185fe Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 12 Nov 2025 03:24:49 -0500 Subject: [PATCH 45/76] Get rid of the extra parenthesis in the WADNAME error message --- src/d_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 01e83b0b7..f7ef6ae82 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1860,7 +1860,7 @@ void D_SRB2Main(void) if (WADNAMECHECK(word)) { if (!(pstartmap = wadnamemap)) - I_Error("Bad '%s' level warp; pstartmap (%d) != wadnamemap (%d)).\n" + I_Error("Bad '%s' level warp; pstartmap (%d) != wadnamemap (%d).\n" #if defined (_WIN32) "Are you using MSDOS 8.3 filenames in Zone Builder?\n" "\n" From ef6958a5782a2c7ea9860828f3996465935f4252 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 12 Nov 2025 07:13:40 -0500 Subject: [PATCH 46/76] KRF_HIDEFROMSPB This isn't a thing in v1 and I'm not sure why it was locked to the Thunder Shield for so long --- src/deh_tables.c | 1 + src/k_items.c | 9 +++++---- src/k_items.h | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index f21e75111..422e60be9 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1575,6 +1575,7 @@ struct int_const_s const INT_CONST[] = { {"KRF_NOTNEAREND",KRF_NOTNEAREND}, {"KRF_NOTFORBOTTOM",KRF_NOTFORBOTTOM}, {"KRF_RUNNERAUGMENT",KRF_RUNNERAUGMENT}, + {"KRF_HIDEFROMSPB",KRF_HIDEFROMSPB}, // kartspinoutflags_t {"KSPIN_THRUST",KSPIN_THRUST}, diff --git a/src/k_items.c b/src/k_items.c index b2da75a72..d9a336947 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -686,10 +686,6 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * } } break; - case KITEM_THUNDERSHIELD: - if (spbplace != -1) - newodds = 0; - break; default: break; } @@ -744,6 +740,11 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * // This item should not appear for losing players. (Usually items that feel less effective at these positions) newodds = 0; } + else if (flags & KRF_HIDEFROMSPB && (spbplace != -1)) + { + // This item doesn't appear if an SPB is chasing a player. + newodds = 0; + } else if (flags & KRF_POWERITEM) { // This item is a "power item". This activates "frantic item" toggle related functionality. diff --git a/src/k_items.h b/src/k_items.h index 74cc589e5..eb817d6ff 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -74,6 +74,7 @@ typedef enum KRF_NOTNEAREND = 1<<3, // This result should not appear at the end of a race. (Usually trap items that lose their effectiveness) KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this result stops appearing for losing players. (Usually items that feel less effective at these positions) KRF_RUNNERAUGMENT = 1<<5, // This result's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) + KRF_HIDEFROMSPB = 1<<6, // This result refuses to appear when a Self-Propelled Bomb is active in a race or battle. } ATTRPACK kartresultflags_e; // Unique useodds function From 2122da0933b141d7b0317f4d4ce2f5d1a28c2c97 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 12 Nov 2025 07:17:54 -0500 Subject: [PATCH 47/76] SPB Rush toggle --- src/d_netcmd.c | 3 +++ src/d_netcmd.h | 2 ++ src/k_items.c | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 3a35645b3..1899127bc 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -572,6 +572,9 @@ consvar_t cv_kartspbdist = CVAR_INIT ("kartspbdist", "4432", CV_NETVAR|CV_CHEAT| consvar_t cv_kartlegacyspbdist = CVAR_INIT ("kartlegacyspbdist", "2216", CV_NETVAR|CV_CHEAT|CV_GUARD, spbdist_cons_t, NULL); #undef MAXODDSDIST +// SPB Rush toggle; toggles on/off the more aggressive "pressure" odds when an SPB is active +consvar_t cv_kartspbrush = CVAR_INIT ("kartspbrush", "On", CV_NETVAR, CV_OnOff, NULL); + // Time limit for Alt. Shrink static CV_PossibleValue_t altshrinktime_cons_t[] = {{0, "MIN"}, {(INT16_MAX / TICRATE), "MAX"}, {0, NULL}}; consvar_t cv_kartaltshrinktime = CVAR_INIT ("kartaltshrinktime", "14", CV_NETVAR|CV_CHEAT|CV_GUARD, altshrinktime_cons_t, NULL); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 6fa9585d6..3aa6c8495 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -202,6 +202,8 @@ extern consvar_t cv_kartlegacyoddsdist; extern consvar_t cv_kartspbdist; extern consvar_t cv_kartlegacyspbdist; +extern consvar_t cv_kartspbrush; + extern consvar_t cv_encorevotes; extern consvar_t cv_votetime; diff --git a/src/k_items.c b/src/k_items.c index d9a336947..a541bc611 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -1171,7 +1171,7 @@ UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrus pdis = K_CalculateInitalPDIS(player, numPlayers); - if (spbplace != -1 && player->position == spbplace+1) + if (cv_kartspbrush.value && spbplace != -1 && player->position == spbplace+1) { // SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell pdis = (3 * pdis) / 2; From 8e17e5e5ccac7c8b29da48a419aaae40bbae4193 Mon Sep 17 00:00:00 2001 From: Anonimus Date: Wed, 12 Nov 2025 07:18:56 -0500 Subject: [PATCH 48/76] Odds exaggeration is a legacy bandaid --- src/k_items.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/k_items.c b/src/k_items.c index a541bc611..33d56ed44 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -1155,10 +1155,10 @@ UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame) else { pdis = K_GetCongaLineDistance(player, 1); - } - // Exaggerate odds; don't you love the legacy system? :Trollic: - pdis = FixedMul(pdis, LEGACYODDSEXAGGERATE); + // Exaggerate odds; don't you love the legacy system? :Trollic: + pdis = FixedMul(pdis, LEGACYODDSEXAGGERATE); + } return pdis; } From 49ada92085c3c8d3250661ab0a8ed91ae778d725 Mon Sep 17 00:00:00 2001 From: yamamama Date: Wed, 12 Nov 2025 07:25:38 -0500 Subject: [PATCH 49/76] Brainfart --- src/k_kart.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index f2028b25a..69f58771a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -404,6 +404,8 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartspbdist); CV_RegisterVar(&cv_kartlegacyspbdist); + CV_RegisterVar(&cv_kartspbrush); + CV_RegisterVar(&cv_kartdriftsounds); CV_RegisterVar(&cv_kartdriftefx); CV_RegisterVar(&cv_kartsplashefx); From 7b2fbfac4e38cfc8c7debcba8b4021da2501d6dc Mon Sep 17 00:00:00 2001 From: minenice55 Date: Wed, 12 Nov 2025 09:07:55 -0500 Subject: [PATCH 50/76] add new values to save archival --- src/p_saveg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/p_saveg.c b/src/p_saveg.c index ddc404ebd..4991aa6da 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -776,6 +776,9 @@ static void P_NetSyncPlayers(savebuffer_t *save) // Wall Transfer bonus SYNCBOOLEAN(players[i].walltransfered); SYNC(players[i].walltransferboost); + + SYNC(players[i].itemusecooldown); + SYNC(players[i].itemusecooldownmax); } TracyCZoneEnd(__zone); } From 474924a5074b71e6b48df7b5258d2b96c971999e Mon Sep 17 00:00:00 2001 From: minenice55 Date: Wed, 12 Nov 2025 09:50:02 -0500 Subject: [PATCH 51/76] let bots check item cooldowns also allow smarter bots to burn flame shield fuel to go thru offroad a bit faster --- src/k_botitem.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index 0e9df0107..21b9ca3d1 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -305,6 +305,14 @@ static void K_ItemConfirmForTarget(botdata_t *bd, const player_t *bot, const pla } } +/// @brief checks if given bot isn't prevented from using items due to item cooldowns +/// @param bot bot to check cooldowns for +/// @return if bot is able to use item-related actions (using items or mashing the roulette) +static boolean K_CheckBotItemCooldown(const player_t *bot) +{ + return bot->itemusecooldown <= 0; +} + // Presses the item button & aim buttons for the bot. // dir - Aiming direction: 1 for forwards, -1 for backwards, 0 for neutral. @@ -958,7 +966,8 @@ static void K_BotItemFlame(botdata_t *bd, const player_t *player) { ZoneScoped; - if (player->flametimer >= (itemtime*3)-5) + // if we're smart enough, allow overheating the flame shield if we want to go off-road + if (player->flametimer >= (itemtime*3)-5 && !(K_ApplyOffroad(player) && player->botvars.difficulty >= 6)) { bd->itemdelay = 5; } @@ -1201,7 +1210,7 @@ void K_BotItemUsage(botdata_t *bd, const player_t *player) } return; } - + if (K_CanHandleItemDelay(bd, player)) { bd->itemdelay--; @@ -1209,6 +1218,8 @@ void K_BotItemUsage(botdata_t *bd, const player_t *player) return; } + if (!K_CheckBotItemCooldown(player)) return; + if (player->itemroulette) { // Mashing behaviors From d2e80f946261bfba268034d3670ee0f9b22876a2 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Wed, 12 Nov 2025 10:08:28 -0500 Subject: [PATCH 52/76] add item use cooldown to hud timers --- src/h_timers.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/h_timers.cpp b/src/h_timers.cpp index 599f2f499..492edc9ee 100644 --- a/src/h_timers.cpp +++ b/src/h_timers.cpp @@ -336,6 +336,16 @@ void K_DisplayItemTimers(void) true, } ); + timers.push_back( + { // item cooldown + "itemusecooldown", + (INT32)stplyr->itemusecooldown, + //{qche("K_ISFLMS")}, + {qche("SERVLOCK")}, + 1, /*-15, -15,*/ -4, -2, + false, false + } + ); /*timers.push_back( { // dead From 15c64731e489b625d243795f8f4e80712e93d25f Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Wed, 12 Nov 2025 22:06:05 +0100 Subject: [PATCH 53/76] Make kartitemtype_e unsigned, replace KITEM_SAD with MAXKARTITEMS Plus some misc fixes/changes (per-map initialization in one place) --- src/d_main.cpp | 2 +- src/d_netcmd.c | 4 ++-- src/d_player.h | 2 +- src/deh_lua.c | 5 +++++ src/dehacked.c | 2 +- src/k_hud.c | 35 +++++------------------------------ src/k_hud.h | 1 - src/k_items.c | 38 ++++++++++++++++++++++++++++++++------ src/k_items.h | 3 ++- src/lua_baselib.c | 1 + src/p_inter.c | 2 +- src/p_mobj.c | 2 +- src/p_setup.c | 6 +----- 13 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index f7ef6ae82..28380aab8 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -94,7 +94,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0xfc28316dfbd899a0 +#define ASSET_HASH_MAIN_PK3 0xc60298b5561908c7 #define ASSET_HASH_MAPPATCH_PK3 0xd4d4ce4a090d5473 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 1899127bc..3e15a6130 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -6184,10 +6184,10 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) } case CHEAT_GIVEITEM: { - SINT8 item = READSINT8(*cp); + UINT8 item = READUINT8(*cp); UINT8 amt = READUINT8(*cp); - item = CLAMP(item, KITEM_SAD, numkartitems - 1); + item = min(item, numkartitems - 1); K_StripItems(player); diff --git a/src/d_player.h b/src/d_player.h index 51c46eea6..985459e45 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -623,7 +623,7 @@ struct player_t UINT8 roulettetype; // Used for the roulette, for deciding type (0 = normal, 1 = better, 2 = eggman mark) // Item held stuff - SINT8 itemtype; // KITEM_ constant for item number + UINT8 itemtype; // KITEM_ constant for item number UINT8 itemamount; // Amount of said item itemflags_t itemflags; // holds IF_flags (see itemstate_t) tic_t itemusecooldown; // timer for which the player is locked out of using an item or mashing the item roulette (implementation of xItemLib setPlayerItemCooldown) diff --git a/src/deh_lua.c b/src/deh_lua.c index 3af0b3e89..4c6dbf4d0 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -446,6 +446,11 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) lua_pushinteger(L, KITEM_THUNDERSHIELD); return 1; } + if (fastcmp(p, "SAD")) + { + lua_pushinteger(L, MAXKARTITEMS); + return 1; + } i = DEH_FindKartItem(p); if (i != MAXKARTITEMS) { diff --git a/src/dehacked.c b/src/dehacked.c index 3c98a1ee2..dd0fd670e 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -770,7 +770,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_kartitem(word2); // find a kartitem by name - if (i >= 1 && i < numkartitems) + if (i >= (mainfile ? 0 : 1) && i < numkartitems) readkartitem(f, &kartitems[i]); else { diff --git a/src/k_hud.c b/src/k_hud.c index f95d39fb1..1753919a3 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -186,8 +186,6 @@ static patch_t *kp_itemtimer[2]; static patch_t *kp_itemmulsticker[4]; static patch_t *kp_itemx; -static patch_t *kp_sadface[2]; - static patch_t *kp_check[11]; static patch_t *kp_eggnum[4]; @@ -539,9 +537,6 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_itemmulsticker[3], "K_ISMULC"); HU_UpdatePatch(&kp_itemx, "K_ITX"); - HU_UpdatePatch(&kp_sadface[0], "K_ITSAD"); - HU_UpdatePatch(&kp_sadface[1], "K_ISSAD"); - for (i = 0; i < numkartitems; i++) { kartitem_t *item = &kartitems[i]; @@ -655,25 +650,6 @@ void K_LoadKartHUDGraphics(void) } } -patch_t *K_GetCachedItemPatch(SINT8 type, boolean tiny, UINT8 amount) -{ - if (type == KITEM_SAD) - return tiny ? kp_sadface[1] : kp_sadface[0]; - else if (type <= KITEM_NONE || type >= numkartitems) - return NULL; - - kartitem_t *item = &kartitems[type]; - kartitemgraphics_t *graphics = &item->graphics[tiny ? 1 : 0]; - - if (graphics->numpatches == 0) - return missingpat; - - if (item->flags & KIF_ANIMATED) - return graphics->patches[(leveltime % (graphics->numpatches*3)) / 3]; - else - return graphics->patches[CLAMP(amount - 1, 0, graphics->numpatches - 1)]; -} - //} INT32 ITEM_X, ITEM_Y; // Item Window @@ -1329,7 +1305,7 @@ static void K_drawKartItem(void) else if (stplyr->sadtimer > 0) { if (leveltime & 2) - localpatch = kp_sadface[tiny ? 1 : 0]; + localpatch = K_GetCachedItemPatch(MAXKARTITEMS, tiny, 0); else localpatch = kp_nodraw; } @@ -1341,11 +1317,10 @@ static void K_drawKartItem(void) localpatch = K_GetCachedItemPatch(stplyr->itemtype, tiny, stplyr->itemamount); if (localpatch == NULL) localpatch = kp_nodraw; // diagnose underflows - else if (kartitems[stplyr->itemtype].altenabled) + else if (K_IsKartItemAlternate(stplyr->itemtype)) isalt = true; - if (stplyr->itemtype > 0 && stplyr->itemtype < numkartitems) - dark = kartitems[stplyr->itemtype].flags & KIF_DARKBG; + dark = K_GetItemFlags(stplyr->itemtype) & KIF_DARKBG; if ((stplyr->itemflags & IF_ITEMOUT) && !(leveltime & 1)) localpatch = kp_nodraw; @@ -1381,7 +1356,7 @@ static void K_drawKartItem(void) { colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); } - else if (kartitems[stplyr->itemtype].flags & KIF_COLPATCH2PLAYER) + else if (K_GetItemFlags(stplyr->itemtype) & KIF_COLPATCH2PLAYER) { colmap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); } @@ -5726,7 +5701,7 @@ static void K_drawDistributionDebugger(void) kartresult_t *result = &kartresults[i]; if (result->cooldown > 0 && result->isalt == K_IsKartItemAlternate(result->type)) { - INT32 color = result->flags & KRF_INDIRECTITEM ? V_ORANGEMAP : kartitems[result->type].flags & KIF_UNIQUE ? V_YELLOWMAP : 0; + INT32 color = result->flags & KRF_INDIRECTITEM ? V_ORANGEMAP : K_GetItemFlags(result->type) & KIF_UNIQUE ? V_YELLOWMAP : 0; V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOBOTTOM, K_GetCachedItemPatch(result->type, true, 0)); if (result->amount > 1) V_DrawThinString(x+30, y+30, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOBOTTOM|V_6WIDTHSPACE, va("x%d", result->amount)); diff --git a/src/k_hud.h b/src/k_hud.h index d9c91dce5..362c472ba 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -100,7 +100,6 @@ void K_ReloadHUDColorCvar(void); boolean K_UseColorHud(void); UINT8 K_GetHudColor(void); void K_LoadKartHUDGraphics(void); -patch_t *K_GetCachedItemPatch(SINT8 type, boolean tiny, UINT8 amount); void K_drawKartHUD(void); void K_drawKartFreePlay(void); void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode); diff --git a/src/k_items.c b/src/k_items.c index 33d56ed44..c8c8978a1 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -152,6 +152,9 @@ void K_SetupItemOdds(void) item->altenabled = !!item->altcvar->value; } } + + for (UINT8 i = 0; i < numkartresults; i++) + kartresults[i].cooldown = 0; } boolean K_ItemResultEnabled(const kartresult_t *result) @@ -167,17 +170,40 @@ boolean K_ItemResultEnabled(const kartresult_t *result) boolean K_IsKartItemAlternate(kartitemtype_e itemtype) { - return itemtype >= 0 && itemtype < numkartitems && kartitems[itemtype].altenabled; + return itemtype < numkartitems && kartitems[itemtype].altenabled; +} + +kartitemflags_e K_GetItemFlags(kartitemtype_e itemtype) +{ + if (itemtype >= numkartitems) + return 0; + + // TODO: alt item flags + return kartitems[itemtype].flags; } UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny) { - if (type <= 0 || type >= numkartitems) + if (type >= numkartitems) return 1; return kartitems[type].flags & KIF_ANIMATED ? 2 : kartitems[type].graphics[tiny ? 1 : 0].numpatches + 1; } +patch_t *K_GetCachedItemPatch(kartitemtype_e type, boolean tiny, UINT8 amount) +{ + kartitem_t *item = &kartitems[type >= numkartresults ? 0 : type]; + kartitemgraphics_t *graphics = &item->graphics[tiny ? 1 : 0]; + + if (graphics->numpatches == 0) + return missingpat; + + if (item->flags & KIF_ANIMATED) + return graphics->patches[(leveltime % (graphics->numpatches*3)) / 3]; + else + return graphics->patches[CLAMP(amount - 1, 0, graphics->numpatches - 1)]; +} + // sprite3 baybeeeee!!!! // the renderer uses mobj->threshold to determine the item type void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount) @@ -185,8 +211,8 @@ void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCo part->sprite = SPR_ITEM; part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; - if (itemType <= 0 || itemType >= numkartitems) - return; + if (itemType >= numkartitems) + itemType = 0; UINT8 numpatches = kartitems[itemType].graphics[0].numpatches; if (kartitems[itemType].flags & KIF_ANIMATED) @@ -301,7 +327,7 @@ static void K_AwardPlayerItem(player_t *player, kartresult_t *result) if (result == NULL) { - player->itemtype = KITEM_SAD; + player->itemtype = MAXKARTITEMS; player->itemamount = 1; return; } @@ -2395,7 +2421,7 @@ void K_PlayerItemThink(player_t *player, boolean onground) K_BotResetItemConfirm(player, false); } break; - case KITEM_SAD: + case MAXKARTITEMS: // aka KITEM_SAD if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && !player->sadtimer) { diff --git a/src/k_items.h b/src/k_items.h index eb817d6ff..4b0b05791 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -33,7 +33,6 @@ extern "C" { typedef enum { - KITEM_SAD = -1, #define _(name, ...) KITEM_##name, #include "info/kartitems.h" #undef _ @@ -159,7 +158,9 @@ kartresult_t *K_GetKartResultAlt(const char *name, boolean alternate); void K_SetupItemOdds(void); boolean K_ItemResultEnabled(const kartresult_t *result); boolean K_IsKartItemAlternate(kartitemtype_e itemtype); +kartitemflags_e K_GetItemFlags(kartitemtype_e itemtype); UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny); +patch_t *K_GetCachedItemPatch(kartitemtype_e type, boolean tiny, UINT8 amount); void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount); void K_UpdateItemCooldown(void); diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 864d7c938..105c371ad 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -5077,6 +5077,7 @@ static int lib_kSetPlayerItemCooldown(lua_State *L) if (!player) return LUA_ErrInvalid(L, "player_t"); K_SetPlayerItemCooldown(player, c, force); + return 0; } static luaL_Reg lib[] = { diff --git a/src/p_inter.c b/src/p_inter.c index 1066319ff..3b066ecb0 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1744,7 +1744,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (target->threshold < 1 || target->threshold >= numkartitems) // bruh moment prevention { - player->itemtype = KITEM_SAD; + player->itemtype = MAXKARTITEMS; player->itemamount = 1; } else diff --git a/src/p_mobj.c b/src/p_mobj.c index c668cbc5b..ad9d28c79 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4166,7 +4166,7 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) UINT32 newRenderFlags = 0; if (itemType < 1 || itemType >= numkartitems) - itemType = KITEM_SAD; + itemType = MAXKARTITEMS; part = mobj; while (!P_MobjWasRemoved(part->hnext)) diff --git a/src/p_setup.c b/src/p_setup.c index 1f2477062..e37767219 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8074,9 +8074,6 @@ static void P_InitLevelSettings(boolean reloadinggamestate) antibumptime = (tic_t)cv_kartantibump.value * TICRATE; - if (!reloadinggamestate) - K_SetupItemOdds(); - // emerald hunt hunt1 = hunt2 = hunt3 = NULL; @@ -8401,8 +8398,7 @@ static void P_InitGametype(void) wantedcalcdelay = wantedfrequency*2; - for (i = 0; i < numkartresults; i++) - kartresults[i].cooldown = 0; + K_SetupItemOdds(); mapreset = 0; From e95faf8b55e0ea65f883c4fb77276420694b33fa Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Wed, 12 Nov 2025 22:49:00 +0100 Subject: [PATCH 54/76] Cleanup K_AwardPlayerItem --- src/d_player.h | 6 -- src/deh_tables.c | 9 +-- src/k_hud.c | 5 +- src/k_items.c | 170 +++++++++++++++++------------------------------ src/k_items.h | 11 ++- src/k_kart.c | 2 +- src/p_inter.c | 20 +----- 7 files changed, 80 insertions(+), 143 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 985459e45..d69d8c72b 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -148,12 +148,6 @@ typedef enum { KROULETTETYPE_EGGMAN, } kartroulettetype_t; -typedef enum { - KITEMBLINKMODE_NORMAL, - KITEMBLINKMODE_MASHED, - KITEMBLINKMODE_KARMA, -} kartitemblinkmode_t; - typedef enum { KSHIELD_NONE = 0, diff --git a/src/deh_tables.c b/src/deh_tables.c index 422e60be9..4ae8378de 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1554,10 +1554,11 @@ struct int_const_s const INT_CONST[] = { {"KROULETTETYPE_KARMA",KROULETTETYPE_KARMA}, {"KROULETTETYPE_EGGMAN",KROULETTETYPE_EGGMAN}, - // kartitemblinkmode_t - {"KITEMBLINKMODE_NORMAL",KITEMBLINKMODE_NORMAL}, - {"KITEMBLINKMODE_MASHED",KITEMBLINKMODE_MASHED}, - {"KITEMBLINKMODE_KARMA",KITEMBLINKMODE_KARMA}, + // kartitemblink_e + {"KITEMBLINK_NORMAL",KITEMBLINK_NORMAL}, + {"KITEMBLINK_MASHED",KITEMBLINK_MASHED}, + {"KITEMBLINK_KARMA",KITEMBLINK_KARMA}, + {"KITEMBLINK_DEBUG",KITEMBLINK_DEBUG}, // kartitemflags_e {"KIF_UNIQUESLOT",KIF_UNIQUESLOT}, diff --git a/src/k_hud.c b/src/k_hud.c index 1753919a3..6d2f43617 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1332,10 +1332,11 @@ static void K_drawKartItem(void) switch (stplyr->itemblinkmode) { - case 2: + case KITEMBLINK_DEBUG: + case KITEMBLINK_KARMA: localcolor = K_RainbowColor(leveltime); break; - case 1: + case KITEMBLINK_MASHED: localcolor = SKINCOLOR_RED; break; default: diff --git a/src/k_items.c b/src/k_items.c index c8c8978a1..bbebb7cd6 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -184,8 +184,10 @@ kartitemflags_e K_GetItemFlags(kartitemtype_e itemtype) UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny) { + if (type == MAXKARTITEMS) + type = 0; // KITEM_SAD displays like normal if (type >= numkartitems) - return 1; + return 1; // actually broken items show x1 to let you know return kartitems[type].flags & KIF_ANIMATED ? 2 : kartitems[type].graphics[tiny ? 1 : 0].numpatches + 1; } @@ -307,43 +309,60 @@ static UINT32 K_IntDistanceForMap(fixed_t curx, \return Don't double for this item? */ -static boolean K_DontDoubleMyItems(const kartresult_t *result) +static boolean K_DontDoubleMyItems(kartitemtype_e type, UINT8 amount) { - return result->type == KITEM_BALLHOG || result->type == KITEM_SPB - || result->type == KITEM_INVINCIBILITY || result->type == KITEM_GROW - || result->type == KITEM_BUBBLESHIELD || result->type == KITEM_FLAMESHIELD - || result->type == KITEM_ROCKETSNEAKER || result->type == KITEM_SHRINK - || result->type == KITEM_HYUDORO - || (result->type == KITEM_BANANA && result->amount >= 4) - || (result->type == KITEM_ORBINAUT && result->amount >= 3) - || (result->type == KITEM_SNEAKER && result->amount >= 3) - || (result->type == KITEM_JAWZ && result->amount >= 2) - || result->type >= KITEM_FIRSTFREESLOT; // TODO: excludes custom items for now + return type == KITEM_BALLHOG || type == KITEM_SPB + || type == KITEM_INVINCIBILITY || type == KITEM_GROW + || type == KITEM_BUBBLESHIELD || type == KITEM_FLAMESHIELD + || type == KITEM_ROCKETSNEAKER || type == KITEM_SHRINK + || type == KITEM_HYUDORO + || (type == KITEM_BANANA && amount >= 4) + || (type == KITEM_ORBINAUT && amount >= 3) + || (type == KITEM_SNEAKER && amount >= 3) + || (type == KITEM_JAWZ && amount >= 2) + || type >= KITEM_FIRSTFREESLOT; // TODO: excludes custom items for now } -static void K_AwardPlayerItem(player_t *player, kartresult_t *result) +static sfxenum_t resultfx[] = { + [KITEMBLINK_NORMAL] = sfx_itrolf, + [KITEMBLINK_MASHED] = sfx_itrolm, + [KITEMBLINK_KARMA] = sfx_itrolk, + [KITEMBLINK_DEBUG] = sfx_dbgsal, +}; + +// be careful with invalid item types here +void K_AwardPlayerItem(player_t *player, kartitemtype_e type, UINT8 amount, kartitemblink_e blink) { K_BotResetItemConfirm(player, true); + player->itemblink = TICRATE; + player->itemblinkmode = blink; + player->itemroulette = KROULETTE_DISABLED; // Since we're done, clear the roulette number + player->roulettetype = KROULETTETYPE_NORMAL; // This too + + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, resultfx[blink]); + + player->itemtype = type; + + if (K_IsAltShrunk(player) && !K_DontDoubleMyItems(type, amount) && blink != KITEMBLINK_DEBUG) + amount *= 2; + + player->itemamount = amount; +} + +static void K_AwardPlayerResult(player_t *player, kartresult_t *result, kartitemblink_e blink) +{ + kartitemtype_e type = result != NULL ? result->type : MAXKARTITEMS; + UINT8 amount = result != NULL ? result->amount : 1; + + K_AwardPlayerItem(player, type, amount, blink); + if (result == NULL) - { - player->itemtype = MAXKARTITEMS; - player->itemamount = 1; return; - } if (result->basecooldown > 0) result->cooldown = result->basecooldown; - - player->itemtype = result->type; - UINT8 itemamount = result->amount; - - if (K_IsAltShrunk(player) && !K_DontDoubleMyItems(result)) - { - itemamount *= 2; - } - - player->itemamount = itemamount; } static fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush) @@ -1308,14 +1327,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) // Give a debug item instead if specified if (cv_kartdebugitem.value != 0 && !modeattacking) { - player->itemtype = cv_kartdebugitem.value; - player->itemamount = cv_kartdebugamount.value; - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_KARMA; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_dbgsal); + K_AwardPlayerItem(player, cv_kartdebugitem.value, cv_kartdebugamount.value, KITEMBLINK_DEBUG); return; } @@ -1324,14 +1336,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) if (!(gametyperules & GTR_RACEODDS) && !(gametyperules & GTR_BATTLEODDS)) { UINT8 itemroll = P_RandomRange(0, numkartresults - 1); - - K_AwardPlayerItem(player, &kartresults[itemroll]); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_NORMAL; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); + K_AwardPlayerResult(player, &kartresults[itemroll], KITEMBLINK_NORMAL); return; } @@ -1345,50 +1350,25 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { if (mashed && (K_ItemResultEnabled(K_GetKartResult("superring")) || (modeattacking && K_RingsActive()))) // ANY mashed value? You get rings. { - K_AwardPlayerItem(player, K_GetKartResult("superring")); - player->itemblinkmode = KITEMBLINKMODE_MASHED; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); + K_AwardPlayerResult(player, K_GetKartResult("superring"), KITEMBLINK_MASHED); } else { if (modeattacking || K_ItemResultEnabled(K_GetKartResult("sneaker"))) // Waited patiently? You get a sneaker! - K_AwardPlayerItem(player, K_GetKartResult("sneaker")); + K_AwardPlayerResult(player, K_GetKartResult("sneaker"), KITEMBLINK_NORMAL); else // Default to sad if nothing's enabled... - K_AwardPlayerItem(player, NULL); - player->itemblinkmode = KITEMBLINKMODE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); + K_AwardPlayerResult(player, NULL, KITEMBLINK_NORMAL); } } else if (gametyperules & GTR_BATTLEODDS) { if (mashed && (bossinfo.boss || K_ItemResultEnabled(K_GetKartResult("banana"))) && !itembreaker) // ANY mashed value? You get a banana. - { - K_AwardPlayerItem(player, K_GetKartResult("banana")); - player->itemblinkmode = KITEMBLINKMODE_MASHED; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } + K_AwardPlayerResult(player, K_GetKartResult("banana"), KITEMBLINK_MASHED); else if (bossinfo.boss) - { - K_AwardPlayerItem(player, K_GetKartResult("orbinaut")); - player->itemblinkmode = KITEMBLINKMODE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } + K_AwardPlayerResult(player, K_GetKartResult("orbinaut"), KITEMBLINK_NORMAL); else if (itembreaker) - { - K_AwardPlayerItem(player, K_GetKartResult("sneaker")); - player->itemblinkmode = KITEMBLINKMODE_MASHED; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } + K_AwardPlayerResult(player, K_GetKartResult("sneaker"), KITEMBLINK_MASHED); } - - player->itemblink = TICRATE; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; return; } @@ -1399,13 +1379,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) INT32 debtamount = min(abs(player->ringmin), abs(player->rings)); if (P_RandomChance((debtamount*FRACUNIT)/abs(player->ringmin))) { - K_AwardPlayerItem(player, K_GetKartResult("superring")); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_MASHED; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); + K_AwardPlayerResult(player, K_GetKartResult("superring"), KITEMBLINK_MASHED); return; } } @@ -1416,13 +1390,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) && spbplace == -1 && K_GetKartResult("spb")->cooldown == 0 && !dontforcespb && K_ItemResultEnabled(K_GetKartResult("spb"))) { - K_AwardPlayerItem(player, K_GetKartResult("spb")); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_KARMA; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolk); + K_AwardPlayerResult(player, K_GetKartResult("spb"), KITEMBLINK_KARMA); return; } @@ -1465,13 +1433,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) if ((bestforce) && (forceresult)) { - K_AwardPlayerItem(player, forceresult); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_KARMA; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolk); + K_AwardPlayerResult(player, forceresult, KITEMBLINK_KARMA); return; } } @@ -1483,26 +1445,14 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } // Award the player whatever power is rolled + kartresult_t *result = NULL; if (totalspawnchance > 0) { totalspawnchance = P_RandomKey(totalspawnchance); for (i = 0; i < numkartresults && spawnchance[i] <= totalspawnchance; i++); - - K_AwardPlayerItem(player, &kartresults[i]); + result = &kartresults[i]; } - else - { - K_AwardPlayerItem(player, NULL); - } - - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, ((player->roulettetype == KROULETTETYPE_KARMA) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf))); - - player->itemblink = TICRATE; - player->itemblinkmode = ((player->roulettetype == KROULETTETYPE_KARMA) ? KITEMBLINKMODE_KARMA : (mashed ? KITEMBLINKMODE_MASHED : KITEMBLINKMODE_NORMAL)); - - player->itemroulette = KROULETTE_DISABLED; // Since we're done, clear the roulette number - player->roulettetype = KROULETTETYPE_NORMAL; // This too + K_AwardPlayerResult(player, result, player->roulettetype == KROULETTETYPE_KARMA ? KITEMBLINK_KARMA : mashed ? KITEMBLINK_MASHED : KITEMBLINK_NORMAL); } void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force) diff --git a/src/k_items.h b/src/k_items.h index 4b0b05791..9925fc8ee 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -43,6 +43,14 @@ typedef enum #define MAXKARTRESULTS 255 +typedef enum +{ + KITEMBLINK_NORMAL, + KITEMBLINK_MASHED, + KITEMBLINK_KARMA, + KITEMBLINK_DEBUG, +} kartitemblink_e; + typedef enum { ODDS_RACE, @@ -161,8 +169,9 @@ boolean K_IsKartItemAlternate(kartitemtype_e itemtype); kartitemflags_e K_GetItemFlags(kartitemtype_e itemtype); UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny); patch_t *K_GetCachedItemPatch(kartitemtype_e type, boolean tiny, UINT8 amount); -void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount); +void K_AwardPlayerItem(player_t *player, kartitemtype_e type, UINT8 amount, kartitemblink_e blink); +void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount); void K_UpdateItemCooldown(void); tic_t K_GetIndirectItemCooldown(void); void K_SetIndirectItemCooldown(tic_t cooldown); diff --git a/src/k_kart.c b/src/k_kart.c index 69f58771a..078987902 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7178,7 +7178,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->itemblink && player->itemblink-- <= 0) { - player->itemblinkmode = KITEMBLINKMODE_NORMAL; + player->itemblinkmode = KITEMBLINK_NORMAL; player->itemblink = 0; } diff --git a/src/p_inter.c b/src/p_inter.c index 3b066ecb0..db594a633 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1742,25 +1742,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget break; } - if (target->threshold < 1 || target->threshold >= numkartitems) // bruh moment prevention - { - player->itemtype = MAXKARTITEMS; - player->itemamount = 1; - } - else - { - player->itemtype = target->threshold; - if (K_GetShieldFromPlayer(player) != KSHIELD_NONE) // never give more than 1 shield - player->itemamount = 1; - else - player->itemamount = max(1, target->movecount); - } - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_NORMAL; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); + K_AwardPlayerItem(player, target->threshold, max(1, target->movecount), KITEMBLINK_NORMAL); } break; } From 1385692e831c2945e9d934392a7f30cd5190b3cb Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Wed, 12 Nov 2025 23:12:58 +0100 Subject: [PATCH 55/76] Cleanup itemroulette --- src/d_netcmd.c | 2 +- src/d_player.h | 11 ----------- src/deh_tables.c | 6 +----- src/g_game.c | 8 ++++---- src/k_collide.c | 5 +---- src/k_items.c | 13 +++++++++++-- src/k_items.h | 8 ++++++++ src/k_kart.c | 10 +++++----- src/p_enemy.c | 2 +- src/p_inter.c | 12 +++--------- 10 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 3e15a6130..e47883266 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -6192,7 +6192,7 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) K_StripItems(player); // Cancel roulette if rolling - player->itemroulette = KROULETTE_DISABLED; + player->itemroulette = 0; player->itemtype = item; player->itemamount = amt; diff --git a/src/d_player.h b/src/d_player.h index d69d8c72b..f52c89bdb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -137,17 +137,6 @@ typedef enum IF_HOLDREADY = 1<<3, // Hold button-style item is ready to activate } itemflags_t; -typedef enum { - KROULETTE_DISABLED, - KROULETTE_ACTIVE, -} kartroulettestate_t; - -typedef enum { - KROULETTETYPE_NORMAL, - KROULETTETYPE_KARMA, - KROULETTETYPE_EGGMAN, -} kartroulettetype_t; - typedef enum { KSHIELD_NONE = 0, diff --git a/src/deh_tables.c b/src/deh_tables.c index 4ae8378de..cb42346aa 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1545,11 +1545,7 @@ struct int_const_s const INT_CONST[] = { {"KSHIELD_FLAME",KSHIELD_FLAME}, {"NUMKARTSHIELDS",NUMKARTSHIELDS}, - // kartroulettestate_t - {"KROULETTE_DISABLED",KROULETTE_DISABLED}, - {"KROULETTE_ACTIVE",KROULETTE_ACTIVE}, - - // kartroulettetype_t + // kartroulettetype_e {"KROULETTETYPE_NORMAL",KROULETTETYPE_NORMAL}, {"KROULETTETYPE_KARMA",KROULETTETYPE_KARMA}, {"KROULETTETYPE_EGGMAN",KROULETTETYPE_EGGMAN}, diff --git a/src/g_game.c b/src/g_game.c index 6dac5c1ea..efc653668 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2806,8 +2806,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) // SRB2kart if (betweenmaps || leveltime <= starttime || spectator == true) { - itemroulette = KROULETTE_DISABLED; - previtemroulette = KROULETTE_DISABLED; + itemroulette = 0; + previtemroulette = 0; roulettetype = KROULETTETYPE_NORMAL; itemtype = 0; itemamount = 0; @@ -2847,8 +2847,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) } else { - itemroulette = (players[player].itemroulette > KROULETTE_DISABLED ? KROULETTE_ACTIVE : KROULETTE_DISABLED); - previtemroulette = (players[player].previtemroulette > KROULETTE_DISABLED ? KROULETTE_ACTIVE : KROULETTE_DISABLED); + itemroulette = players[player].itemroulette > 0 ? 1 : 0; + previtemroulette = players[player].previtemroulette > 0 ? 1 : 0; roulettetype = players[player].roulettetype; if (players[player].itemflags & IF_ITEMOUT) diff --git a/src/k_collide.c b/src/k_collide.c index b104ef38e..cb49ec324 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -256,10 +256,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) } else { - K_DropItems(t2->player); //K_StripItems(t2->player); - //K_StripOther(t2->player); - t2->player->itemroulette = KROULETTE_ACTIVE; - t2->player->roulettetype = KROULETTETYPE_EGGMAN; + K_StartRoulette(t2->player, KROULETTETYPE_EGGMAN); } if (t2->player->flamestore && (K_GetShieldFromPlayer(t2->player) == KSHIELD_FLAME)) diff --git a/src/k_items.c b/src/k_items.c index bbebb7cd6..d38f07639 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -337,7 +337,7 @@ void K_AwardPlayerItem(player_t *player, kartitemtype_e type, UINT8 amount, kart player->itemblink = TICRATE; player->itemblinkmode = blink; - player->itemroulette = KROULETTE_DISABLED; // Since we're done, clear the roulette number + player->itemroulette = 0; // Since we're done, clear the roulette number player->roulettetype = KROULETTETYPE_NORMAL; // This too if (P_IsDisplayPlayer(player)) @@ -1316,7 +1316,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) if (player->roulettetype == KROULETTETYPE_EGGMAN) { player->eggmanexplode = 4*TICRATE; - player->itemroulette = KROULETTE_DISABLED; + player->itemroulette = 0; player->roulettetype = KROULETTETYPE_NORMAL; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrole); @@ -1455,6 +1455,15 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) K_AwardPlayerResult(player, result, player->roulettetype == KROULETTETYPE_KARMA ? KITEMBLINK_KARMA : mashed ? KITEMBLINK_MASHED : KITEMBLINK_NORMAL); } +void K_StartRoulette(player_t *player, kartroulettetype_e roulettetype) +{ + if (roulettetype == KROULETTETYPE_EGGMAN) + K_DropItems(player); + + player->itemroulette = 1; + player->roulettetype = roulettetype; +} + void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force) { if ((timer >= player->itemusecooldown) || force) diff --git a/src/k_items.h b/src/k_items.h index 9925fc8ee..38ec0944e 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -51,6 +51,13 @@ typedef enum KITEMBLINK_DEBUG, } kartitemblink_e; +typedef enum +{ + KROULETTETYPE_NORMAL, + KROULETTETYPE_KARMA, + KROULETTETYPE_EGGMAN, +} kartroulettetype_e; + typedef enum { ODDS_RACE, @@ -184,6 +191,7 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); INT32 K_GetRollingRouletteItem(player_t *player); void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); +void K_StartRoulette(player_t *player, kartroulettetype_e roulettetype); void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force); diff --git a/src/k_kart.c b/src/k_kart.c index 078987902..81d230e65 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7285,8 +7285,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_KartItemRoulette(player, cmd); // Gain rings when roulette starts. - if (player->itemroulette > KROULETTE_DISABLED - && player->previtemroulette == KROULETTE_DISABLED + if (player->itemroulette > 0 + && player->previtemroulette == 0 && player->roulettetype != KROULETTETYPE_EGGMAN) { K_AwardScaledPlayerRings(player, ASR_ITEMBOX); @@ -9669,9 +9669,9 @@ void K_StripItems(player_t *player) player->itemflags &= ~IF_EGGMANOUT; K_UnsetItemOut(player); - if (!player->itemroulette || player->roulettetype != 2) + if (!player->itemroulette || player->roulettetype != KROULETTETYPE_EGGMAN) { - player->itemroulette = KROULETTE_DISABLED; + player->itemroulette = 0; player->roulettetype = KROULETTETYPE_NORMAL; } @@ -9690,7 +9690,7 @@ void K_StripItems(player_t *player) void K_StripOther(player_t *player) { - player->itemroulette = KROULETTE_DISABLED; + player->itemroulette = 0; player->roulettetype = KROULETTETYPE_NORMAL; player->invincibilitytimer = 0; diff --git a/src/p_enemy.c b/src/p_enemy.c index 5094eceb6..fbea63c8e 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -10839,7 +10839,7 @@ void A_ItemPop(void *thing) if (!((gametyperules & GTR_BUMPERS) && actor->target->player->bumper <= 0) && !itembreaker) { - actor->target->player->itemroulette = KROULETTE_ACTIVE; + K_StartRoulette(actor->target->player, KROULETTETYPE_NORMAL); } else if (itembreaker) { diff --git a/src/p_inter.c b/src/p_inter.c index db594a633..68e22f0c5 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -459,8 +459,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) special->target->player->karmadelay = comebacktime; - player->itemroulette = KROULETTE_ACTIVE; - player->roulettetype = KROULETTETYPE_KARMA; + K_StartRoulette(player, KROULETTETYPE_KARMA); } else if (special->target->player->karmamode == 2 && P_CanPickupItem(player, 2)) { @@ -495,11 +494,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) special->target->player->karmadelay = comebacktime; - K_DropItems(player); //K_StripItems(player); - //K_StripOther(player); - - player->itemroulette = KROULETTE_ACTIVE; - player->roulettetype = KROULETTETYPE_EGGMAN; + K_StartRoulette(player, KROULETTETYPE_EGGMAN); if (special->target->player->eggmanblame >= 0 && special->target->player->eggmanblame < MAXPLAYERS @@ -591,8 +586,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->fuse || !P_CanPickupItem(player, 1) || ((gametyperules & GTR_BUMPERS) && player->bumper <= 0)) return; - player->itemroulette = KROULETTE_ACTIVE; - player->roulettetype = KROULETTETYPE_KARMA; + K_StartRoulette(player, KROULETTETYPE_KARMA); // Karma fireworks for (i = 0; i < 5; i++) From be8196c411a47d1c403ccea2a6b786d4a38f4e0c Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 13 Nov 2025 00:22:44 -0500 Subject: [PATCH 56/76] Buff Alt. Shrink speed Curse of Easy Speed (or Gear 1) --- src/d_netcmd.c | 2 +- src/k_kart.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e47883266..58f6a3812 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -470,7 +470,7 @@ consvar_t cv_kartstacking_grow_speedboost = CVAR_INIT ("vanillaboost_grow_speedb consvar_t cv_kartstacking_grow_accelboost = CVAR_INIT ("vanillaboost_grow_accelboost", "0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_grow_stackable = CVAR_INIT ("vanillaboost_grow_stackable", "Off", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); -consvar_t cv_kartstacking_altshrink_speedboost = CVAR_INIT ("vanillaboost_altshrink_speedboost", "0.40", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); +consvar_t cv_kartstacking_altshrink_speedboost = CVAR_INIT ("vanillaboost_altshrink_speedboost", "0.50", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_altshrink_accelboost = CVAR_INIT ("vanillaboost_altshrink_accelboost", "0.15", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_altshrink_stackable = CVAR_INIT ("vanillaboost_altshrink_stackable", "On", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); diff --git a/src/k_kart.c b/src/k_kart.c index 81d230e65..630a1c428 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2646,7 +2646,7 @@ static void K_GetKartBoostPower(player_t *player) fixed_t shrinkspeed = FixedDiv(SHRINKSPEEDBOOST, SHRINK_SCALE); fixed_t shrinkaccel = FixedDiv(SHRINKACCELBOOST, SHRINK_SCALE); - K_DoBoost(player, FixedMul(shrinkspeed, GROW_SCALE), FixedMul(shrinkaccel, GROW_SCALE), SHRINKSTACKABLE, SHRINKSTACKABLE); // + 45% top speed, + 200% acceleration + K_DoBoost(player, FixedMul(shrinkspeed, GROW_SCALE), FixedMul(shrinkaccel, GROW_SCALE), SHRINKSTACKABLE, SHRINKSTACKABLE); // + 50% top speed, + 15% acceleration } if (player->bubbleboost) // Bubble Shield popping boost From 1bc89c3fa7d32ebb471a2812c36927561eb9cb48 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Thu, 13 Nov 2025 10:02:02 -0500 Subject: [PATCH 57/76] If flags are not specified, assume 0 for P_DoPlayerExit --- src/lua_baselib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 7e11a3bee..c79010696 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -1418,7 +1418,7 @@ static int lib_pMovePlayer(lua_State *L) static int lib_pDoPlayerExit(lua_State *L) { player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); - pflags_t flags = luaL_checkinteger(L, 2); + pflags_t flags = lua_isnoneornil(L, 2) ? 0 : luaL_checkinteger(L, 2); NOHUD INLEVEL if (!player) From 911c454fa85745a714d89a14459059a1e1bfd79e Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Thu, 13 Nov 2025 15:54:07 +0100 Subject: [PATCH 58/76] Fix graphics replacements, fix altitem flag not being synced --- src/deh_soc.c | 14 ++++++++++++++ src/k_hud.c | 21 ++++++++++++++++----- src/p_saveg.c | 7 +++++++ src/r_things.cpp | 1 - 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 8869c8075..2adcd7dd7 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -4044,6 +4044,7 @@ void readkartitem(MYFILE *f, kartitem_t *item) char *word = s; char *word2; char *tmp; + INT32 i; do if (myfgets(s, MAXLINELEN, f)) { @@ -4076,6 +4077,19 @@ void readkartitem(MYFILE *f, kartitem_t *item) if (fastcmp(word, "BIGPATCHES") || fastcmp(word, "SMALLPATCHES")) { kartitemgraphics_t *graphics = &item->graphics[word[0] == 'B' ? 0 : 1]; + + if (graphics == &item->graphics[0] && item->spritedef.numframes > 0) + { + Z_Free(item->spritedef.spriteframes); + memset(&item->spritedef, 0, sizeof(item->spritedef)); + } + for (i = 0; i < graphics->numpatches; i++) + { + Z_Free(graphics->patchnames[i]); + Patch_Free(graphics->patches[i]); + graphics->patches[i] = NULL; + } + tmp = strtok(word2,","); for (graphics->numpatches = 0; graphics->numpatches < MAXITEMPATCHES;) { diff --git a/src/k_hud.c b/src/k_hud.c index 6d2f43617..0b2a8fdf5 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -540,11 +540,22 @@ void K_LoadKartHUDGraphics(void) for (i = 0; i < numkartitems; i++) { kartitem_t *item = &kartitems[i]; - for (j = 0; j < item->graphics[0].numpatches; j++) - HU_UpdatePatch(&item->graphics[0].patches[j], item->graphics[0].patchnames[j]); - for (j = 0; j < item->graphics[1].numpatches; j++) - HU_UpdatePatch(&item->graphics[1].patches[j], item->graphics[1].patchnames[j]); - + for (j = 0; j < 2; j++) + { + kartitemgraphics_t *graphics = &item->graphics[j]; + for (INT32 k = 0; k < graphics->numpatches; k++) + { + if (graphics->patches[k] == NULL) + { + // have to manually do this because HU_UpdatePatch only checks graphics from added WADs during partadd + // UUUUUGGGGGGGHHHHHHHHHHHHHHHHHHh + lumpnum_t lump = W_CheckNumForName(graphics->patchnames[k]); + graphics->patches[k] = lump == LUMPERROR ? missingpat : W_CachePatchNum(lump, PU_HUDGFX); + } + else + HU_UpdatePatch(&graphics->patches[k], graphics->patchnames[k]); + } + } R_AddKartItemSprites(item); } diff --git a/src/p_saveg.c b/src/p_saveg.c index 4991aa6da..d35d1b3ad 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -3942,6 +3942,10 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNC(gametype); +#if MAXPLAYERS > 32 +#error Update playeringame syncing please +#endif + if (save->write) { UINT32 pig = 0; @@ -4270,6 +4274,9 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNC(wantedcalcdelay); + for (i = 0; i < numkartitems; i++) + SYNC(kartitems[i].altenabled); + for (i = 0; i < numkartresults; i++) SYNC(kartresults[i].cooldown); diff --git a/src/r_things.cpp b/src/r_things.cpp index 45ae0f379..483edd525 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -290,7 +290,6 @@ void R_AddKartItemSprites(kartitem_t *item) spritedef_t *spritedef = &item->spritedef; memset(sprtemp, 0xff, sizeof(sprtemp)); spritename = "ITEM"; - memset(spritedef, 0, sizeof(*spritedef)); maxframe = spritedef->numframes - 1; const kartitemgraphics_t *graphics = &item->graphics[0]; From 16ff617c8bb5eb7494b3bc26f6210f34dc764383 Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 13 Nov 2025 12:33:32 -0500 Subject: [PATCH 59/76] Alt. Invin. bottlenecker prevention prelim --- src/d_player.h | 5 +++-- src/k_kart.c | 13 ++++++++++++- src/k_kart.h | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index f52c89bdb..cb0d61288 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -668,8 +668,9 @@ struct player_t UINT16 rocketsneakertimer; // Rocket Sneaker duration timer - UINT16 invincibilitytimer; // Invincibility timer - UINT16 maxinvincibilitytime; // (Alternate) Initial time for Invincibility, used for the item bar. + UINT16 invincibilitytimer; // Invincibility timer + boolean shortinvin; // (Alternate) This Invincibility is too short to punish with bottleneckers, + UINT16 maxinvincibilitytime; // (Alternate) Initial time for Invincibility, used for the item bar. UINT16 invincibilitybottleneck; // (Alternate) Prevents breakaways by gradienting towards a heavier decrement. INT16 invincibilitycancel; // (Alternate) Duration of Invincibility canceling. UINT8 invincibilitywarning; // (Alternate) "Timer warning" boolean to signal Alt. Invin. is running out. diff --git a/src/k_kart.c b/src/k_kart.c index 630a1c428..ce13165ff 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2388,6 +2388,9 @@ UINT16 K_GetInvincibilityTime(player_t *player) UINT32 i, pingame; fixed_t distmul = FRACUNIT; + // ALWAYS reset shortinvin! + player->shortinvin = false; + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return BASEINVINTIME; @@ -2418,7 +2421,15 @@ UINT16 K_GetInvincibilityTime(player_t *player) } fixed_t clustermul = K_InvincibilityEasing(FixedMul(player->distancefromcluster,distmul)); - return FixedMul(BASEINVINTIME, clustermul); + UINT16 invintics = FixedMul(BASEINVINTIME, clustermul); + + if (invintics <= MININVINTIME) + { + player->shortinvin = true; + invintics = MININVINTIME; + } + + return invintics; } #undef LEGACYALTINVINMUL diff --git a/src/k_kart.h b/src/k_kart.h index 2b7f842fe..902a2ffdc 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -33,6 +33,7 @@ Make sure this matches the actual number of states #define SHRINK_SCALE ((6*FRACUNIT)/8) #define BASEINVINTIME (10 * TICRATE) +#define MININVINTIME (7 * TICRATE) #define AUTORESPAWN_TIME (TICRATE/2) #define AUTORESPAWN_THRESHOLD (TICRATE/4) From ea090b35f09abbd95cdb593f7d0388b76a027184 Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 13 Nov 2025 16:11:38 -0500 Subject: [PATCH 60/76] Increase Alt. Invin. distance threshold...again 95% this time --- src/k_items.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/k_items.c b/src/k_items.c index d38f07639..8462dbb82 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -445,7 +445,7 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) // Odds value for Alt. Invin. to force itself on trailing players. #define INVFORCEODDS (MAXINVODDS / 2) -#define FRAC_3h (3 * FRACUNIT / 4) +#define FRAC_95pct (95 * FRACUNIT / 100) static INT32 K_KartGetInvincibilityOdds(UINT32 dist) { @@ -465,10 +465,10 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist) } else { - if (fac <= FRAC_3h) + if (fac <= FRAC_95pct) { // Invincibility is practically useless at lower distances. - // At below 75%, remove it from the item pool. + // Only let it appear at or above 95%. return 0; } // Basic linear climb to "reasonable" odds. @@ -478,7 +478,7 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist) return min(MAXINVODDS, finodds); } -#undef FRAC_3h +#undef FRAC_95pct // updates all result cooldown timers, and sets cooldowns for "unique" items void K_UpdateItemCooldown(void) From 1529a5d94a03998125ecf93810d7d76ee13d32be Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 13 Nov 2025 16:12:11 -0500 Subject: [PATCH 61/76] Shortinvin isn't necessary, just use a minimum time limit --- src/d_player.h | 1 - src/k_kart.c | 11 +---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index cb0d61288..b828778cd 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -669,7 +669,6 @@ struct player_t UINT16 rocketsneakertimer; // Rocket Sneaker duration timer UINT16 invincibilitytimer; // Invincibility timer - boolean shortinvin; // (Alternate) This Invincibility is too short to punish with bottleneckers, UINT16 maxinvincibilitytime; // (Alternate) Initial time for Invincibility, used for the item bar. UINT16 invincibilitybottleneck; // (Alternate) Prevents breakaways by gradienting towards a heavier decrement. INT16 invincibilitycancel; // (Alternate) Duration of Invincibility canceling. diff --git a/src/k_kart.c b/src/k_kart.c index ce13165ff..6659acb08 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2388,9 +2388,6 @@ UINT16 K_GetInvincibilityTime(player_t *player) UINT32 i, pingame; fixed_t distmul = FRACUNIT; - // ALWAYS reset shortinvin! - player->shortinvin = false; - if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return BASEINVINTIME; @@ -2423,13 +2420,7 @@ UINT16 K_GetInvincibilityTime(player_t *player) fixed_t clustermul = K_InvincibilityEasing(FixedMul(player->distancefromcluster,distmul)); UINT16 invintics = FixedMul(BASEINVINTIME, clustermul); - if (invintics <= MININVINTIME) - { - player->shortinvin = true; - invintics = MININVINTIME; - } - - return invintics; + return max(MININVINTIME, invintics); } #undef LEGACYALTINVINMUL From 5e63f06de84c0736d0105fe8bbf62547a52c8e18 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Fri, 14 Nov 2025 17:22:05 -0500 Subject: [PATCH 62/76] only call netkeepalive between calls during gl precache https://github.com/Indev450/SRB2Kart-Saturn/commit/bcd54110d523333b188df0bba2a8c07a721c38b2 --- src/hardware/hw_cache.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c index 8ce3e07b6..76536350d 100644 --- a/src/hardware/hw_cache.c +++ b/src/hardware/hw_cache.c @@ -977,11 +977,20 @@ void HWR_PrecacheLevel(void) // Precache flats. HWR_PrecacheLevelFlats(); + // prevent timeouts + NetKeepAlive(); + // Precache textures. HWR_PrecacheLevelTextures(); + // prevent timeouts + NetKeepAlive(); + // Precache sprites. //HWR_PrecacheLevelSprites(); + + // prevent timeouts + //NetKeepAlive(); } void HWR_LoadMapTextures(size_t pnumtextures) From 9efc4689790dbaafd8a88588b35fa4a2a1b81db4 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 15 Nov 2025 17:17:34 +0100 Subject: [PATCH 63/76] Updated item toggles menu Invinc is animated on the title screen, superring now gets locked out Small odds code cleanup since the landmine's result is finally gone --- src/d_main.cpp | 2 +- src/k_items.c | 51 +++++++++++++++++++++----------------------------- src/m_menu.c | 38 ++++++++++++++++++++++++------------- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 28380aab8..d0e3d13c4 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -94,7 +94,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0xc60298b5561908c7 +#define ASSET_HASH_MAIN_PK3 0x3f0509a0ec4bffd7 #define ASSET_HASH_MAPPATCH_PK3 0xd4d4ce4a090d5473 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE diff --git a/src/k_items.c b/src/k_items.c index 8462dbb82..cd0661914 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -200,8 +200,10 @@ patch_t *K_GetCachedItemPatch(kartitemtype_e type, boolean tiny, UINT8 amount) if (graphics->numpatches == 0) return missingpat; + tic_t timer = G_GamestateUsesLevel() ? leveltime : finalecount; + if (item->flags & KIF_ANIMATED) - return graphics->patches[(leveltime % (graphics->numpatches*3)) / 3]; + return graphics->patches[(timer % (graphics->numpatches*3)) / 3]; else return graphics->patches[CLAMP(amount - 1, 0, graphics->numpatches - 1)]; } @@ -695,6 +697,24 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * } } + // TODO: convert into uniqueodds functions (what happened to "already shrunk"?) + if (result->type == KITEM_SHRINK) + { + if (!K_IsKartItemAlternate(KITEM_SHRINK)) + { + if (roulette->pingame-1 <= roulette->pexiting) + newodds = 0; + } + else + { + if (roulette->rival) + { + // Rival bot or already shrunk. DON'T roll another. + newodds = 0; + } + } + } + if (result->unique_odds[oddstable]) { // This item has unique odds! @@ -706,35 +726,6 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * newodds = uoddsfunc(newodds, roulette, result, forceme); //CONS_Printf("OK!\n"); } - else - { - switch (result->type) - { - // Alt. Invin. odds are handled in its odds function - // So are SPB odds - case KITEM_LANDMINE: - // au revoir - newodds = 0; - break; - case KITEM_SHRINK: - if (!K_IsKartItemAlternate(KITEM_SHRINK)) - { - if (roulette->pingame-1 <= roulette->pexiting) - newodds = 0; - } - else - { - if (roulette->rival) - { - // Rival bot or already shrunk. DON'T roll another. - newodds = 0; - } - } - break; - default: - break; - } - } // In very small matches, remove the stupid bottom half item limiter if (roulette->pingame < 6) diff --git a/src/m_menu.c b/src/m_menu.c index 1e8e49656..89caf29b7 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -8347,6 +8347,10 @@ INT32 MR_SetupMonitorToggles(INT32 choice) { item->argument = 0; item->patch = result->cvar->name; + if (kartitems[result->type].altcvar != NULL) + item->tooltip = "Press BACKSPACE to swap variants."; + else if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) + item->tooltip = "Rings must be enabled in Gameplay Mods!"; } } @@ -8376,7 +8380,7 @@ void MD_DrawMonitorToggles(void) INT32 i, translucent; const kartresult_t *result = K_GetKartResult(currentMenu->menuitems[itemOn].patch); const char *displayname; - boolean hasalt; // ha, salt! (has alt) + //boolean hasalt; // ha, salt! (has alt) if (result != NULL && kartitems[result->type].altcvar != NULL && kartitems[result->type].altcvar->value == 1) result = K_GetKartResultAlt(result->cvar->name, true); @@ -8430,7 +8434,11 @@ void MD_DrawMonitorToggles(void) kartresult_t *result = K_GetKartResult(currentMenu->menuitems[thisitem].patch); kartitem_t *item = &kartitems[result->type]; - boolean enabled = K_ItemResultEnabled(result); + boolean enabled = result->cvar->value == 1; + + if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) + enabled = false; + translucent = enabled ? 0 : V_TRANSLUCENT; if (enabled) @@ -8457,8 +8465,6 @@ void MD_DrawMonitorToggles(void) y = currentMenu->y+(spacing/4); } - hasalt = false; - switch (currentMenu->menuitems[itemOn].argument) { case 2: @@ -8487,7 +8493,11 @@ void MD_DrawMonitorToggles(void) { displayname = result->displayname; kartitem_t *item = &kartitems[result->type]; - boolean enabled = K_ItemResultEnabled(result); + boolean enabled = result->cvar->value == 1; + + if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) + enabled = false; + translucent = enabled ? 0 : V_TRANSLUCENT; if (enabled) @@ -8507,7 +8517,6 @@ void MD_DrawMonitorToggles(void) if (item->altcvar != NULL) { - hasalt = true; translucent = item->altcvar->value == 1 ? 0 : V_TRANSLUCENT; V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName("K_ALTITM", PU_CACHE)); } @@ -8520,11 +8529,7 @@ void MD_DrawMonitorToggles(void) V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, MENUCAPS|highlightflags, va("* %s *", displayname)); - if (hasalt) - V_DrawThinString(8, - BASEVIDHEIGHT - 12, - highlightflags | V_ALLOWLOWERCASE | V_SNAPTOBOTTOM | V_SNAPTOLEFT, - "Press BACKSPACE to swap variants"); + M_DrawMenuTooltips(); } INT32 MR_HandleMonitorToggles(INT32 choice) @@ -8603,8 +8608,15 @@ INT32 MR_HandleMonitorToggles(INT32 choice) } else if (result != NULL) { - S_StartSound(NULL, sfx_s1ba); - CV_AddValue(result->cvar, 1); + if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) + { + S_StartSound(NULL, sfx_s231); + } + else + { + S_StartSound(NULL, sfx_s1ba); + CV_AddValue(result->cvar, 1); + } } break; From 77e4d92f5d48991f18b374eb883fc915f91b0cc3 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sat, 15 Nov 2025 22:17:00 +0100 Subject: [PATCH 64/76] Item capsules now use stringarg0 to specify the item type ...for obvious reasons arg0 is now remapped from RR item types, cause why the hell not --- src/p_mobj.c | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index ad9d28c79..33ca9ddae 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -15,6 +15,7 @@ #include "d_netcmd.h" #include "d_think.h" #include "dehacked.h" +#include "deh_tables.h" #include "doomdef.h" #include "doomstat.h" #include "doomtype.h" @@ -13573,9 +13574,39 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean if (!P_IsObjectOnGround(mobj)) mobj->flags |= MF_NOGRAVITY; - // Angle = item type - if (mthing->args[0] > 0 && mthing->args[0] < numkartitems) - mobj->threshold = mthing->args[0]; + // Item type + static kartitemtype_e rrtypes[] = { + KITEM_SNEAKER, + KITEM_ROCKETSNEAKER, + KITEM_INVINCIBILITY, + KITEM_BANANA, + KITEM_EGGMAN, + KITEM_ORBINAUT, + KITEM_JAWZ, + KITEM_MINE, + MAXKARTITEMS, // LANDMINE + KITEM_BALLHOG, + KITEM_SPB, + KITEM_GROW, + KITEM_SHRINK, + KITEM_THUNDERSHIELD, + KITEM_BUBBLESHIELD, + KITEM_FLAMESHIELD, + KITEM_HYUDORO, + KITEM_POGOSPRING, + KITEM_SUPERRING, + KITEM_KITCHENSINK, + MAXKARTITEMS, // DROPTARGET + MAXKARTITEMS, // GARDENTOP + MAXKARTITEMS, // GACHABOM + MAXKARTITEMS, // STONESHOE + MAXKARTITEMS, // TOXOMISTER + }; + + if (mthing->stringargs[0] != NULL) + mobj->threshold = DEH_FindKartItem(mthing->stringargs[0]); + else if (mapnamespace == MNS_RINGRACERS && mthing->args[0] > 0 && (size_t)mthing->args[0] < sizeof(rrtypes)/sizeof(*rrtypes)) + mobj->threshold = rrtypes[mthing->args[0] - 1]; // Parameter = extra items (x5 for rings) mobj->movecount += mthing->args[1]; From fc4c3ada89cf8a29c9b365ddd8492ca8026bf05b Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sat, 15 Nov 2025 17:05:47 -0500 Subject: [PATCH 65/76] comment out eggmine stuff (for now) --- src/info/mobjs.h | 4 ++-- src/info/states.h | 6 +++--- src/k_collide.c | 8 ++++---- src/p_mobj.c | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/info/mobjs.h b/src/info/mobjs.h index 207456333..448199356 100644 --- a/src/info/mobjs.h +++ b/src/info/mobjs.h @@ -605,8 +605,8 @@ _(BOOMPARTICLE) // Land Mine _(LANDMINE) // Egg Mine -_(EGGMINE_CAPSULE) -_(EGGMINE_SHIELD) +// _(EGGMINE_CAPSULE) +// _(EGGMINE_SHIELD) _(BALLHOG) // Ballhog _(BALLHOGBOOM) diff --git a/src/info/states.h b/src/info/states.h index 86c8c67f4..21102a637 100644 --- a/src/info/states.h +++ b/src/info/states.h @@ -2817,9 +2817,9 @@ _(LANDMINE) _(LANDMINE_EXPLODE) // Egg mine -_(EGGMINE) -_(EGGMINE_POP) -_(EGGMINE_SHIELD) +// _(EGGMINE) +// _(EGGMINE_POP) +// _(EGGMINE_SHIELD) // Ballhog _(BALLHOG1) diff --git a/src/k_collide.c b/src/k_collide.c index cb49ec324..bc92a4ce8 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -576,8 +576,8 @@ static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble, INT16 d static boolean K_BubbleReflectingTrapItem(const mobj_t *t) { return t->type == MT_BANANA || (t->type == MT_ORBINAUT && t->flags2 & MF2_AMBUSH) || t->type == MT_JAWZ_DUD || - t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE || - t->type == MT_EGGMINE_CAPSULE || t->type == MT_EGGMINE_SHIELD; + t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE;// || + // t->type == MT_EGGMINE_CAPSULE || t->type == MT_EGGMINE_SHIELD; } static boolean K_StrongPlayerBump(const player_t *player) @@ -640,7 +640,7 @@ boolean K_BubbleShieldCanReflect(mobj_t *t) { return (t->type == MT_ORBINAUT || t->type == MT_JAWZ || t->type == MT_JAWZ_DUD || t->type == MT_BANANA || t->type == MT_EGGMANITEM || t->type == MT_BALLHOG - || t->type == MT_SSMINE || t->type == MT_LANDMINE || t->type == MT_EGGMINE_CAPSULE || t->type == MT_SINK + || t->type == MT_SSMINE || t->type == MT_LANDMINE || t->type == MT_SINK //|| t->type == MT_EGGMINE_CAPSULE || t->type == MT_KART_LEFTOVER || t->type == MT_PLAYER); } @@ -660,7 +660,7 @@ static INT16 K_GetBubbleDamage(mobj_t* itm) { case MT_EGGMANITEM: case MT_BALLHOG: - case MT_EGGMINE_CAPSULE: + // case MT_EGGMINE_CAPSULE: // Experiment: Let Bubble Shields hard-counter Ballhogs and Eggboxes. dmg = 0; break; diff --git a/src/p_mobj.c b/src/p_mobj.c index 33ca9ddae..ef57fb276 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1229,7 +1229,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo) case MT_EGGMANITEM: case MT_SSMINE: case MT_LANDMINE: - case MT_EGGMINE_CAPSULE: + // case MT_EGGMINE_CAPSULE: case MT_SINK: if (mo->extravalue2 > 0) { @@ -6486,7 +6486,7 @@ boolean P_IsKartFieldItem(INT32 type) case MT_JAWZ_DUD: case MT_SSMINE: case MT_LANDMINE: - case MT_EGGMINE_CAPSULE: + // case MT_EGGMINE_CAPSULE: case MT_BALLHOG: case MT_BUBBLESHIELDTRAP: case MT_SINK: @@ -7983,7 +7983,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj) case MT_BANANA: case MT_EGGMANITEM: case MT_LANDMINE: - case MT_EGGMINE_CAPSULE: + // case MT_EGGMINE_CAPSULE: case MT_SPB: if (P_IsObjectOnGround(mobj)) { @@ -8767,7 +8767,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (mobj->threshold > 0) mobj->threshold--; break; - case MT_EGGMINE_CAPSULE: + // case MT_EGGMINE_CAPSULE: // todo // decrement fuse while on ground // when fuse runs out pop open and spawn a land mine @@ -10853,8 +10853,8 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_SSMINE: case MT_SSMINE_SHIELD: case MT_LANDMINE: - case MT_EGGMINE_CAPSULE: - case MT_EGGMINE_SHIELD: + // case MT_EGGMINE_CAPSULE: + // case MT_EGGMINE_SHIELD: case MT_BALLHOG: case MT_SINK: case MT_ROCKETSNEAKER: From 23603656ad799a26a01387c3405b85385b843dc0 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 16 Nov 2025 01:36:46 +0100 Subject: [PATCH 66/76] LUA_HookPlayerItem is now UINT8 pt1 doing this in the web client for codeberg cus lazy lol --- src/lua_hooklib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index c7b4f7e73..4b2321301 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -1279,7 +1279,7 @@ static void res_trueforce(Hook_State *hook) state->force = lua_toboolean(gL, -1); }; -boolean LUA_HookPlayerItem(player_t *player, SINT8 itemType, boolean wasHoldingItem, boolean *force) +boolean LUA_HookPlayerItem(player_t *player, UINT8 itemType, boolean wasHoldingItem, boolean *force) { Hook_State hook; TrueForce_State state = {0}; From f5c1cd63b45d890ec3a7ad514630b43ed4c4caa4 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 16 Nov 2025 01:37:26 +0100 Subject: [PATCH 67/76] LUA_HookPlayerItem is now UINT8 pt2 done --- src/lua_hook.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lua_hook.h b/src/lua_hook.h index 674d752fb..a09508570 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -1,4 +1,3 @@ - //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by John "JTE" Muniz. // Copyright (C) 2012-2022 by Sonic Team Junior. @@ -176,7 +175,7 @@ int LUA_HookShouldJingleContinue(player_t *, const char *musname); int LUA_HookMusicChange(const char *oldname, struct MusicChange *); // Item shit -boolean LUA_HookPlayerItem(player_t *player, SINT8 itemType, boolean wasHoldingItem, boolean *force); +boolean LUA_HookPlayerItem(player_t *player, UINT8 itemType, boolean wasHoldingItem, boolean *force); boolean LUA_HookKartHyudoro(player_t *player, INT32 *target, boolean sink); // SRB2Kart: Hook for K_DoHyudoroSteal and overriding its results. boolean LUA_HookMobjScaleChange(mobj_t *target, fixed_t newscale, fixed_t oldscale); // SRB2Kart: Hook for P_SetScale. boolean LUA_HookKartSneaker(player_t *player, int type); // SRB2Kart: Hook for K_DoSneaker. From 84e0f9503e2e356c09e946911c02338c9e23fc69 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 16 Nov 2025 00:23:12 -0500 Subject: [PATCH 68/76] SecsToFixed, SecsToFixedTens, and 64-bit variants Converts seconds/tens of seconds to fixed_t values: - SecsToFixed: 1 second is 1 fracunit - SecsToFixedTens: 10 seconds is 1 fracunit --- src/m_fixed.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/m_fixed.h b/src/m_fixed.h index 3190d0f1a..74b2f29b6 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -16,6 +16,8 @@ #define __M_FIXED__ #include "doomtype.h" +#include "doomdef.h" + #ifdef __GNUC__ #include #endif @@ -84,6 +86,53 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t DoubleToFixed(double f) return (fixed_t)(f * FRACUNIT); } +/** \brief Converts seconds (in tics) to fixed-point number values; 1 second is 1 fracunit. + + \param t (tic_t) time + + \return (fixed_t) seconds as fracunits +*/ +FUNCMATH FUNCINLINE static ATTRINLINE fixed_t SecsToFixed(tic_t t) +{ + return (fixed_t)(((UINT64)t * FRACUNIT) / (TICRATE)); +} + +/** \brief Converts tens of seconds (in tics) to fixed-point number values; 10 seconds is 1 + fracunit. + + \param t (tic_t) time + + \return (fixed_t) tens of seconds as fracunits +*/ +FUNCMATH FUNCINLINE static ATTRINLINE fixed_t SecsToFixedTens(tic_t t) +{ + return (fixed_t)(((UINT64)t * FRACUNIT) / (10 * TICRATE)); +} + +/** \brief 64-bit version of SecsToFixed. Converts seconds (in tics) to fixed-point number values; 1 + second is 1 fracunit. + + \param t (tic_t) time + + \return (sint64_t) seconds as fracunits +*/ +FUNCMATH FUNCINLINE static ATTRINLINE INT64 SecsToFixed64(tic_t t) +{ + return (INT64)(((UINT64)t * FRACUNIT) / (TICRATE)); +} + +/** \brief 64-bit version of SecsToFixedTens. Converts tens of seconds (in tics) to fixed-point + number values; 10 seconds is 1 fracunit. + + \param t (tic_t) time + + \return (sint64_t) tens of seconds as fracunits +*/ +FUNCMATH FUNCINLINE static ATTRINLINE INT64 SecsToFixedTens64(tic_t t) +{ + return (INT64)(((UINT64)t * FRACUNIT) / (10 * TICRATE)); +} + // for backwards compat #define FIXED_TO_FLOAT(x) FixedToFloat(x) // (((float)(x)) / ((float)FRACUNIT)) #define FLOAT_TO_FIXED(f) FloatToFixed(f) // (fixed_t)((f) * ((float)FRACUNIT)) From 245085fa2b094f90a9f17c5a3de8900a673bb425 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 16 Nov 2025 00:25:30 -0500 Subject: [PATCH 69/76] Prevent bottlenecking at minimum Alt. Invin. times The time limit is super short, and the offroad creep and speed loss kick in after 2 seconds. I think this'll be okay. --- src/k_kart.c | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 6659acb08..3dbf0142c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4775,6 +4775,8 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) void K_DoInvincibility(player_t *player, tic_t time) { + const boolean isalt = K_IsKartItemAlternate(KITEM_INVINCIBILITY); + if (!player->invincibilitytimer) { mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INVULNFLASH); @@ -4782,7 +4784,7 @@ void K_DoInvincibility(player_t *player, tic_t time) overlay->destscale = player->mo->scale; P_SetScale(overlay, player->mo->scale); - if (K_IsKartItemAlternate(KITEM_INVINCIBILITY)) + if (isalt) { mobj_t *aura = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_OVERLAY); P_SetTarget(&aura->target, player->mo); @@ -4792,7 +4794,7 @@ void K_DoInvincibility(player_t *player, tic_t time) } } - if (K_IsKartItemAlternate(KITEM_INVINCIBILITY)) + if (isalt) { // Rim suggestion: Don't allow already invincible players to chain. if (K_InvincibilityGradient(player->invincibilitytimer) < (FRACUNIT/2)) @@ -4806,7 +4808,14 @@ void K_DoInvincibility(player_t *player, tic_t time) player->invincibilitytimer = time; } - player->maxinvincibilitytime = time; + player->maxinvincibilitytime = player->invincibilitytimer; + + if (player->maxinvincibilitytime <= MININVINTIME && isalt) + { + // Merritt suggestion: Kill bottlenecking if you get a short Invincibility. + // Anti-bottleneck code 2: Signify to play the warning signal later than usual! + player->invincibilitybottleneck = (UINT16)(-2); + } if (P_IsLocalPlayer(player) == true) { @@ -6993,14 +7002,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->invincibilitytimer) { INT16 invinfac = 1; - if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (player->invincibilitytimer > 2)) + if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && + (player->invincibilitytimer > 2)) { UINT32 invindist = INVINDIST >> 2; // Value to subtract from the Invincibility timer during bottlenecking. INT16 invin_subtrahend = 1; - if ((INT16)(player->invincibilitybottleneck) != -1) + if ((INT16)(player->invincibilitybottleneck) >= 0) { if (player->distancefromcluster < invindist) { @@ -7028,9 +7038,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer = (UINT16)(max( 2, (INT32)(player->invincibilitytimer) - invin_subtrahend)); - if ((K_InvincibilityGradient(player->invincibilitytimer) < - (FRACHALF * invin_subtrahend)) && - (player->maxinvincibilitytime >= (BASEINVINTIME - TICRATE))) + const boolean warning_cond_standard = + ((K_InvincibilityGradient(player->invincibilitytimer) < + (FRACHALF * invin_subtrahend)) && + (player->maxinvincibilitytime >= (BASEINVINTIME - TICRATE))); + + const boolean warning_cond_nobottleneck = + ((K_InvincibilityGradient(player->invincibilitytimer) < + SecsToFixedTens(MININVINTIME / 2)) && + (player->maxinvincibilitytime >= (MININVINTIME - TICRATE))); + + const boolean warning_cond = + ((INT16)(player->invincibilitybottleneck) == -2) + ? warning_cond_nobottleneck + : warning_cond_standard; + + if (warning_cond) { if (!player->invincibilitywarning) { From c37924428d62a69f4ad4a4ca9cee7539e712b82e Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 16 Nov 2025 00:27:46 -0500 Subject: [PATCH 70/76] Expose Alt. Invin. values to Lua Not letting people modify maxinvincibilitytime because that'd mess up too much It's also super redundant; you can emulate this in like maybe a line of code in a script lol --- src/lua_playerlib.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 48ec14afd..0f4f0ee6a 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -287,6 +287,10 @@ enum player_e player_squishedtimer, player_rocketsneakertimer, player_invincibilitytimer, + player_maxinvincibilitytime, + player_invincibilitybottleneck, + player_invincibilitycancel, + player_invincibilitywarning, player_eggmanexplode, player_eggmanblame, player_bananadrag, @@ -493,6 +497,10 @@ static const char *const player_opt[] = { "squishedtimer", "rocketsneakertimer", "invincibilitytimer", + "maxinvincibilitytime", + "invincibilitybottleneck", + "invincibilitycancel", + "invincibilitywarning", // This gets cast to a boolean in Lua. "eggmanexplode", "eggmanblame", "bananadrag", @@ -973,6 +981,19 @@ static int player_get(lua_State *L) case player_invincibilitytimer: lua_pushinteger(L, plr->invincibilitytimer); break; + case player_maxinvincibilitytime: + lua_pushinteger(L, plr->maxinvincibilitytime); + break; + case player_invincibilitybottleneck: + // Push as an INT16 due to the negative value signal systems. + lua_pushinteger(L, (INT16)plr->invincibilitybottleneck); + break; + case player_invincibilitycancel: + lua_pushinteger(L, plr->invincibilitycancel); + break; + case player_invincibilitywarning: + lua_pushboolean(L, (boolean)plr->invincibilitywarning); + break; case player_eggmanexplode: lua_pushinteger(L, plr->eggmanexplode); break; @@ -1699,6 +1720,18 @@ static int player_set(lua_State *L) case player_invincibilitytimer: plr->invincibilitytimer = luaL_checkinteger(L, 3); break; + case player_maxinvincibilitytime: + // For most other cases, I'd just say: "it's your funeral". Not for this one. + return NOSET; + case player_invincibilitybottleneck: + plr->invincibilitybottleneck = (UINT16)luaL_checkinteger(L, 3); + break; + case player_invincibilitycancel: + plr->invincibilitycancel = (INT16)luaL_checkinteger(L, 3); + break; + case player_invincibilitywarning: + plr->invincibilitywarning = (UINT8)luaL_checkboolean(L, 3); + break; case player_eggmanexplode: plr->eggmanexplode = luaL_checkinteger(L, 3); break; From edbcfe3af82e7baef057196b1c078ebc2b408273 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 16 Nov 2025 00:51:44 -0500 Subject: [PATCH 71/76] Read baked offsets from the interpolation target --- src/hardware/hw_main.c | 4 ++-- src/r_things.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index f0c745a74..b74f66291 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -4913,9 +4913,9 @@ static void HWR_ProjectSprite(mobj_t *thing) const fixed_t visoffymul = (vflip ? -FRACUNIT : FRACUNIT); - if (R_ThingIsUsingBakedOffsets(thing)) + if (R_ThingIsUsingBakedOffsets(interptarg)) { - R_RotateSpriteOffsetsByPitchRoll(thing, + R_RotateSpriteOffsetsByPitchRoll(interptarg, vflip, hflip, &interp, diff --git a/src/r_things.cpp b/src/r_things.cpp index 483edd525..4b95b98cc 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2000,9 +2000,9 @@ static void R_ProjectSprite(mobj_t *thing) const fixed_t visoffymul = (vflip ? -FRACUNIT : FRACUNIT); - if (R_ThingIsUsingBakedOffsets(thing)) + if (R_ThingIsUsingBakedOffsets(interptarg)) { - R_RotateSpriteOffsetsByPitchRoll(thing, + R_RotateSpriteOffsetsByPitchRoll(interptarg, vflip, hflip, &interp, From e4107aaa4517cb9ffd5dbbcf2243b42736c9ab0c Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 16 Nov 2025 04:58:06 -0500 Subject: [PATCH 72/76] Readjust pathfind odds distance value Forgot to correct it after I fixed the multiplier oversight --- src/d_netcmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 58f6a3812..0ee09b735 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -563,7 +563,7 @@ consvar_t cv_kartantibump = CVAR_INIT ("kartantibump", "0", CV_NETVAR|CV_CALL|CV // Odds distancing #define MAXODDSDIST ((INT32_MAX / FRACUNIT) / 2) static CV_PossibleValue_t distvar_cons_t[] = {{1, "MIN"}, {MAXODDSDIST, "MAX"}, {0, NULL}}; -consvar_t cv_kartoddsdist = CVAR_INIT ("kartoddsdist", "987", CV_NETVAR|CV_CHEAT|CV_GUARD, distvar_cons_t, NULL); +consvar_t cv_kartoddsdist = CVAR_INIT ("kartoddsdist", "1480", CV_NETVAR|CV_CHEAT|CV_GUARD, distvar_cons_t, NULL); consvar_t cv_kartlegacyoddsdist = CVAR_INIT ("kartlegacyoddsdist", "1050", CV_NETVAR|CV_CHEAT|CV_GUARD, distvar_cons_t, NULL); // SPB distance From 67dd2c875fec1c76ace86f6b813041144cfa150d Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Sun, 16 Nov 2025 15:47:05 +0100 Subject: [PATCH 73/76] Add altitem flags, fix alt shrink setting indirect cooldown --- src/d_main.cpp | 2 +- src/deh_soc.c | 6 +++--- src/k_items.c | 19 ++++++++++--------- src/k_items.h | 2 +- src/m_menu.c | 36 ++++++++++++++++++++++-------------- 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index d0e3d13c4..a5c199e41 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -94,7 +94,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x3f0509a0ec4bffd7 +#define ASSET_HASH_MAIN_PK3 0x2928bc5e6dfecb56 #define ASSET_HASH_MAPPATCH_PK3 0xd4d4ce4a090d5473 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE diff --git a/src/deh_soc.c b/src/deh_soc.c index 2adcd7dd7..c128b1691 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3695,7 +3695,7 @@ void readfollower(MYFILE *f) char *s; char *word, *word2, dname[SKINNAMESIZE+1]; char *tmp; - char testname[SKINNAMESIZE]; + char testname[SKINNAMESIZE+1]; boolean nameset; INT32 fallbackstate = 0; @@ -4098,9 +4098,9 @@ void readkartitem(MYFILE *f, kartitem_t *item) break; } } - else if (fastcmp(word, "FLAGS")) + else if (fastcmp(word, "FLAGS") || fastcmp(word, "FLAGSALT")) { - item->flags = get_number(word2); + item->flags[word[5] == 'A' ? 1 : 0] = get_number(word2); } else WARN("unknown word '%s'", word); diff --git a/src/k_items.c b/src/k_items.c index cd0661914..1c8319700 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -178,8 +178,7 @@ kartitemflags_e K_GetItemFlags(kartitemtype_e itemtype) if (itemtype >= numkartitems) return 0; - // TODO: alt item flags - return kartitems[itemtype].flags; + return kartitems[itemtype].flags[K_IsKartItemAlternate(itemtype) ? 1 : 0]; } UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny) @@ -189,7 +188,7 @@ UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny) if (type >= numkartitems) return 1; // actually broken items show x1 to let you know - return kartitems[type].flags & KIF_ANIMATED ? 2 : kartitems[type].graphics[tiny ? 1 : 0].numpatches + 1; + return K_GetItemFlags(type) & KIF_ANIMATED ? 2 : kartitems[type].graphics[tiny ? 1 : 0].numpatches + 1; } patch_t *K_GetCachedItemPatch(kartitemtype_e type, boolean tiny, UINT8 amount) @@ -202,7 +201,7 @@ patch_t *K_GetCachedItemPatch(kartitemtype_e type, boolean tiny, UINT8 amount) tic_t timer = G_GamestateUsesLevel() ? leveltime : finalecount; - if (item->flags & KIF_ANIMATED) + if (K_GetItemFlags(type) & KIF_ANIMATED) return graphics->patches[(timer % (graphics->numpatches*3)) / 3]; else return graphics->patches[CLAMP(amount - 1, 0, graphics->numpatches - 1)]; @@ -219,7 +218,7 @@ void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCo itemType = 0; UINT8 numpatches = kartitems[itemType].graphics[0].numpatches; - if (kartitems[itemType].flags & KIF_ANIMATED) + if (K_GetItemFlags(itemType) & KIF_ANIMATED) part->frame |= (leveltime % (numpatches*3)) / 3; else part->frame |= CLAMP(itemCount - 1, 0, numpatches - 1); @@ -513,7 +512,7 @@ void K_UpdateItemCooldown(void) if (player->itemtype <= 0 || player->itemtype >= numkartitems || player->itemamount <= 0) continue; - if (kartitems[player->itemtype].flags & KIF_UNIQUESLOT) + if (K_GetItemFlags(player->itemtype) & KIF_UNIQUESLOT) setcooldown[player->itemtype] = true; } @@ -528,7 +527,7 @@ void K_UpdateItemCooldown(void) switch (mobj->type) { case MT_FLOATINGITEM: - if (mobj->threshold > 0 && mobj->threshold < numkartitems && kartitems[mobj->threshold].flags & KIF_UNIQUEDROP) + if (mobj->threshold > 0 && mobj->threshold < numkartitems && K_GetItemFlags(mobj->threshold) & KIF_UNIQUEDROP) setcooldown[mobj->threshold] = true; break; @@ -546,7 +545,9 @@ void K_UpdateItemCooldown(void) for (i = 0; i < numkartresults; i++) { kartresult_t *result = &kartresults[i]; - if ((gametyperules & GTR_RACEODDS || result->flags & KRF_INDIRECTITEM) && setcooldown[result->type]) + if (result->isalt == K_IsKartItemAlternate(result->type) + && (gametyperules & GTR_RACEODDS || result->flags & KRF_INDIRECTITEM) + && setcooldown[result->type]) result->cooldown = result->basecooldown; } @@ -1001,7 +1002,7 @@ INT32 K_GetRollingRouletteItem(player_t *player) { kartresult_t *result = &kartresults[i]; - if (kartitems[result->type].flags & KIF_HIDEFROMROULETTE) + if (K_GetItemFlags(result->type) & KIF_HIDEFROMROULETTE) continue; for (j = 0; j < numseen; j++) diff --git a/src/k_items.h b/src/k_items.h index 38ec0944e..b2be3e1fd 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -106,7 +106,7 @@ struct kartitemgraphics_t struct kartitem_t { dehinfo_t info; - kartitemflags_e flags; + kartitemflags_e flags[2]; kartitemgraphics_t graphics[2]; spritedef_t spritedef; diff --git a/src/m_menu.c b/src/m_menu.c index 89caf29b7..e918e580d 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -8378,11 +8378,17 @@ void MD_DrawMonitorToggles(void) INT32 x = currentMenu->x, y = currentMenu->y+(spacing/4); INT32 onx = 0, ony = 0; INT32 i, translucent; - const kartresult_t *result = K_GetKartResult(currentMenu->menuitems[itemOn].patch); const char *displayname; - //boolean hasalt; // ha, salt! (has alt) - if (result != NULL && kartitems[result->type].altcvar != NULL && kartitems[result->type].altcvar->value == 1) - result = K_GetKartResultAlt(result->cvar->name, true); + + // INCREDIBLY goofy hack, but I don't wanna have to pass a "use cvar or altenabled" + // flag to literally ALL of the item functions just for the sake of the menu + boolean oldalt[MAXKARTITEMS]; + for (i = 0; i < numkartitems; i++) + { + kartitem_t *item = &kartitems[i]; + oldalt[i] = item->altenabled; + item->altenabled = item->altcvar != NULL && item->altcvar->value == 1; + } M_DrawMenuTitle(); @@ -8432,8 +8438,8 @@ void MD_DrawMonitorToggles(void) continue; } - kartresult_t *result = K_GetKartResult(currentMenu->menuitems[thisitem].patch); - kartitem_t *item = &kartitems[result->type]; + const kartresult_t *result = K_GetKartResult(currentMenu->menuitems[thisitem].patch); + const kartitem_t *item = &kartitems[result->type]; boolean enabled = result->cvar->value == 1; if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) @@ -8446,7 +8452,7 @@ void MD_DrawMonitorToggles(void) else V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBGD", PU_CACHE)); - if (result->amount > item->graphics[1].numpatches && !(item->flags & KIF_ANIMATED)) + if (result->amount >= K_GetItemNumberDisplayMin(result->type, true)) { V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISMUL", PU_CACHE)); V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); @@ -8455,10 +8461,8 @@ void MD_DrawMonitorToggles(void) else V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); - if (item->altcvar != NULL && item->altcvar->value == 1) - { + if (item->altenabled) V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ALTITS", PU_CACHE)); - } } x += spacing; @@ -8491,13 +8495,14 @@ void MD_DrawMonitorToggles(void) } default: { - displayname = result->displayname; - kartitem_t *item = &kartitems[result->type]; + const kartresult_t *result = K_GetKartResult(currentMenu->menuitems[itemOn].patch); + const kartitem_t *item = &kartitems[result->type]; boolean enabled = result->cvar->value == 1; if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) enabled = false; + displayname = result->displayname; translucent = enabled ? 0 : V_TRANSLUCENT; if (enabled) @@ -8505,7 +8510,7 @@ void MD_DrawMonitorToggles(void) else V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE)); - if (result->amount > item->graphics[0].numpatches && !(item->flags & KIF_ANIMATED)) + if (result->amount >= K_GetItemNumberDisplayMin(result->type, false)) { V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITMUL", PU_CACHE)); V_DrawScaledPatch(onx-1, ony-2, translucent, K_GetCachedItemPatch(result->type, false, result->amount)); @@ -8517,7 +8522,7 @@ void MD_DrawMonitorToggles(void) if (item->altcvar != NULL) { - translucent = item->altcvar->value == 1 ? 0 : V_TRANSLUCENT; + translucent = item->altenabled ? 0 : V_TRANSLUCENT; V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName("K_ALTITM", PU_CACHE)); } break; @@ -8530,6 +8535,9 @@ void MD_DrawMonitorToggles(void) V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, MENUCAPS|highlightflags, va("* %s *", displayname)); M_DrawMenuTooltips(); + + for (i = 0; i < numkartitems; i++) + kartitems[i].altenabled = oldalt[i]; } INT32 MR_HandleMonitorToggles(INT32 choice) From fdf2464ccf8a33e63a475cc589fe08bd3c14611f Mon Sep 17 00:00:00 2001 From: chromaticpipe Date: Fri, 1 Aug 2025 10:33:19 -0500 Subject: [PATCH 74/76] Configurable sector brightness in software --- src/hardware/hw_main.c | 2 +- src/hardware/hw_main.h | 1 - src/r_main.cpp | 4 ++++ src/r_main.h | 1 + src/r_plane.cpp | 11 +++++++---- src/r_segs.cpp | 18 +++++++++--------- src/r_things.cpp | 6 +++--- 7 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index b74f66291..adf9cc13f 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -296,7 +296,7 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col } // Clamp the light level, since it can sometimes go out of the 0-255 range from animations - light_level = min(max(light_level, cv_glsecbright.value), 255); + light_level = min(max(light_level, cv_secbright.value), 255); V_CubeApply(&tint_color.s.red, &tint_color.s.green, &tint_color.s.blue); V_CubeApply(&fade_color.s.red, &fade_color.s.green, &fade_color.s.blue); diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h index 52581545f..d73e161c9 100644 --- a/src/hardware/hw_main.h +++ b/src/hardware/hw_main.h @@ -97,7 +97,6 @@ extern consvar_t cv_glcoronasize; #endif extern consvar_t cv_glshaders, cv_glallowshaders; -extern consvar_t cv_glsecbright; extern consvar_t cv_glmodels; // SRB2Kart: We don't like these options. diff --git a/src/r_main.cpp b/src/r_main.cpp index 2612d4daa..f857dcb2e 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -140,6 +140,7 @@ static CV_PossibleValue_t fov_cons_t[] = {{60*FRACUNIT, "MIN"}, {179*FRACUNIT, " static CV_PossibleValue_t translucenthud_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}}; static CV_PossibleValue_t maxportals_cons_t[] = {{0, "MIN"}, {12, "MAX"}, {0, NULL}}; // lmao rendering 32 portals, you're a card static CV_PossibleValue_t homremoval_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Flash"}, {0, NULL}}; +static CV_PossibleValue_t secbright_cons_t[] = {{0, "MIN"}, {255, "MAX"}, {0, NULL}}; static void Fov_OnChange(void); @@ -182,6 +183,8 @@ consvar_t cv_fov[MAXSPLITSCREENPLAYERS] = { // Okay, whoever said homremoval causes a performance hit should be shot. consvar_t cv_homremoval = CVAR_INIT ("homremoval", "Yes", CV_SAVE, homremoval_cons_t, NULL); +consvar_t cv_secbright = CVAR_INIT ("r_secbright", "0", CV_SAVE, secbright_cons_t, NULL); + consvar_t cv_maxportals = CVAR_INIT ("maxportals", "2", CV_SAVE, maxportals_cons_t, NULL); consvar_t cv_renderstats = CVAR_INIT ("renderstats", "Off", 0, CV_OnOff, NULL); @@ -1780,6 +1783,7 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_shadow); CV_RegisterVar(&cv_skybox); CV_RegisterVar(&cv_ffloorclip); + CV_RegisterVar(&cv_secbright); for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { diff --git a/src/r_main.h b/src/r_main.h index c9fd110a7..ad018adaa 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -154,6 +154,7 @@ extern consvar_t cv_fov[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_skybox; extern consvar_t cv_tailspickup; extern consvar_t cv_debugfinishline; +extern consvar_t cv_secbright; extern consvar_t cv_sloperoll; extern consvar_t cv_sliptidetilt; diff --git a/src/r_plane.cpp b/src/r_plane.cpp index c3836b43e..1f44bb27b 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -1038,7 +1038,7 @@ void R_DrawSinglePlane(drawspandata_t *ds, visplane_t *pl, boolean allow_paralle spanfunctype = SPANDRAWFUNC_SPLAT; if (pl->polyobj->translucency == 0 || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG))) - light = (pl->lightlevel >> LIGHTSEGSHIFT); + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else light = LIGHTLEVELS-1; } @@ -1079,16 +1079,17 @@ void R_DrawSinglePlane(drawspandata_t *ds, visplane_t *pl, boolean allow_paralle } if ((spanfunctype == SPANDRAWFUNC_SPLAT) || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG))) - light = (pl->lightlevel >> LIGHTSEGSHIFT); + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else light = LIGHTLEVELS-1; } else if (pl->ffloor->fofflags & FOF_FOG) { spanfunctype = SPANDRAWFUNC_FOG; - light = (pl->lightlevel >> LIGHTSEGSHIFT); + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); } - else light = (pl->lightlevel >> LIGHTSEGSHIFT); + else + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); debug = SW_HI_FOFPLANES; } @@ -1147,6 +1148,8 @@ void R_DrawSinglePlane(drawspandata_t *ds, visplane_t *pl, boolean allow_paralle vidwidth, vidwidth); } } + else + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); } ds->currentplane = pl; diff --git a/src/r_segs.cpp b/src/r_segs.cpp index e631578f6..af01e3f3d 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -274,7 +274,7 @@ static void R_RenderMaskedSegLoop(drawcolumndata_t* dc, drawseg_t *drawseg, INT3 if ((R_CheckColumnFunc(COLDRAWFUNC_FUZZY) == false) || (rlight->flags & FOF_FOG) || (rlight->extra_colormap && (rlight->extra_colormap->flags & CMF_FOG))) - lightnum = (rlight->lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((rlight->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else lightnum = LIGHTLEVELS - 1; @@ -919,9 +919,9 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) // Check if the current light effects the colormap/lightlevel if (pfloor->fofflags & FOF_FOG) - rlight->lightnum = (pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT); + rlight->lightnum = std::max((pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else - rlight->lightnum = (rlight->lightlevel >> LIGHTSEGSHIFT); + rlight->lightnum = std::max((rlight->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (pfloor->fofflags & FOF_FOG || rlight->flags & FOF_FOG || (rlight->extra_colormap && (rlight->extra_colormap->flags & CMF_FOG))) ; @@ -937,14 +937,14 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) { // Get correct light level! if ((frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG))) - lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((frontsector->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else if (pfloor->fofflags & FOF_FOG) - lightnum = (pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else if (R_CheckColumnFunc(COLDRAWFUNC_FUZZY) == true) lightnum = LIGHTLEVELS-1; else - lightnum = R_FakeFlat(frontsector, &tempsec, &templight, &templight, false) - ->lightlevel >> LIGHTSEGSHIFT; + lightnum = std::max((R_FakeFlat(frontsector, &tempsec, &templight, &templight, false) + ->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (pfloor->fofflags & FOF_FOG || (frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG))) ; @@ -1556,7 +1556,7 @@ static void R_RenderSegLoop (drawcolumndata_t* dc) for (i = 0; i < dc->numlights; i++) { INT32 lightnum; - lightnum = (dc->lightlist[i].lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((dc->lightlist[i].lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (dc->lightlist[i].extra_colormap) ; @@ -2665,7 +2665,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) // use different light tables // for horizontal / vertical / diagonal // OPTIMIZE: get rid of LIGHTSEGSHIFT globally - lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((frontsector->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (P_ApplyLightOffset(lightnum, frontsector)) lightnum += curline->lightOffset; diff --git a/src/r_things.cpp b/src/r_things.cpp index 4b95b98cc..c537c5745 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1320,7 +1320,7 @@ static void R_SplitSprite(vissprite_t *sprite) newsprite->cut = static_cast(newsprite->cut | SC_TOP); if (!(sector->lightlist[i].caster->fofflags & FOF_NOSHADE)) { - lightnum = (*sector->lightlist[i].lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((*sector->lightlist[i].lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (lightnum < 0) spritelights = scalelight[0]; @@ -2354,7 +2354,7 @@ static void R_ProjectSprite(mobj_t *thing) lightnum = thing->subsector->sector->lightlevel; } - lightnum = (lightnum + R_ThingLightLevel(thing)) >> LIGHTSEGSHIFT; + lightnum = std::max((lightnum + R_ThingLightLevel(thing)) >> LIGHTSEGSHIFT, cv_secbright.value); if (maplighting.directional == true && P_SectorUsesDirectionalLighting(thing->subsector->sector)) { @@ -2780,7 +2780,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) { if (sec->heightsec == -1) lightlevel = sec->lightlevel; - lightnum = (lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (lightnum < 0) spritelights = scalelight[0]; From 321baf1cd72220fda47ed51de1b2d32e8f6b18b1 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 16 Nov 2025 20:13:08 -0500 Subject: [PATCH 75/76] Reshuffle sound ids so that it matches up to v1 You can blame v1 map makers for this one. --- src/info/sounds.h | 145 +++++++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 66 deletions(-) diff --git a/src/info/sounds.h b/src/info/sounds.h index 8a692c256..2df90115b 100644 --- a/src/info/sounds.h +++ b/src/info/sounds.h @@ -41,8 +41,6 @@ _(pstop) _(steam1) _(steam2) _(wbreak) -_(ambmac) -_(spsmsh) _(rainin) _(litng1) @@ -114,7 +112,6 @@ _(dmpain) _(drown) _(fizzle) _(gbeep) -_(wepfir) _(ghit) _(gloop) _(gspray) @@ -126,19 +123,12 @@ _(lose) _(lvpass) _(mindig) _(mixup) -_(monton) _(pogo) _(pop) _(rail1) _(rail2) _(rlaunc) _(shield) -_(wirlsg) -_(forcsg) -_(frcssg) -_(elemsg) -_(armasg) -_(attrsg) _(shldls) _(spdpad) _(spkdth) @@ -154,24 +144,8 @@ _(trfire) _(trpowr) _(turhit) _(wdjump) -_(shrpsp) -_(shrpgo) _(mswarp) _(mspogo) -_(boingf) -_(corkp) -_(corkh) -_(alart) -_(vwre) -_(bowl) -_(chuchu) -//sfx_bsnipe, -_(sprong) -_(lvfal1) -_(pscree) -_(iceb) -_(shattr) -_(antiri) // Menu, interface _(chchng) @@ -185,16 +159,12 @@ _(radio) _(wepchg) _(wtrdng) _(zelda) -_(adderr) -_(notadd) -_(addfil) // NiGHTS _(ideya) _(xideya) // Xmas _(nbmper) _(nxbump) // Xmas -_(ncchip) _(ncitem) _(nxitem) // Xmas _(ngdone) @@ -210,14 +180,6 @@ _(hoop3) _(hidden) _(prloop) _(timeup) -_(ngjump) -_(peww) - -// Halloween -_(lntsit) -_(lntdie) -_(pumpkn) -_(ghosty) // Mario _(koopfr) @@ -400,7 +362,6 @@ _(s26f) _(s270) // S3&K sounds -_(s3k2b) _(s3k33) _(s3k34) _(s3k35) @@ -522,21 +483,6 @@ _(s3ka8) _(s3ka9) _(s3kaa) _(s3kab) -_(s3kab1) -_(s3kab2) -_(s3kab3) -_(s3kab4) -_(s3kab5) -_(s3kab6) -_(s3kab7) -_(s3kab8) -_(s3kab9) -_(s3kaba) -_(s3kabb) -_(s3kabc) -_(s3kabd) -_(s3kabe) -_(s3kabf) _(s3kac) _(s3kad) _(s3kae) @@ -845,18 +791,6 @@ _(itfree) _(dbgsal) _(bhurry) // mine: wasn't here already? -// Chaining Sound -_(bstchn) - -// Low Ring Sound -_(ringlw) - -// Flip Over Sound -_(flipos) - -// Shout message sound effect -_(sysmsg) - // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight _(krta00) @@ -997,3 +931,82 @@ _(kbost2) _(kslow) _(khitem) _(kgloat) + +// Relocated 2.2 sounds to fix magic numbers in v1 maps +_(ambmac) +_(spsmsh) + +_(wepfir) + +_(monton) + +_(wirlsg) +_(forcsg) +_(frcssg) +_(elemsg) +_(armasg) +_(attrsg) + +_(shrpsp) +_(shrpgo) + +_(boingf) +_(corkp) +_(corkh) +_(alart) +_(vwre) +_(bowl) +_(chuchu) +_(sprong) +_(lvfal1) +_(pscree) +_(iceb) +_(shattr) +_(antiri) + +_(adderr) +_(notadd) +_(addfil) + +_(ncchip) + +_(ngjump) +_(peww) + +// Halloween +_(lntsit) +_(lntdie) +_(pumpkn) +_(ghosty) + +_(s3k2b) + +_(s3kab1) +_(s3kab2) +_(s3kab3) +_(s3kab4) +_(s3kab5) +_(s3kab6) +_(s3kab7) +_(s3kab8) +_(s3kab9) +_(s3kaba) +_(s3kabb) +_(s3kabc) +_(s3kabd) +_(s3kabe) +_(s3kabf) + +// New Blan sounds + +// Chaining Sound +_(bstchn) + +// Low Ring Sound +_(ringlw) + +// Flip Over Sound +_(flipos) + +// Shout message sound effect +_(sysmsg) From b54d13120a7d271fa94dbd47a826c2a32879d398 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 16 Nov 2025 20:21:02 -0500 Subject: [PATCH 76/76] Finally add noverifyiwads cmake flag --- CMakeLists.txt | 6 ++++++ src/d_main.cpp | 2 +- src/doomdef.h | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22205ae73..720d820e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,10 +85,16 @@ option(SRB2_CONFIG_PROFILEMODE "Compile for profiling (GCC only)." OFF) option(SRB2_CONFIG_TRACY "Compile with Tracy profiling enabled" OFF) option(SRB2_CONFIG_ASAN "Compile with AddressSanitizer (libasan)." OFF) option(SRB2_CONFIG_UBSAN "Compile with UndefinedBehaviorSanitizer (libubsan)." OFF) +option(SRB2_CONFIG_NOVERIFYIWADS "Compile with IWAD verification turned off." OFF) set(SRB2_CONFIG_ASSET_DIRECTORY "" CACHE PATH "Path to directory that contains all asset files for the installer. If set, assets will be part of installation and cpack.") option(SRB2_CONFIG_LTO "Enable link time optimizations, improves performance at the cost of longer link times." ON) option(SRB2_CONFIG_TIDY "Enable clang tiny, checks compiled code for issues at the cost of longer compile times." OFF) +if(SRB2_CONFIG_NOVERIFYIWADS) + target_compile_definitions(SRB2SDL2 PRIVATE -DNOVERIFYIWADS) +endif() + + if(SRB2_CONFIG_ENABLE_TESTS) # https://github.com/catchorg/Catch2 CPMAddPackage( diff --git a/src/d_main.cpp b/src/d_main.cpp index a5c199e41..174dbf675 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -94,7 +94,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x2928bc5e6dfecb56 +#define ASSET_HASH_MAIN_PK3 0x9dc33fb314952e03 #define ASSET_HASH_MAPPATCH_PK3 0xd4d4ce4a090d5473 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE diff --git a/src/doomdef.h b/src/doomdef.h index afd6f17f4..d4a5bf465 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -91,7 +91,7 @@ extern "C" { // Special Hashing. //#define NOFILEHASH -#define NOVERIFYIWADS +//#define NOVERIFYIWADS // Uncheck this to compile debugging code //#define RANGECHECK