From 92d86be8135f845c5cc2735663c3bde5fe787634 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Wed, 22 Jan 2025 13:15:42 -0500 Subject: [PATCH] Implement distance based itemodds when legacy waypoints are in play --- src/doomstat.h | 1 + src/g_game.c | 1 + src/k_hud.c | 111 +++++++----- src/k_kart.c | 417 +++++++++++++++++++++++++++++++++++++++++++++- src/k_kart.h | 2 + src/lua_baselib.c | 39 +++++ src/lua_script.c | 3 + src/p_saveg.c | 2 + src/p_setup.c | 1 + src/p_tick.c | 3 + 10 files changed, 537 insertions(+), 43 deletions(-) diff --git a/src/doomstat.h b/src/doomstat.h index ecb1a42b0..6031355ae 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -715,6 +715,7 @@ extern boolean comeback; 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 becae291b..0a06ac7f2 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -310,6 +310,7 @@ SINT8 pickedvote; // What vote the host rolls SINT8 battlewanted[4]; // WANTED players in battle, worth x2 points 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 +tic_t hyubgone; // Cooldown before hyudoro is allowed to be rerolled 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_hud.c b/src/k_hud.c index dee493684..4032d0718 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4407,57 +4407,86 @@ static void K_drawDistributionDebugger(void) pdis = (15 * pdis) / 14; } - useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush); + if (numbosswaypoints > 0) + useodds = K_FindLegacyUseodds(stplyr, 0, pdis, bestbumper, spbrush); + else + useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush); - for (i = 1; i < NUMKARTRESULTS; i++) + if (pingame == 1) + { + if (stplyr->itemroulette && (stplyr->cmd.buttons & BT_ATTACK) && (cv_superring.value && !ringsdisabled)) + V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOTOP, kp_superring[1]); + else + V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOTOP, items[1]); + V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SNAPTOTOP, va("%d", 200)); + } + else { - INT32 itemodds = K_KartGetItemOdds( - useodds, i, - stplyr->distancetofinish, - 0, - spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival) - ); - if (itemodds <= 0) - continue; - - V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOTOP, items[i]); - V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds)); - - // Display amount for multi-items - if (i >= NUMKARTITEMS) + for (i = 1; i < NUMKARTRESULTS; i++) { - INT32 amount; - switch (i) + INT32 itemodds; + + + + if (numbosswaypoints > 0) + itemodds = K_KartGetLegacyItemOdds(useodds, i, 0, spbrush); + else + itemodds = K_KartGetItemOdds( + useodds, i, + stplyr->distancetofinish, + 0, + spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival) + ); + + if (itemodds <= 0) + continue; + + V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOTOP, items[i]); + V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds)); + + // Display amount for multi-items + if (i >= NUMKARTITEMS) { - case KRITEM_TENFOLDBANANA: - amount = 10; - break; - case KRITEM_QUADORBINAUT: - amount = 4; - break; - case KRITEM_DUALJAWZ: - amount = 2; - break; - case KRITEM_DUALSNEAKER: - amount = 2; - break; - default: - amount = 3; - break; + INT32 amount; + switch (i) + { + case KRITEM_TENFOLDBANANA: + amount = 10; + break; + case KRITEM_QUADORBINAUT: + amount = 4; + break; + case KRITEM_DUALJAWZ: + amount = 2; + break; + case KRITEM_DUALSNEAKER: + amount = 2; + break; + default: + amount = 3; + break; + } + V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", amount)); } - V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", amount)); - } - x += 32; - if (x >= 297) - { - x = -9; - y += 32; + x += 32; + if (x >= 297) + { + x = -9; + y += 32; + } } } - V_DrawString(0, 0, V_HUDTRANS|V_SNAPTOTOP, va("USEODDS %d", useodds)); + if (pingame == 1) + V_DrawString(0, 0, V_HUDTRANS|V_SNAPTOTOP, "TA MODE"); + else + V_DrawString(0, 0, V_HUDTRANS|V_SNAPTOTOP, va("USEODDS %d", useodds)); + + if (numbosswaypoints > 0) + V_DrawSmallString(70, 0, V_HUDTRANS|V_SNAPTOTOP, "Legacy Distance Mode"); + } static void K_drawCheckpointDebugger(void) diff --git a/src/k_kart.c b/src/k_kart.c index f1d1d4db9..4bfdebe3d 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -425,6 +425,9 @@ static void K_KartGetItemResult(player_t *player, SINT8 getitem) if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) // Indirect items indirectitemcooldown = 20*TICRATE; + if (getitem == KITEM_HYUDORO) // Hyudoro cooldown + hyubgone = 20*TICRATE; + player->botvars.itemdelay = TICRATE; player->botvars.itemconfirm = 0; @@ -654,7 +657,6 @@ INT32 K_KartGetItemOdds( case KITEM_LANDMINE: case KITEM_DROPTARGET: case KITEM_BALLHOG: - case KITEM_HYUDORO: case KRITEM_TRIPLESNEAKER: case KRITEM_TRIPLEORBINAUT: case KRITEM_QUADORBINAUT: @@ -715,6 +717,12 @@ INT32 K_KartGetItemOdds( if (spbplace != -1) newodds = 0; break; + case KITEM_HYUDORO: + cooldownOnStart = true; + + if (hyubgone > 0) + newodds = 0; + break; default: break; } @@ -771,6 +779,181 @@ INT32 K_KartGetItemOdds( return newodds; } +INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush) +{ + INT32 newodds; + INT32 i; + UINT8 pingame = 0, pexiting = 0; + SINT8 first = -1, second = -1; + UINT32 secondToFirst = 0; + INT32 shieldtype = KSHIELD_NONE; + + boolean powerItem = false; + boolean cooldownOnStart = false; + boolean indirectItem = false; + + I_Assert(item > KITEM_NONE); // too many off by one scenarioes. + + if (!KartItemCVars[item-1]->value && !modeattacking) + return 0; + + if (gametype == GT_BATTLE) + newodds = K_KartItemOddsBattle[item-1][pos]; + else + newodds = K_KartItemOddsRace[item-1][pos]; + + // Base multiplication to ALL item odds to simulate fractional precision + newodds *= 4; + + shieldtype = K_GetShieldFromItem(item); + + 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 != KSHIELD_NONE && shieldtype == K_GetShieldFromItem(players[i].itemtype)) + { + // Don't allow more than one of each shield type at a time + return 0; + } + + if (players[i].mo && gametype == GT_RACE) + { + if (players[i].position == 1 && first == -1) + first = i; + if (players[i].position == 2 && second == -1) + second = i; + } + } + + if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB + { + secondToFirst = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x, + players[first].mo->y - players[second].mo->y), + players[first].mo->z - players[second].mo->z) / FRACUNIT; + secondToFirst = K_ScaleItemDistance(secondToFirst, pingame, spbrush); + } + + switch (item) + { + case KITEM_ROCKETSNEAKER: + case KITEM_JAWZ: + case KITEM_BALLHOG: + case KITEM_LANDMINE: + case KITEM_DROPTARGET: + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEBANANA: + case KRITEM_TENFOLDBANANA: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KRITEM_DUALJAWZ: + powerItem = true; + break; + case KITEM_INVINCIBILITY: + case KITEM_MINE: + case KITEM_GROW: + case KITEM_BUBBLESHIELD: + case KITEM_FLAMESHIELD: + cooldownOnStart = true; + powerItem = true; + break; + case KITEM_SPB: + cooldownOnStart = true; + indirectItem = true; + //powerItem = true; + + if (pexiting > 0) + { + newodds = 0; + } + else if (pos != 9) // Force SPB + { + const INT32 distFromStart = max(0, (INT32)secondToFirst - SPBSTARTDIST); + const INT32 distRange = SPBFORCEDIST - SPBSTARTDIST; + const fixed_t mulMax = 3*FRACUNIT; + + fixed_t multiplier = (distFromStart * mulMax) / distRange; + + if (multiplier < 0) + multiplier = 0; + + if (multiplier > mulMax) + multiplier = mulMax; + + newodds = FixedMul(newodds * FRACUNIT, multiplier) / FRACUNIT; + } + break; + case KITEM_SHRINK: + cooldownOnStart = true; + powerItem = true; + indirectItem = true; + + if (pingame-1 <= pexiting) + newodds = 0; + break; + case KITEM_THUNDERSHIELD: + cooldownOnStart = true; + powerItem = true; + break; + case KITEM_HYUDORO: + cooldownOnStart = true; + + if (hyubgone > 0) + newodds = 0; + break; + default: + break; + } + + if (newodds == 0) + { + // Nothing else we want to do with odds matters at this point :p + return newodds; + } + + if ((indirectItem == true) && (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)) + { + // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + newodds = 0; + } + else if (powerItem == true) + { + // This item is a "power item". This activates "frantic item" toggle related functionality. + fixed_t fracOdds = newodds * FRACUNIT; + + if (franticitems == true) + { + // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. + fracOdds *= 2; + } + + fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame, spbrush)); + + if (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); + } + + newodds = fracOdds / FRACUNIT; + } + + return newodds; +} + + //{ SRB2kart Roulette Code - Distance Based, yes waypoints UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) @@ -868,6 +1051,232 @@ UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbum return useodds; } + + +INT32 K_FindLegacyUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 bestbumper, boolean spbrush) +{ + SINT8 sortedPlayers[MAXPLAYERS]; + UINT8 sortLength = 0; + + UINT32 pdis = 0; + + UINT8 disttable[14]; + UINT8 distlen = 0; + + boolean oddsvalid[8]; + INT32 useodds = 0; + + INT32 i; + + // Unused now, oops :V + (void)bestbumper; + + for (i = 0; i < 8; i++) + { + INT32 j; + boolean available = false; + + if (gametype == GT_BATTLE && i > 1) + { + oddsvalid[i] = false; + break; + } + + for (j = 1; j < NUMKARTRESULTS; j++) + { + + if (K_KartGetLegacyItemOdds(i, j, mashed, spbrush) > 0) + { + available = true; + break; + } + + } + + oddsvalid[i] = available; + } + + memset(sortedPlayers, -1, sizeof(sortedPlayers)); + + if (player->mo != NULL && P_MobjWasRemoved(player->mo) == false) + { + // Sort all of the players ahead of you. + // Then tally up their distances in a conga line. + + // This will create a much more consistent item + // distance algorithm than the "spider web" thing + // that it was doing before. + + // Add yourself to the list. + // You'll always be the end of the list, + // so we can also calculate the length here. + sortedPlayers[ player->position - 1 ] = player - players; + sortLength = player->position; + + // Will only need to do this if there's goint to be + // more than yourself in the list. + if (sortLength > 1) + { + SINT8 firstIndex = -1; + SINT8 secondIndex = -1; + INT32 startFrom = INT32_MAX; + + // Add all of the other players. + for (i = 0; i < MAXPLAYERS; i++) + { + INT32 pos = INT32_MAX; + + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) + { + continue; + } + + pos = players[i].position; + + if (pos <= 0 || pos > MAXPLAYERS) + { + // Invalid position. + continue; + } + + if (pos >= player->position) + { + // Tied / behind us. + // Also handles ourselves, obviously. + continue; + } + + // Ties are done with port priority, if there are any. + if (sortedPlayers[ pos - 1 ] == -1) + { + sortedPlayers[ pos - 1 ] = i; + } + } + + // The chance of this list having gaps is improbable, + // but not impossible. So we need to spend some extra time + // to prevent the gaps from mattering. + for (i = 0; i < sortLength-1; i++) + { + if (sortedPlayers[i] >= 0 && sortedPlayers[i] < MAXPLAYERS) + { + // First valid index in the list found. + firstIndex = sortedPlayers[i]; + + // Start the next loop after this player. + startFrom = i + 1; + break; + } + } + + if (firstIndex >= 0 && firstIndex < MAXPLAYERS + && startFrom < sortLength) + { + // First index is valid, so we can + // start comparing the players. + + player_t *firstPlayer = NULL; + player_t *secondPlayer = NULL; + + for (i = startFrom; i < sortLength; i++) + { + if (sortedPlayers[i] >= 0 && sortedPlayers[i] < MAXPLAYERS) + { + secondIndex = sortedPlayers[i]; + + firstPlayer = &players[firstIndex]; + secondPlayer = &players[secondIndex]; + + // Add the distance to the player behind you. + pdis += P_AproxDistance(P_AproxDistance( + firstPlayer->mo->x - secondPlayer->mo->x, + firstPlayer->mo->y - secondPlayer->mo->y), + firstPlayer->mo->z - secondPlayer->mo->z) / FRACUNIT; + + // Advance to next index. + firstIndex = secondIndex; + } + } + } + } + } + +#define SETUPDISTTABLE(odds, num) \ + if (oddsvalid[odds]) \ + for (i = num; i; --i) \ + disttable[distlen++] = odds; + + if (gametype == GT_BATTLE) // Battle Mode + { + if (player->roulettetype == 1 && oddsvalid[1] == true) + { + // 1 is the extreme odds of player-controlled "Karma" items + useodds = 1; + } + else + { + useodds = 0; + + if (oddsvalid[0] == false && oddsvalid[1] == true) + { + // try to use karma odds as a fallback + useodds = 1; + } + } + } + else + { + SETUPDISTTABLE(0,1); + SETUPDISTTABLE(1,1); + SETUPDISTTABLE(2,1); + SETUPDISTTABLE(3,2); + SETUPDISTTABLE(4,2); + SETUPDISTTABLE(5,3); + SETUPDISTTABLE(6,3); + SETUPDISTTABLE(7,1); + + pdis = K_ScaleItemDistance(pdis, pingame, spbrush); + + if (pingame == 1 && oddsvalid[0]) + { + // Record Attack / FREE PLAY + useodds = 0; + } + else if (pdis <= 0) + { + // 1st place + useodds = disttable[0]; + } + else if (pdis > DISTVAR * ((12 * distlen) / 14)) + { + // Back of the pack + useodds = disttable[distlen-1]; + } + else + { + for (i = 1; i < 13; i++) + { + if (pdis <= (unsigned)(DISTVAR * ((i * distlen) / 14))) + { + useodds = disttable[((i * distlen) / 14)]; + break; + } + } + } + } + +#undef SETUPDISTTABLE + + //CONS_Printf("Got useodds %d. (position: %d, distance: %d)\n", useodds, player->position, pdis); + + return useodds; +} + static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { INT32 i; @@ -1111,7 +1520,11 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) spawnchance[i] = 0; // Split into another function for a debug function below - useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); + // Use a legacy version for maps not using waypoints. + if (numbosswaypoints > 0) + useodds = K_FindLegacyUseodds(player, mashed, pdis, bestbumper, spbrush); + else + useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); for (i = 1; i < NUMKARTRESULTS; i++) { diff --git a/src/k_kart.h b/src/k_kart.h index 2aa04fd76..2ed3dacba 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -38,9 +38,11 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value); extern consvar_t *KartItemCVars[NUMKARTRESULTS-1]; UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); +INT32 K_FindLegacyUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 bestbumper, boolean spbrush); fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush); UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival); +INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush); INT32 K_GetShieldFromItem(INT32 item); fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against); boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2); diff --git a/src/lua_baselib.c b/src/lua_baselib.c index e0125e1a3..4f599e572 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -3903,6 +3903,41 @@ static int lib_kDeclareWeakspot(lua_State *L) return 0; } +// sets the remaining time before players blow up +static int lib_kSetRaceCountdown(lua_State *L) +{ + tic_t c = (tic_t)luaL_checkinteger(L, 1); + racecountdown = c; + return 0; +} + +// sets the remaining time before the race ends after everyone finishes +static int lib_kSetExitCountdown(lua_State *L) +{ + tic_t c = (tic_t)luaL_checkinteger(L, 1); + NOHUD + exitcountdown = c; + return 0; +} + +// Sets the item cooldown before another shrink / SPB can be rolled +static int lib_kSetIndirectItemCountdown(lua_State *L) +{ + tic_t c = (tic_t)luaL_checkinteger(L, 1); + NOHUD + indirectitemcooldown = c; + return 0; +} + +// Sets the item cooldown before another shrink / SPB can be rolled +static int lib_kSetHyuCountdown(lua_State *L) +{ + tic_t c = (tic_t)luaL_checkinteger(L, 1); + NOHUD + hyubgone = c; + return 0; +} + static int lib_getTimeMicros(lua_State *L) { lua_pushinteger(L, I_GetPreciseTime() / (I_GetPrecisePrecision() / 1000000)); @@ -4195,6 +4230,10 @@ static luaL_Reg lib[] = { {"K_GetKartAccel",lib_kGetKartAccel}, {"K_GetKartFlashing",lib_kGetKartFlashing}, {"K_GetItemPatch",lib_kGetItemPatch}, + {"K_SetRaceCountdown",lib_kSetRaceCountdown}, + {"K_SetExitCountdown",lib_kSetExitCountdown}, + {"K_SetIndirectItemCooldown",lib_kSetIndirectItemCountdown}, + {"K_SetHyudoroCooldown",lib_kSetHyuCountdown}, {"K_GetCollideAngle",lib_kGetCollideAngle}, diff --git a/src/lua_script.c b/src/lua_script.c index 36da3e957..ec2b15dfa 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -363,6 +363,9 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"gamespeed")) { lua_pushinteger(L, gamespeed); return 1; + } else if (fastcmp(word,"hyubgone")) { + lua_pushinteger(L, hyubgone); + return 1; } else if (fastcmp(word,"encoremode")) { lua_pushboolean(L, encoremode); return 1; diff --git a/src/p_saveg.c b/src/p_saveg.c index 57ebc6da0..1494623d1 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -5009,6 +5009,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEUINT32(save->p, wantedcalcdelay); WRITEUINT32(save->p, indirectitemcooldown); + WRITEUINT32(save->p, hyubgone); WRITEUINT32(save->p, mapreset); WRITEUINT8(save->p, spectateGriefed); @@ -5174,6 +5175,7 @@ FUNCINLINE static ATTRINLINE boolean P_NetUnArchiveMisc(savebuffer_t *save, bool battlewanted[i] = READSINT8(save->p); wantedcalcdelay = READUINT32(save->p); + hyubgone = READUINT32(save->p); indirectitemcooldown = READUINT32(save->p); mapreset = READUINT32(save->p); diff --git a/src/p_setup.c b/src/p_setup.c index 8f2b21d7f..266c03405 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8036,6 +8036,7 @@ static void P_InitGametype(void) wantedcalcdelay = wantedfrequency*2; indirectitemcooldown = 0; + hyubgone = 0; mapreset = 0; thwompsactive = false; diff --git a/src/p_tick.c b/src/p_tick.c index b17204636..691a717de 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -848,6 +848,9 @@ void P_Ticker(boolean run) if (indirectitemcooldown > 0) indirectitemcooldown--; + if (hyubgone > 0) + hyubgone--; + K_BossInfoTicker(); if ((gametyperules & GTR_BUMPERS))