From a481e4b34b3e237362d7018ff57baf759ece16fc Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 11 Dec 2022 23:58:11 -0500 Subject: [PATCH 01/24] Deterministic roulette The roulette contains NO (non-seeded) RNG anymore. You manually stop it at any time. Still needs the visual of the items scrolling, to make it not blind. --- src/Sourcefile | 1 + src/d_netcmd.c | 61 +-- src/d_netcmd.h | 33 +- src/d_player.h | 23 +- src/g_game.c | 13 +- src/k_botitem.c | 7 +- src/k_collide.c | 6 +- src/k_hud.c | 21 +- src/k_kart.c | 998 +----------------------------------------- src/k_kart.h | 8 - src/k_menudraw.c | 4 +- src/k_menufunc.c | 8 +- src/k_roulette.c | 1001 +++++++++++++++++++++++++++++++++++++++++++ src/k_roulette.h | 35 ++ src/lua_playerlib.c | 8 +- src/p_enemy.c | 7 +- src/p_inter.c | 11 +- src/p_mobj.c | 8 +- src/p_saveg.c | 52 ++- src/typedef.h | 1 + 20 files changed, 1199 insertions(+), 1107 deletions(-) create mode 100644 src/k_roulette.c create mode 100644 src/k_roulette.h diff --git a/src/Sourcefile b/src/Sourcefile index f2aa652b1..4da925828 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -125,3 +125,4 @@ k_director.c k_follower.c k_profiles.c k_specialstage.c +k_roulette.c diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 086c4cbfa..9820eeed3 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -360,35 +360,36 @@ consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = { //Alam: Dummy for save #endif // SRB2kart -consvar_t cv_sneaker = CVAR_INIT ("sneaker", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_rocketsneaker = CVAR_INIT ("rocketsneaker", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_invincibility = CVAR_INIT ("invincibility", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_banana = CVAR_INIT ("banana", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_eggmanmonitor = CVAR_INIT ("eggmanmonitor", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_orbinaut = CVAR_INIT ("orbinaut", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_jawz = CVAR_INIT ("jawz", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_mine = CVAR_INIT ("mine", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_landmine = CVAR_INIT ("landmine", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_ballhog = CVAR_INIT ("ballhog", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_selfpropelledbomb = CVAR_INIT ("selfpropelledbomb", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_grow = CVAR_INIT ("grow", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_shrink = CVAR_INIT ("shrink", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_lightningshield = CVAR_INIT ("lightningshield", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_bubbleshield = CVAR_INIT ("bubbleshield", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_flameshield = CVAR_INIT ("flameshield", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_hyudoro = CVAR_INIT ("hyudoro", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_pogospring = CVAR_INIT ("pogospring", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_superring = CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_kitchensink = CVAR_INIT ("kitchensink", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_droptarget = CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_gardentop = CVAR_INIT ("gardentop", "On", CV_NETVAR, CV_OnOff, NULL); - -consvar_t cv_dualsneaker = CVAR_INIT ("dualsneaker", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_triplesneaker = CVAR_INIT ("triplesneaker", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_triplebanana = CVAR_INIT ("triplebanana", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_tripleorbinaut = CVAR_INIT ("tripleorbinaut", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_quadorbinaut = CVAR_INIT ("quadorbinaut", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_dualjawz = CVAR_INIT ("dualjawz", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_items[NUMKARTRESULTS-1] = { + CVAR_INIT ("sneaker", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("rocketsneaker", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("invincibility", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("banana", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("eggmanmonitor", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("orbinaut", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("jawz", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("mine", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("landmine", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("ballhog", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("selfpropelledbomb", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("grow", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("shrink", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("lightningshield", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("bubbleshield", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("flameshield", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("hyudoro", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("pogospring", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("kitchensink", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("gardentop", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("dualsneaker", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("triplesneaker", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("triplebanana", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("tripleorbinaut", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("quadorbinaut", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("dualjawz", "On", CV_NETVAR, CV_OnOff, NULL) +}; consvar_t cv_kartspeed = CVAR_INIT ("gamespeed", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartspeed_cons_t, KartSpeed_OnChange); static CV_PossibleValue_t kartbumpers_cons_t[] = {{1, "MIN"}, {12, "MAX"}, {0, NULL}}; @@ -5659,7 +5660,7 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) K_StripItems(player); // Cancel roulette if rolling - player->itemroulette = 0; + player->itemRoulette.active = false; player->itemtype = item; player->itemamount = amt; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 3f4d00645..e0b969ffd 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -16,6 +16,7 @@ #define __D_NETCMD__ #include "command.h" +#include "d_player.h" // console vars extern consvar_t cv_playername[MAXSPLITSCREENPLAYERS]; @@ -72,37 +73,7 @@ extern consvar_t cv_pause; extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers, cv_respawntime; // SRB2kart items -extern consvar_t - cv_sneaker, - cv_rocketsneaker, - cv_invincibility, - cv_banana, - cv_eggmanmonitor, - cv_orbinaut, - cv_jawz, - cv_mine, - cv_landmine, - cv_ballhog, - cv_selfpropelledbomb, - cv_grow, - cv_shrink, - cv_lightningshield, - cv_bubbleshield, - cv_flameshield, - cv_hyudoro, - cv_pogospring, - cv_superring, - cv_kitchensink, - cv_droptarget, - cv_gardentop; - -extern consvar_t - cv_dualsneaker, - cv_triplesneaker, - cv_triplebanana, - cv_tripleorbinaut, - cv_quadorbinaut, - cv_dualjawz; +extern consvar_t cv_items[NUMKARTRESULTS-1]; extern consvar_t cv_kartspeed; extern consvar_t cv_kartbumpers; diff --git a/src/d_player.h b/src/d_player.h index a65f0235b..fdcb5bdc4 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -131,6 +131,7 @@ Do with it whatever you want. Run this macro, then #undef FOREACH afterward */ #define KART_ITEM_ITERATOR \ + FOREACH (EGGEXPLODE, -2),\ FOREACH (SAD, -1),\ FOREACH (NONE, 0),\ FOREACH (SNEAKER, 1),\ @@ -329,6 +330,25 @@ struct skybox_t { mobj_t * centerpoint; }; +// player_t struct for item roulette variables +struct itemroulette_t +{ + boolean active; + + size_t itemListCap; + size_t itemListLen; + SINT8 *itemList; + + size_t index; + UINT8 sound; + + tic_t speed; + tic_t tics; + tic_t elapsed; + + boolean eggman; +}; + // ======================================================================== // PLAYER STRUCTURE // ======================================================================== @@ -477,8 +497,7 @@ struct player_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. - UINT16 itemroulette; // Used for the roulette when deciding what item to give you (was "pw_kartitem") - UINT8 roulettetype; // Used for the roulette, for deciding type (0 = normal, 1 = better, 2 = eggman mark) + itemroulette_t itemRoulette; // Item roulette data // Item held stuff SINT8 itemtype; // KITEM_ constant for item number diff --git a/src/g_game.c b/src/g_game.c index 5cde2d270..91d309638 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2260,11 +2260,10 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) SINT8 xtralife; // SRB2kart + itemroulette_t itemRoulette; respawnvars_t respawn; INT32 itemtype; INT32 itemamount; - INT32 itemroulette; - INT32 roulettetype; INT32 growshrinktimer; INT32 bumper; boolean songcredit = false; @@ -2325,8 +2324,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) // SRB2kart if (betweenmaps || leveltime < introtime) { - itemroulette = 0; - roulettetype = 0; itemtype = 0; itemamount = 0; growshrinktimer = 0; @@ -2348,9 +2345,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) } else { - itemroulette = (players[player].itemroulette > 0 ? 1 : 0); - roulettetype = players[player].roulettetype; - if (players[player].pflags & PF_ITEMOUT) { itemtype = 0; @@ -2406,6 +2400,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) P_SetTarget(&players[player].follower, NULL); } + memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette)); memcpy(&respawn, &players[player].respawn, sizeof (respawn)); p = &players[player]; @@ -2453,8 +2448,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->xtralife = xtralife; // SRB2kart - p->itemroulette = itemroulette; - p->roulettetype = roulettetype; p->itemtype = itemtype; p->itemamount = itemamount; p->growshrinktimer = growshrinktimer; @@ -2470,6 +2463,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->botvars.rubberband = FRACUNIT; p->botvars.controller = UINT16_MAX; + memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); if (follower) @@ -2481,7 +2475,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) //p->follower = NULL; // respawn a new one with you, it looks better. // ^ Not necessary anyway since it will be respawned regardless considering it doesn't exist anymore. - p->playerstate = PST_LIVE; p->panim = PA_STILL; // standing animation diff --git a/src/k_botitem.c b/src/k_botitem.c index 6c8b9ece4..e3054aa69 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -26,6 +26,7 @@ #include "d_ticcmd.h" #include "m_random.h" #include "r_things.h" // numskins +#include "k_roulette.h" /*-------------------------------------------------- static inline boolean K_ItemButtonWasDown(player_t *player) @@ -739,7 +740,7 @@ static void K_BotItemEggman(player_t *player, ticcmd_t *cmd) tryLookback = true; } - if (stealth > 1 || player->itemroulette > 0) + if (stealth > 1 || player->itemRoulette.active == true) { player->botvars.itemconfirm += player->botvars.difficulty * 4; throwdir = -1; @@ -1400,7 +1401,7 @@ static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) return; } - if (player->rings < 0 && cv_superring.value) + if (player->rings < 0 && K_ItemEnabled(KITEM_SUPERRING) == true) { // Uh oh, we need a loan! // It'll be better in the long run for bots to lose an item set for 10 free rings. @@ -1441,7 +1442,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) return; } - if (player->itemroulette) + if (player->itemRoulette.active == true) { // Mashing behaviors K_BotItemRouletteMash(player, cmd); diff --git a/src/k_collide.c b/src/k_collide.c index dac44b5f7..d9ab58e1c 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -12,6 +12,7 @@ #include "doomdef.h" // Sink snipe print #include "g_game.h" // Sink snipe print #include "k_objects.h" +#include "k_roulette.h" angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2) { @@ -158,10 +159,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 = 1; - t2->player->roulettetype = 2; + K_StartEggmanRoulette(t2->player); } if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD) diff --git a/src/k_hud.c b/src/k_hud.c index 5325f07eb..25a869c13 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -36,6 +36,7 @@ #include "r_things.h" #include "r_fps.h" #include "m_random.h" +#include "k_roulette.h" //{ Patch Definitions static patch_t *kp_nodraw; @@ -1146,12 +1147,14 @@ static void K_drawKartItem(void) UINT8 *colmap = NULL; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff - if (stplyr->itemroulette) + if (stplyr->itemRoulette.active == true) { - const INT32 item = K_GetRollingRouletteItem(stplyr); + const SINT8 item = K_ItemResultToType(stplyr->itemRoulette.itemList[ stplyr->itemRoulette.index ]); if (stplyr->skincolor) + { localcolor = stplyr->skincolor; + } switch (item) { @@ -1165,6 +1168,7 @@ static void K_drawKartItem(void) default: localpatch = K_GetCachedItemPatch(item, offset); + break; } } else @@ -1223,7 +1227,7 @@ static void K_drawKartItem(void) if (stplyr->itemamount <= 0) return; - switch(stplyr->itemtype) + switch (stplyr->itemtype) { case KITEM_INVINCIBILITY: localpatch = localinv; @@ -1302,7 +1306,7 @@ static void K_drawKartItem(void) V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|fflags, localbg); // Then, the numbers: - if (stplyr->itemamount >= numberdisplaymin && !stplyr->itemroulette) + if (stplyr->itemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) { V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]); // flip this graphic for p2 and p4 in split and shift it. V_DrawFixedPatch(fx<botvars.itemdelay = TICRATE; - player->botvars.itemconfirm = 0; - - player->itemtype = K_ItemResultToType(getitem); - player->itemamount = K_ItemResultToAmount(getitem); -} - -fixed_t K_ItemOddsScale(UINT8 playerCount) -{ - const UINT8 basePlayer = 8; // The player count we design most of the game around. - fixed_t playerScaling = 0; - - if (playerCount < 2) - { - // Cap to 1v1 scaling - playerCount = 2; - } - - // Then, it multiplies it further if the player count isn't equal to basePlayer. - // This is done to make low player count races more interesting and high player count rates more fair. - if (playerCount < basePlayer) - { - // Less than basePlayer: increase odds significantly. - // 2P: x2.5 - playerScaling = (basePlayer - playerCount) * (FRACUNIT / 4); - } - else if (playerCount > basePlayer) - { - // More than basePlayer: reduce odds slightly. - // 16P: x0.75 - playerScaling = (basePlayer - playerCount) * (FRACUNIT / 32); - } - - return playerScaling; -} - -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) -{ - if (mapobjectscale != FRACUNIT) - { - // Bring back to normal scale. - distance = FixedDiv(distance, mapobjectscale); - } - - if (franticitems == true) - { - // Frantic items pretends everyone's farther apart, for crazier items. - distance = (15 * distance) / 14; - } - - // Items get crazier with the fewer players that you have. - distance = FixedMul( - distance, - FRACUNIT + (K_ItemOddsScale(numPlayers) / 2) - ); - - return distance; -} - -/** \brief Item Roulette for Kart - - \param player player object passed from P_KartPlayerThink - - \return void -*/ - -INT32 K_KartGetItemOdds( - UINT8 pos, SINT8 item, - UINT32 ourDist, - fixed_t mashed, - boolean bot, boolean rival) -{ - INT32 newodds; - INT32 i; - - UINT8 pingame = 0, pexiting = 0; - - player_t *first = NULL; - player_t *second = NULL; - - UINT32 firstDist = UINT32_MAX; - UINT32 secondDist = UINT32_MAX; - UINT32 secondToFirst = 0; - boolean isFirst = false; - - boolean powerItem = false; - boolean cooldownOnStart = false; - boolean notNearEnd = false; - - 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 - - if (!KartItemCVars[item-1]->value && !modeattacking) - { - return 0; - } - - if (K_GetItemCooldown(item) > 0) - { - // Cooldown is still running, don't give another. - return 0; - } - - /* - if (bot) - { - // TODO: Item use on bots should all be passed-in functions. - // Instead of manually inserting these, it should return 0 - // for any items without an item use function supplied - - switch (item) - { - case KITEM_SNEAKER: - break; - default: - return 0; - } - } - */ - (void)bot; - - if (gametype == GT_BATTLE) - { - I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table - newodds = K_KartItemOddsBattle[item-1][pos]; - } - else - { - I_Assert(pos < 8); // Ditto - 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].bumpers) - pingame++; - - if (players[i].exiting) - pexiting++; - - switch (shieldtype) - { - case KSHIELD_NONE: - /* Marble Garden Top is not REALLY - a Sonic 3 shield */ - case KSHIELD_TOP: - break; - - default: - if (shieldtype == K_GetShieldFromItem(players[i].itemtype)) - { - // Don't allow more than one of each shield type at a time - return 0; - } - } - - if (players[i].position == 1) - { - first = &players[i]; - } - - if (players[i].position == 2) - { - second = &players[i]; - } - } - - if (first != NULL) // calculate 2nd's distance from 1st, for SPB - { - firstDist = first->distancetofinish; - isFirst = (ourDist <= firstDist); - } - - if (second != NULL) - { - secondDist = second->distancetofinish; - } - - if (first != NULL && second != NULL) - { - secondToFirst = secondDist - firstDist; - secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); // Reversed scaling, so 16P is like 1v1, and 1v1 is like 16P - } - - switch (item) - { - case KITEM_BANANA: - case KITEM_EGGMAN: - case KITEM_SUPERRING: - notNearEnd = true; - break; - - case KITEM_ROCKETSNEAKER: - case KITEM_JAWZ: - case KITEM_LANDMINE: - case KITEM_DROPTARGET: - case KITEM_BALLHOG: - case KITEM_HYUDORO: - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - case KRITEM_DUALJAWZ: - powerItem = true; - break; - - case KRITEM_TRIPLEBANANA: - powerItem = true; - notNearEnd = 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; - notNearEnd = true; - - if (firstDist < ENDDIST*2 // No SPB when 1st is almost done - || isFirst == true) // No SPB for 1st ever - { - newodds = 0; - } - else - { - const UINT32 dist = max(0, ((signed)secondToFirst) - SPBSTARTDIST); - const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const UINT8 maxOdds = 20; - fixed_t multiplier = (dist * FRACUNIT) / distRange; - - if (multiplier < 0) - { - multiplier = 0; - } - - if (multiplier > FRACUNIT) - { - multiplier = FRACUNIT; - } - - newodds = FixedMul(maxOdds * 4, multiplier); - } - break; - - case KITEM_SHRINK: - cooldownOnStart = true; - powerItem = true; - notNearEnd = true; - - if (pingame-1 <= pexiting) - newodds = 0; - break; - - case KITEM_LIGHTNINGSHIELD: - cooldownOnStart = true; - powerItem = true; - - if (spbplace != -1) - newodds = 0; - break; - - default: - break; - } - - if (newodds == 0) - { - // Nothing else we want to do with odds matters at this point :p - return newodds; - } - - - 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 ((notNearEnd == true) && (ourDist < ENDDIST)) - { - // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) - 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; - } - - if (rival == true) - { - // The Rival bot gets frantic-like items, also :p - fracOdds *= 2; - } - - fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame)); - - 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) -{ - UINT8 i; - UINT8 useodds = 0; - UINT8 disttable[14]; - UINT8 distlen = 0; - boolean oddsvalid[8]; - - // Unused now, oops :V - (void)bestbumper; - - for (i = 0; i < 8; i++) - { - UINT8 j; - boolean available = false; - - if (gametype == GT_BATTLE && i > 1) - { - oddsvalid[i] = false; - break; - } - - for (j = 1; j < NUMKARTRESULTS; j++) - { - if (K_KartGetItemOdds( - i, j, - player->distancetofinish, - mashed, - player->bot, (player->bot && player->botvars.rival) - ) > 0) - { - available = true; - break; - } - } - - oddsvalid[i] = available; - } - -#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); - - if (pdis == 0) - useodds = disttable[0]; - else if (pdis > DISTVAR * ((12 * distlen) / 14)) - useodds = disttable[distlen-1]; - else - { - for (i = 1; i < 13; i++) - { - if (pdis <= DISTVAR * ((i * distlen) / 14)) - { - useodds = disttable[((i * distlen) / 14)]; - break; - } - } - } - } - -#undef SETUPDISTTABLE - - return useodds; -} - -INT32 K_GetRollingRouletteItem(player_t *player) -{ - static UINT8 translation[NUMKARTITEMS-1]; - static UINT16 roulette_size; - - static INT16 odds_cached = -1; - - // Race odds have more columns than Battle - const UINT8 EMPTYODDS[sizeof K_KartItemOddsRace[0]] = {0}; - - if (odds_cached != gametype) - { - UINT8 *odds_row; - size_t odds_row_size; - - UINT8 i; - - roulette_size = 0; - - if (gametype == GT_BATTLE) - { - odds_row = K_KartItemOddsBattle[0]; - odds_row_size = sizeof K_KartItemOddsBattle[0]; - } - else - { - odds_row = K_KartItemOddsRace[0]; - odds_row_size = sizeof K_KartItemOddsRace[0]; - } - - for (i = 1; i < NUMKARTITEMS; ++i) - { - if (memcmp(odds_row, EMPTYODDS, odds_row_size)) - { - translation[roulette_size] = i; - roulette_size++; - } - - odds_row += odds_row_size; - } - - roulette_size *= 3; - odds_cached = gametype; - } - - return translation[(player->itemroulette % roulette_size) / 3]; -} - -boolean K_ForcedSPB(player_t *player) -{ - player_t *first = NULL; - player_t *second = NULL; - UINT32 secondToFirst = UINT32_MAX; - UINT8 pingame = 0; - UINT8 i; - - if (!cv_selfpropelledbomb.value) - { - return false; - } - - if (!(gametyperules & GTR_CIRCUIT)) - { - return false; - } - - if (player->position <= 1) - { - return false; - } - - if (spbplace != -1) - { - return false; - } - - if (itemCooldowns[KITEM_SPB - 1] > 0) - { - return false; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - { - continue; - } - - if (players[i].exiting) - { - return false; - } - - pingame++; - - if (players[i].position == 1) - { - first = &players[i]; - } - - if (players[i].position == 2) - { - second = &players[i]; - } - } - -#if 0 - if (pingame <= 2) - { - return false; - } -#endif - - if (first != NULL && second != NULL) - { - secondToFirst = second->distancetofinish - first->distancetofinish; - secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); - } - - return (secondToFirst >= SPBFORCEDIST); -} - -static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) -{ - INT32 i; - UINT8 pingame = 0; - UINT8 roulettestop; - UINT32 pdis = 0; - UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; - INT32 totalspawnchance = 0; - UINT8 bestbumper = 0; - fixed_t mashed = 0; - - // This makes the roulette cycle through items - if this is 0, you shouldn't be here. - if (!player->itemroulette) - return; - player->itemroulette++; - - // Gotta check how many players are active at this moment. - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - - pingame++; - - if (players[i].bumpers > bestbumper) - bestbumper = players[i].bumpers; - } - - // This makes the roulette produce the random noises. - if ((player->itemroulette % 3) == 1 && P_IsDisplayPlayer(player) && !demo.freecam) - { -#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->itemroulette / 3) % 8)) - for (i = 0; i <= r_splitscreen; i++) - { - if (player == &players[displayplayers[i]] && players[displayplayers[i]].itemroulette) - PLAYROULETTESND; - } -#undef PLAYROULETTESND - } - - roulettestop = TICRATE + (3*(pingame - player->position)); - - // If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item. - // I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think. - // 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) - && !(player->pflags & (PF_ITEMOUT|PF_EGGMANOUT|PF_USERINGS))) - { - // Mashing reduces your chances for the good items - mashed = FixedDiv((player->itemroulette)*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT; - } - else if (!(player->itemroulette >= (TICRATE*3))) - return; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator - && players[i].position == 1) - { - // This player is first! Yay! - - if (player->distancetofinish <= players[i].distancetofinish) - { - // Guess you're in first / tied for first? - pdis = 0; - } - else - { - // Subtract 1st's distance from your distance, to get your distance from 1st! - pdis = player->distancetofinish - players[i].distancetofinish; - } - break; - } - } - - pdis = K_ScaleItemDistance(pdis, pingame); - - if (player->bot && player->botvars.rival) - { - // Rival has better odds :) - pdis = (15 * pdis) / 14; - } - - // SPECIAL CASE No. 1: - // Fake Eggman items - if (player->roulettetype == 2) - { - player->eggmanexplode = 4*TICRATE; - //player->karthud[khud_itemblink] = TICRATE; - //player->karthud[khud_itemblinkmode] = 1; - player->itemroulette = 0; - player->roulettetype = 0; - if (P_IsDisplayPlayer(player) && !demo.freecam) - S_StartSound(NULL, sfx_itrole); - return; - } - - // SPECIAL CASE No. 2: - // Give a debug item instead if specified - if (cv_kartdebugitem.value != 0 && !modeattacking) - { - K_KartGetItemResult(player, cv_kartdebugitem.value); - player->itemamount = cv_kartdebugamount.value; - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = 2; - player->itemroulette = 0; - player->roulettetype = 0; - if (P_IsDisplayPlayer(player) && !demo.freecam) - S_StartSound(NULL, sfx_dbgsal); - return; - } - - // SPECIAL CASE No. 3: - // Record Attack / alone mashing behavior - if (modeattacking || pingame == 1) - { - if (gametype == GT_RACE) - { - if (mashed && (modeattacking || cv_superring.value)) // ANY mashed value? You get rings. - { - K_KartGetItemResult(player, KITEM_SUPERRING); - player->karthud[khud_itemblinkmode] = 1; - 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); - else // Default to sad if nothing's enabled... - K_KartGetItemResult(player, KITEM_SAD); - player->karthud[khud_itemblinkmode] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - } - else if (gametype == GT_BATTLE) - { - if (mashed && (modeattacking || bossinfo.boss || cv_banana.value)) // ANY mashed value? You get a banana. - { - K_KartGetItemResult(player, KITEM_BANANA); - player->karthud[khud_itemblinkmode] = 1; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } - else if (bossinfo.boss) - { - K_KartGetItemResult(player, KITEM_ORBINAUT); - player->karthud[khud_itemblinkmode] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - else - { - if (modeattacking || cv_tripleorbinaut.value) // Waited patiently? You get Orbinaut x3! - K_KartGetItemResult(player, KRITEM_TRIPLEORBINAUT); - else // Default to sad if nothing's enabled... - K_KartGetItemResult(player, KITEM_SAD); - player->karthud[khud_itemblinkmode] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - } - - player->karthud[khud_itemblink] = TICRATE; - player->itemroulette = 0; - player->roulettetype = 0; - return; - } - - // SPECIAL CASE No. 4: - // Being in ring debt occasionally forces Super Ring on you if you mashed - if (!(gametyperules & GTR_SPHERES) && mashed && player->rings < 0 && cv_superring.value) - { - INT32 debtamount = min(20, abs(player->rings)); - if (P_RandomChance(PR_ITEM_ROULETTE, (debtamount*FRACUNIT)/20)) - { - K_KartGetItemResult(player, KITEM_SUPERRING); - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = 1; - player->itemroulette = 0; - player->roulettetype = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - return; - } - } - - // SPECIAL CASE No. 5: - // Force SPB if 2nd is way too far behind - if (K_ForcedSPB(player) == true) - { - K_KartGetItemResult(player, KITEM_SPB); - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = 2; - player->itemroulette = 0; - player->roulettetype = 0; - 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 - for (i = 0; i < NUMKARTRESULTS; i++) - spawnchance[i] = 0; - - // Split into another function for a debug function below - useodds = K_FindUseodds(player, mashed, pdis, bestbumper); - - for (i = 1; i < NUMKARTRESULTS; i++) - { - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, i, - player->distancetofinish, - mashed, - player->bot, (player->bot && player->botvars.rival)) - ); - } - - // Award the player whatever power is rolled - if (totalspawnchance > 0) - { - totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); - - K_KartGetItemResult(player, i); - } - else - { - player->itemtype = KITEM_SAD; - player->itemamount = 1; - } - - if (P_IsDisplayPlayer(player) && !demo.freecam) - S_StartSound(NULL, ((player->roulettetype == 1) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf))); - - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = ((player->roulettetype == 1) ? 2 : (mashed ? 1 : 0)); - - player->itemroulette = 0; // Since we're done, clear the roulette number - player->roulettetype = 0; // This too -} - //} //{ SRB2kart p_user.c Stuff @@ -7040,7 +6067,6 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( useodds, i, UINT32_MAX, - 0, false, false) ); } @@ -10046,10 +9072,9 @@ void K_StripItems(player_t *player) player->itemamount = 0; player->pflags &= ~(PF_ITEMOUT|PF_EGGMANOUT); - if (!player->itemroulette || player->roulettetype != 2) + if (player->itemRoulette.eggman == false) { - player->itemroulette = 0; - player->roulettetype = 0; + player->itemRoulette.active = false; } player->hyudorotimer = 0; @@ -10065,8 +9090,7 @@ void K_StripItems(player_t *player) void K_StripOther(player_t *player) { - player->itemroulette = 0; - player->roulettetype = 0; + player->itemRoulette.active = false; player->invincibilitytimer = 0; if (player->growshrinktimer) @@ -10086,7 +9110,7 @@ void K_StripOther(player_t *player) static INT32 K_FlameShieldMax(player_t *player) { UINT32 disttofinish = 0; - UINT32 distv = DISTVAR; + UINT32 distv = 2048; UINT8 numplayers = 0; UINT8 i; @@ -10758,7 +9782,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->itemtype == KITEM_NONE && NO_HYUDORO && !(HOLDING_ITEM || player->itemamount - || player->itemroulette + || player->itemRoulette.active == true || player->rocketsneakertimer || player->eggmanexplode)) player->pflags |= PF_USERINGS; diff --git a/src/k_kart.h b/src/k_kart.h index cddf3d503..83d54b898 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -50,14 +50,6 @@ void K_ReduceVFX(mobj_t *mo, player_t *owner); boolean K_IsPlayerLosing(player_t *player); 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); -fixed_t K_ItemOddsScale(UINT8 numPlayers); -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers); -INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean bot, boolean rival); -INT32 K_GetRollingRouletteItem(player_t *player); -boolean K_ForcedSPB(player_t *player); INT32 K_GetShieldFromItem(INT32 item); SINT8 K_ItemResultToType(SINT8 getitem); UINT8 K_ItemResultToAmount(SINT8 getitem); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8bfec4716..71f8b4242 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3455,7 +3455,7 @@ void M_DrawItemToggles(void) continue; } - cv = KartItemCVars[currentMenu->menuitems[thisitem].mvar1-1]; + cv = &cv_items[currentMenu->menuitems[thisitem].mvar1-1]; translucent = (cv->value ? 0 : V_TRANSLUCENT); drawnum = K_ItemResultToAmount(currentMenu->menuitems[thisitem].mvar1); @@ -3502,7 +3502,7 @@ void M_DrawItemToggles(void) } else { - cv = KartItemCVars[currentMenu->menuitems[itemOn].mvar1-1]; + cv = &cv_items[currentMenu->menuitems[itemOn].mvar1-1]; translucent = (cv->value ? 0 : V_TRANSLUCENT); drawnum = K_ItemResultToAmount(currentMenu->menuitems[itemOn].mvar1); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index ab43217ff..384905f71 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -5632,12 +5632,12 @@ void M_HandleItemToggles(INT32 choice) else if (currentMenu->menuitems[itemOn].mvar1 == 0) { - INT32 v = cv_sneaker.value; + INT32 v = cv_items[0].value; S_StartSound(NULL, sfx_s1b4); for (i = 0; i < NUMKARTRESULTS-1; i++) { - if (KartItemCVars[i]->value == v) - CV_AddValue(KartItemCVars[i], 1); + if (cv_items[i].value == v) + CV_AddValue(&cv_items[i], 1); } } else @@ -5650,7 +5650,7 @@ void M_HandleItemToggles(INT32 choice) { S_StartSound(NULL, sfx_s1ba); } - CV_AddValue(KartItemCVars[currentMenu->menuitems[itemOn].mvar1-1], 1); + CV_AddValue(&cv_items[currentMenu->menuitems[itemOn].mvar1-1], 1); } } diff --git a/src/k_roulette.c b/src/k_roulette.c new file mode 100644 index 000000000..df16eba5f --- /dev/null +++ b/src/k_roulette.c @@ -0,0 +1,1001 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Kart Krew +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// +// 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_roulette.c +/// \brief Item roulette code. + +#include "k_roulette.h" + +#include "d_player.h" +#include "doomdef.h" +#include "hu_stuff.h" +#include "g_game.h" +#include "m_random.h" +#include "p_local.h" +#include "p_slopes.h" +#include "p_setup.h" +#include "r_draw.h" +#include "r_local.h" +#include "r_things.h" +#include "s_sound.h" +#include "st_stuff.h" +#include "v_video.h" +#include "z_zone.h" +#include "m_misc.h" +#include "m_cond.h" +#include "f_finale.h" +#include "lua_hud.h" // For Lua hud checks +#include "lua_hook.h" // For MobjDamage and ShouldDamage +#include "m_cheat.h" // objectplacing +#include "p_spec.h" + +#include "k_kart.h" +#include "k_battle.h" +#include "k_boss.h" +#include "k_pwrlv.h" +#include "k_color.h" +#include "k_respawn.h" +#include "k_waypoint.h" +#include "k_bot.h" +#include "k_hud.h" +#include "k_terrain.h" +#include "k_director.h" +#include "k_collide.h" +#include "k_follower.h" +#include "k_objects.h" +#include "k_grandprix.h" +#include "k_specialstage.h" + +// Magic number distance for use with item roulette tiers +#define DISTVAR (2048) + +// Distance when SPB can start appearing +#define SPBSTARTDIST (8*DISTVAR) + +// Distance when SPB is forced onto the next person who rolls an item +#define SPBFORCEDIST (14*DISTVAR) + +// Distance when the game stops giving you bananas +#define ENDDIST (12*DISTVAR) + +// Consistent seed used for item reels +#define ITEM_REEL_SEED (0x22D5FAA8) + +static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = +{ + { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker + { 0, 0, 0, 0, 0, 3, 4, 5 }, // Rocket Sneaker + { 0, 0, 0, 0, 2, 5, 5, 7 }, // Invincibility + { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana + { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor + { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut + { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz + { 0, 3, 3, 2, 0, 0, 0, 0 }, // Mine + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine + { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog + { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb + { 0, 0, 0, 0, 2, 5, 0, 0 }, // Grow + { 0, 0, 0, 0, 0, 2, 4, 2 }, // Shrink + { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield + { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield + { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring + { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target + { 0, 0, 0, 3, 5, 0, 0, 0 }, // Garden Top + { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 + { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 + { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 + { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 + { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 + { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 +}; + +static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = +{ + //K L + { 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, 0 }, // Land Mine + { 2, 1 }, // Ballhog + { 0, 0 }, // Self-Propelled Bomb + { 2, 1 }, // Grow + { 0, 0 }, // Shrink + { 4, 0 }, // Lightning Shield + { 1, 0 }, // Bubble Shield + { 1, 0 }, // Flame Shield + { 2, 0 }, // Hyudoro + { 3, 0 }, // Pogo Spring + { 0, 0 }, // Super Ring + { 0, 0 }, // Kitchen Sink + { 2, 0 }, // Drop Target + { 4, 0 }, // Garden Top + { 0, 0 }, // Sneaker x2 + { 0, 1 }, // Sneaker x3 + { 0, 0 }, // Banana x3 + { 2, 0 }, // Orbinaut x3 + { 1, 1 }, // Orbinaut x4 + { 5, 1 } // Jawz x2 +}; + +static kartitems_t K_KartItemReelTimeAttack[] = +{ + KITEM_SNEAKER, + KITEM_SUPERRING, + KITEM_NONE +}; + +static kartitems_t K_KartItemReelBreakTheCapsules[] = +{ + KRITEM_TRIPLEORBINAUT, + KITEM_BANANA, + KITEM_NONE +}; + +#if 0 +static kartitems_t K_KartItemReelBoss[] = +{ + KITEM_ORBINAUT, + KITEM_BANANA, + KITEM_NONE +}; +#endif + +boolean K_ItemEnabled(SINT8 item) +{ + if (item < 1 || item >= NUMKARTRESULTS) + { + // Not a real item. + return false; + } + + if (K_CanChangeRules(true) == false) + { + // Force all items to be enabled. + return true; + } + + // Allow the user preference. + return cv_items[item - 1].value; +} + +fixed_t K_ItemOddsScale(UINT8 playerCount) +{ + const UINT8 basePlayer = 8; // The player count we design most of the game around. + fixed_t playerScaling = 0; + + if (playerCount < 2) + { + // Cap to 1v1 scaling + playerCount = 2; + } + + // Then, it multiplies it further if the player count isn't equal to basePlayer. + // This is done to make low player count races more interesting and high player count rates more fair. + if (playerCount < basePlayer) + { + // Less than basePlayer: increase odds significantly. + // 2P: x2.5 + playerScaling = (basePlayer - playerCount) * (FRACUNIT / 4); + } + else if (playerCount > basePlayer) + { + // More than basePlayer: reduce odds slightly. + // 16P: x0.75 + playerScaling = (basePlayer - playerCount) * (FRACUNIT / 32); + } + + return playerScaling; +} + +UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) +{ + if (mapobjectscale != FRACUNIT) + { + // Bring back to normal scale. + distance = FixedDiv(distance, mapobjectscale); + } + + if (franticitems == true) + { + // Frantic items pretends everyone's farther apart, for crazier items. + distance = (15 * distance) / 14; + } + + // Items get crazier with the fewer players that you have. + distance = FixedMul( + distance, + FRACUNIT + (K_ItemOddsScale(numPlayers) / 2) + ); + + return distance; +} + +UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 pingame) +{ + UINT32 pdis = 0; + +#if 0 + if (specialStage.active == true) + { + UINT32 ufoDis = K_GetSpecialUFODistance(); + + if (player->distancetofinish <= ufoDis) + { + // You're ahead of the UFO. + pdis = 0; + } + else + { + // Subtract the UFO's distance from your distance! + pdis = player->distancetofinish - ufoDis; + } + } + else +#endif + { + UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator + && players[i].position == 1) + { + // This player is first! Yay! + + if (player->distancetofinish <= players[i].distancetofinish) + { + // Guess you're in first / tied for first? + pdis = 0; + } + else + { + // Subtract 1st's distance from your distance, to get your distance from 1st! + pdis = player->distancetofinish - players[i].distancetofinish; + } + break; + } + } + } + + pdis = K_ScaleItemDistance(pdis, pingame); + + if (player->bot && player->botvars.rival) + { + // Rival has better odds :) + pdis = (15 * pdis) / 14; + } + + return pdis; +} + +/** \brief Item Roulette for Kart + + \param player player object passed from P_KartPlayerThink + + \return void +*/ + +INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, boolean rival) +{ + fixed_t newOdds; + INT32 i; + + UINT8 pingame = 0, pexiting = 0; + + player_t *first = NULL; + player_t *second = NULL; + + UINT32 firstDist = UINT32_MAX; + UINT32 secondDist = UINT32_MAX; + UINT32 secondToFirst = 0; + boolean isFirst = false; + + boolean powerItem = false; + boolean cooldownOnStart = false; + boolean notNearEnd = false; + + INT32 shieldType = KSHIELD_NONE; + + I_Assert(item > KITEM_NONE); // too many off by one scenarioes. + I_Assert(cv_items[NUMKARTRESULTS-2] != NULL); // Make sure this exists + + if (K_ItemEnabled(item) == false) + { + return 0; + } + + if (K_GetItemCooldown(item) > 0) + { + // Cooldown is still running, don't give another. + return 0; + } + + /* + if (bot) + { + // TODO: Item use on bots should all be passed-in functions. + // Instead of manually inserting these, it should return 0 + // for any items without an item use function supplied + + switch (item) + { + case KITEM_SNEAKER: + break; + default: + return 0; + } + } + */ + (void)bot; + + if (gametype == GT_BATTLE) + { + I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table + newOdds = K_KartItemOddsBattle[item-1][pos]; + } + else + { + I_Assert(pos < 8); // Ditto + newOdds = K_KartItemOddsRace[item-1][pos]; + } + + newOdds <<= FRACBITS; + + shieldType = K_GetShieldFromItem(item); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + if (!(gametyperules & GTR_BUMPERS) || players[i].bumpers) + { + pingame++; + } + + if (players[i].exiting) + { + pexiting++; + } + + switch (shieldType) + { + case KSHIELD_NONE: + /* Marble Garden Top is not REALLY + a Sonic 3 shield */ + case KSHIELD_TOP: + { + break; + } + + default: + { + if (shieldType == K_GetShieldFromItem(players[i].itemtype)) + { + // Don't allow more than one of each shield type at a time + return 0; + } + } + } + + if (players[i].position == 1) + { + first = &players[i]; + } + + if (players[i].position == 2) + { + second = &players[i]; + } + } + + if (first != NULL) // calculate 2nd's distance from 1st, for SPB + { + firstDist = first->distancetofinish; + isFirst = (ourDist <= firstDist); + } + + if (second != NULL) + { + secondDist = second->distancetofinish; + } + + if (first != NULL && second != NULL) + { + secondToFirst = secondDist - firstDist; + secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); // Reversed scaling, so 16P is like 1v1, and 1v1 is like 16P + } + + switch (item) + { + case KITEM_BANANA: + case KITEM_EGGMAN: + case KITEM_SUPERRING: + { + notNearEnd = true; + break; + } + + case KITEM_ROCKETSNEAKER: + case KITEM_JAWZ: + case KITEM_LANDMINE: + case KITEM_DROPTARGET: + case KITEM_BALLHOG: + case KITEM_HYUDORO: + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KRITEM_DUALJAWZ: + { + powerItem = true; + break; + } + + case KRITEM_TRIPLEBANANA: + { + powerItem = true; + notNearEnd = 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; + notNearEnd = true; + + if (firstDist < ENDDIST*2 // No SPB when 1st is almost done + || isFirst == true) // No SPB for 1st ever + { + newOdds = 0; + } + else + { + const UINT32 dist = max(0, ((signed)secondToFirst) - SPBSTARTDIST); + const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; + const UINT8 maxOdds = 20; + fixed_t multiplier = (dist * FRACUNIT) / distRange; + + if (multiplier < 0) + { + multiplier = 0; + } + + if (multiplier > FRACUNIT) + { + multiplier = FRACUNIT; + } + + newOdds = FixedMul(maxOdds << FRACBITS, multiplier); + } + break; + } + + case KITEM_SHRINK: + cooldownOnStart = true; + powerItem = true; + notNearEnd = true; + + if (pingame-1 <= pexiting) + { + newOdds = 0; + } + break; + + case KITEM_LIGHTNINGSHIELD: + cooldownOnStart = true; + powerItem = true; + + if (spbplace != -1) + { + newOdds = 0; + } + break; + + default: + break; + } + + if (newOdds == 0) + { + // Nothing else we want to do with odds matters at this point :p + return newOdds; + } + + 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 ((notNearEnd == true) && (ourDist < ENDDIST)) + { + // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) + newOdds = 0; + } + else if (powerItem == true) + { + // This item is a "power item". This activates "frantic item" toggle related functionality. + if (franticitems == true) + { + // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. + newOdds *= 2; + } + + if (rival == true) + { + // The Rival bot gets frantic-like items, also :p + newOdds *= 2; + } + + newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(pingame)); + } + + return FixedInt(FixedRound(newOdds)); +} + +//{ SRB2kart Roulette Code - Distance Based, yes waypoints + +UINT8 K_FindUseodds(player_t *const player, UINT32 playerDist) +{ + UINT8 i; + UINT8 useOdds = 0; + UINT8 distTable[14]; + UINT8 distLen = 0; + UINT8 totalSize = 0; + boolean oddsValid[8]; + + for (i = 0; i < 8; i++) + { + UINT8 j; + + if (gametype == GT_BATTLE && i > 1) + { + oddsValid[i] = false; + break; + } + + for (j = 1; j < NUMKARTRESULTS; j++) + { + if (K_KartGetItemOdds( + i, j, + player->distancetofinish, + player->bot, (player->bot && player->botvars.rival) + ) > 0) + { + break; + } + } + + oddsValid[i] = (j < NUMKARTRESULTS); + } + +#define SETUPDISTTABLE(odds, num) \ + totalSize += num; \ + if (oddsValid[odds]) \ + for (i = num; i; --i) \ + distTable[distLen++] = odds; + + if (gametype == GT_BATTLE) // Battle Mode + { + useOdds = 0; + } + 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); + + for (i = 0; i < totalSize; i++) + { + fixed_t pos = 0; + fixed_t dist = 0; + UINT8 index = 0; + + if (i == totalSize-1) + { + useOdds = distTable[distLen - 1]; + break; + } + + pos = ((i << FRACBITS) * distLen) / totalSize; + dist = FixedMul(DISTVAR << FRACBITS, pos) >> FRACBITS; + index = FixedInt(FixedRound(pos)); + + if (playerDist <= (unsigned)dist) + { + useOdds = distTable[index]; + break; + } + } + } + +#undef SETUPDISTTABLE + + return useOdds; +} + +boolean K_ForcedSPB(player_t *const player) +{ + player_t *first = NULL; + player_t *second = NULL; + UINT32 secondToFirst = UINT32_MAX; + UINT8 pingame = 0; + UINT8 i; + + if (K_ItemEnabled(KITEM_SPB) == false) + { + return false; + } + + if (!(gametyperules & GTR_CIRCUIT)) + { + return false; + } + + if (player->position <= 1) + { + return false; + } + + if (spbplace != -1) + { + return false; + } + + if (itemCooldowns[KITEM_SPB - 1] > 0) + { + return false; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + if (players[i].exiting) + { + return false; + } + + pingame++; + + if (players[i].position == 1) + { + first = &players[i]; + } + + if (players[i].position == 2) + { + second = &players[i]; + } + } + +#if 0 + if (pingame <= 2) + { + return false; + } +#endif + + if (first != NULL && second != NULL) + { + secondToFirst = second->distancetofinish - first->distancetofinish; + secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); + } + + return (secondToFirst >= SPBFORCEDIST); +} + +static void K_InitRoulette(itemroulette_t *const roulette) +{ + if (roulette->itemList == NULL) + { + roulette->itemListCap = 8; + roulette->itemList = Z_Calloc( + sizeof(SINT8) * roulette->itemListCap, + PU_LEVEL, + &roulette->itemList + ); + } + + memset(roulette->itemList, KITEM_NONE, sizeof(SINT8) * roulette->itemListCap); + roulette->itemListLen = 0; + + roulette->index = 0; + roulette->elapsed = 0; + roulette->tics = roulette->speed = 3; // Some default speed + roulette->active = true; +} + +static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t item) +{ + I_Assert(roulette->itemList != NULL); + + if (roulette->itemListLen >= roulette->itemListCap) + { + roulette->itemListCap *= 2; + roulette->itemList = Z_Realloc( + roulette->itemList, + sizeof(SINT8) * roulette->itemListCap, + PU_LEVEL, + &roulette->itemList + ); + } + + roulette->itemList[ roulette->itemListLen ] = item; + roulette->itemListLen++; +} + +static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *const roulette, UINT8 playing) +{ + // TODO: Change speed based on two factors: + // - Get faster when your distancetofinish is closer to 1st place's distancetofinish. (winning) + // - Get faster based on overall distancetofinish (race progress) + // Slowest speed should be 12 tics, fastest should be 3 tics. + + (void)player; + (void)playing; + + roulette->tics = roulette->speed = 7; +} + +void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) +{ + UINT8 playing = 0; + UINT32 playerDist = UINT32_MAX; + + UINT8 useOdds = 0; + UINT32 spawnChance[NUMKARTRESULTS] = {0}; + UINT32 totalSpawnChance = 0; + size_t rngRoll = 0; + + size_t i; + + K_InitRoulette(roulette); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + playing++; + } + + K_CalculateRouletteSpeed(player, roulette, playing); + + // SPECIAL CASE No. 1: + // Give only the debug item if specified + if (cv_kartdebugitem.value != KITEM_NONE) + { + K_PushToRouletteItemList(roulette, cv_kartdebugitem.value); + return; + } + + // SPECIAL CASE No. 2: + // Use a special, pre-determined item reel for Time Attack / Free Play + if (modeattacking || playing <= 1) + { + switch (gametype) + { + case GT_RACE: + default: + { + for (i = 0; K_KartItemReelTimeAttack[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, K_KartItemReelTimeAttack[i]); + CONS_Printf("Added %d\n", K_KartItemReelTimeAttack[i]); + } + break; + } + case GT_BATTLE: + { + for (i = 0; K_KartItemReelBreakTheCapsules[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, K_KartItemReelBreakTheCapsules[i]); + CONS_Printf("Added %d\n", K_KartItemReelBreakTheCapsules[i]); + } + break; + } + } + + return; + } + + // SPECIAL CASE No. 3: + // Only give the SPB if conditions are right + if (K_ForcedSPB(player) == true) + { + K_PushToRouletteItemList(roulette, KITEM_SPB); + return; + } + + playerDist = K_GetItemRouletteDistance(player, playing); + + useOdds = K_FindUseodds(player, playerDist); + + for (i = 1; i < NUMKARTRESULTS; i++) + { + UINT8 thisItemsOdds = K_KartGetItemOdds( + useOdds, i, + player->distancetofinish, + player->bot, (player->bot && player->botvars.rival) + ); + + spawnChance[i] = (totalSpawnChance += thisItemsOdds); + } + + // SPECIAL CASE No. 4: + // All items are off, so give a placeholder item + if (totalSpawnChance == 0) + { + K_PushToRouletteItemList(roulette, KITEM_SAD); + return; + } + + // We always want the same result when making the same item reel. + P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); + + while (totalSpawnChance > 0) + { + rngRoll = P_RandomKey(PR_ITEM_ROULETTE, totalSpawnChance); + + for (i = 0; i < NUMKARTRESULTS && spawnChance[i] <= rngRoll; i++) + { + continue; + } + + K_PushToRouletteItemList(roulette, i); + + // If we're in ring debt, pad out the reel with + // a BUNCH of Super Rings. + if (K_ItemEnabled(KITEM_SUPERRING) + && player->rings < 0 + && !(gametyperules & GTR_SPHERES)) + { + K_PushToRouletteItemList(roulette, KITEM_SUPERRING); + } + + spawnChance[i]--; + totalSpawnChance--; + } +} + +void K_StartEggmanRoulette(player_t *const player) +{ + itemroulette_t *const roulette = &player->itemRoulette; + + K_InitRoulette(roulette); + K_PushToRouletteItemList(roulette, KITEM_EGGEXPLODE); + roulette->eggman = true; +} + +/** \brief Item Roulette for Kart + + \param player player + \param getitem what item we're looking for + + \return void +*/ +static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) +{ + if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) + { + K_SetItemCooldown(getitem, 20*TICRATE); + } + + player->botvars.itemdelay = TICRATE; + player->botvars.itemconfirm = 0; + + player->itemtype = K_ItemResultToType(getitem); + player->itemamount = K_ItemResultToAmount(getitem); +} + +void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) +{ + itemroulette_t *const roulette = &player->itemRoulette; + boolean confirmItem = false; + + // This makes the roulette cycle through items. + // If this isn't active, you shouldn't be here. + if (roulette->active == false) + { + return; + } + + if (roulette->itemList == NULL || roulette->itemListLen == 0) + { + // Invalid roulette setup. + // Escape before we run into issues. + roulette->active = false; + return; + } + + if (roulette->elapsed > TICRATE>>1) // Prevent accidental immediate item confirm + { + if (roulette->elapsed > TICRATE<<4) + { + // Waited way too long, forcefully confirm the item. + confirmItem = true; + } + else + { + // We can stop our item when we choose. + confirmItem = !!(cmd->buttons & BT_ATTACK); + } + } + + // If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item. + // I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think. + // Finally, if you get past this check, now you can actually start calculating what item you get. + if (confirmItem == true && (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT|PF_USERINGS)) == 0) + { + kartitems_t finalItem = roulette->itemList[ roulette->index ]; + + K_KartGetItemResult(player, finalItem); + player->karthud[khud_itemblink] = TICRATE; + + if (P_IsDisplayPlayer(player) && !demo.freecam) + { + S_StartSound(NULL, sfx_itrolf); + } + + // We're done, disable the roulette + roulette->active = false; + return; + } + + roulette->elapsed++; + + if (roulette->tics == 0) + { + roulette->index = (roulette->index + 1) % roulette->itemListLen; + roulette->tics = roulette->speed; + + // This makes the roulette produce the random noises. + roulette->sound = (roulette->sound + 1) % 8; + + if (P_IsDisplayPlayer(player) && !demo.freecam) + { + S_StartSound(NULL, sfx_itrol1 + roulette->sound); + } + } + else + { + roulette->tics--; + } +} diff --git a/src/k_roulette.h b/src/k_roulette.h new file mode 100644 index 000000000..8fd302abf --- /dev/null +++ b/src/k_roulette.h @@ -0,0 +1,35 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Kart Krew +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// +// 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_roulette.h +/// \brief Item roulette code. + +#ifndef __K_ROULETTE_H__ +#define __K_ROULETTE_H__ + +#include "doomtype.h" +#include "d_player.h" + +boolean K_ItemEnabled(SINT8 item); + +fixed_t K_ItemOddsScale(UINT8 playerCount); +UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers); +UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 pingame); + +INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, boolean rival); +UINT8 K_FindUseodds(player_t *const player, UINT32 playerDist); + +boolean K_ForcedSPB(player_t *const player); + +void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette); +void K_StartEggmanRoulette(player_t *const player); + +void K_KartItemRoulette(player_t *const player, ticcmd_t *cmd); + +#endif // __K_ROULETTE_H__ diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 2a3b9e46a..b69449ba2 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -304,10 +304,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->tripwirePass); else if (fastcmp(field,"tripwireLeniency")) lua_pushinteger(L, plr->tripwireLeniency); + /* else if (fastcmp(field,"itemroulette")) lua_pushinteger(L, plr->itemroulette); - else if (fastcmp(field,"roulettetype")) - lua_pushinteger(L, plr->roulettetype); + */ else if (fastcmp(field,"itemtype")) lua_pushinteger(L, plr->itemtype); else if (fastcmp(field,"itemamount")) @@ -680,10 +680,10 @@ static int player_set(lua_State *L) plr->tripwirePass = luaL_checkinteger(L, 3); else if (fastcmp(field,"tripwireLeniency")) plr->tripwireLeniency = luaL_checkinteger(L, 3); + /* else if (fastcmp(field,"itemroulette")) plr->itemroulette = luaL_checkinteger(L, 3); - else if (fastcmp(field,"roulettetype")) - plr->roulettetype = luaL_checkinteger(L, 3); + */ else if (fastcmp(field,"itemtype")) plr->itemtype = luaL_checkinteger(L, 3); else if (fastcmp(field,"itemamount")) diff --git a/src/p_enemy.c b/src/p_enemy.c index 131e92bf7..636eb500a 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -34,6 +34,7 @@ #include "k_respawn.h" #include "k_collide.h" #include "k_objects.h" +#include "k_roulette.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -13042,9 +13043,13 @@ void A_ItemPop(mobj_t *actor) Obj_SpawnItemDebrisEffects(actor, actor->target); if (locvar1 == 1) + { P_GivePlayerSpheres(actor->target->player, actor->extravalue1); + } else if (locvar1 == 0) - actor->target->player->itemroulette = 1; + { + K_StartItemRoulette(actor->target->player, &actor->target->player->itemRoulette); + } // Here at mapload in battle? if ((gametyperules & GTR_BUMPERS) && (actor->flags2 & MF2_BOSSNOTRAP)) diff --git a/src/p_inter.c b/src/p_inter.c index 76757f193..2c05f5732 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -38,6 +38,7 @@ #include "k_respawn.h" #include "p_spec.h" #include "k_objects.h" +#include "k_roulette.h" // CTF player names #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" @@ -130,7 +131,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) return false; // Already have fake - if (player->roulettetype == 2 + if (player->itemRoulette.eggman == true || player->eggmanexplode) return false; } @@ -143,7 +144,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) return false; // Item slot already taken up - if (player->itemroulette + if (player->itemRoulette.active == true || (weapon != 3 && player->itemamount) || (player->pflags & PF_ITEMOUT)) return false; @@ -411,8 +412,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->fuse || !P_CanPickupItem(player, 1) || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)) return; - player->itemroulette = 1; - player->roulettetype = 1; + K_StartItemRoulette(player, &player->itemRoulette); // Karma fireworks for (i = 0; i < 5; i++) @@ -1449,8 +1449,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget } player->karthud[khud_itemblink] = TICRATE; player->karthud[khud_itemblinkmode] = 0; - player->itemroulette = 0; - player->roulettetype = 0; + player->itemRoulette.active = false; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); } diff --git a/src/p_mobj.c b/src/p_mobj.c index 340e40e52..d48fa6de8 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6124,7 +6124,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) break; // see also K_drawKartItem in k_hud.c - case MT_PLAYERARROW: + case MT_PLAYERARROW: // FIXME: Delete this object, attach to name tags instead. if (mobj->target && mobj->target->health && mobj->target->player && !mobj->target->player->spectator && mobj->target->health && mobj->target->player->playerstate != PST_DEAD @@ -6178,7 +6178,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) } // Do this in an easy way - if (mobj->target->player->itemroulette) + if (mobj->target->player->itemRoulette.active) { mobj->tracer->color = mobj->target->player->skincolor; mobj->tracer->colorized = true; @@ -6194,11 +6194,11 @@ static void P_MobjSceneryThink(mobj_t *mobj) const INT32 numberdisplaymin = ((mobj->target->player->itemtype == KITEM_ORBINAUT) ? 5 : 2); // Set it to use the correct states for its condition - if (mobj->target->player->itemroulette) + if (mobj->target->player->itemRoulette.active) { P_SetMobjState(mobj, S_PLAYERARROW_BOX); mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = K_GetRollingRouletteItem(mobj->target->player) | FF_FULLBRIGHT; + mobj->tracer->frame = 1 | FF_FULLBRIGHT; mobj->tracer->renderflags &= ~RF_DONTDRAW; } else if (mobj->target->player->stealingtimer < 0) diff --git a/src/p_saveg.c b/src/p_saveg.c index 73d4a7d1f..fee092df9 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -310,9 +310,6 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].tripwirePass); WRITEUINT16(save_p, players[i].tripwireLeniency); - WRITEUINT16(save_p, players[i].itemroulette); - WRITEUINT8(save_p, players[i].roulettetype); - WRITESINT8(save_p, players[i].itemtype); WRITEUINT8(save_p, players[i].itemamount); WRITESINT8(save_p, players[i].throwdir); @@ -410,6 +407,19 @@ static void P_NetArchivePlayers(void) WRITEUINT32(save_p, players[i].botvars.itemconfirm); WRITESINT8(save_p, players[i].botvars.turnconfirm); WRITEUINT32(save_p, players[i].botvars.spindashconfirm); + + // itemroulette_t + WRITEUINT8(save_p, players[i].itemRoulette.active); + WRITEUINT32(save_p, players[i].itemRoulette.itemListCap); + WRITEUINT32(save_p, players[i].itemRoulette.itemListLen); + for (j = 0; (unsigned)j < players[i].itemRoulette.itemListLen; j++) + { + WRITESINT8(save_p, players[i].itemRoulette.itemList[j]); + } + WRITEUINT32(save_p, players[i].itemRoulette.index); + WRITEUINT32(save_p, players[i].itemRoulette.speed); + WRITEUINT32(save_p, players[i].itemRoulette.tics); + WRITEUINT32(save_p, players[i].itemRoulette.elapsed); } } @@ -612,9 +622,6 @@ static void P_NetUnArchivePlayers(void) players[i].tripwirePass = READUINT8(save_p); players[i].tripwireLeniency = READUINT16(save_p); - players[i].itemroulette = READUINT16(save_p); - players[i].roulettetype = READUINT8(save_p); - players[i].itemtype = READSINT8(save_p); players[i].itemamount = READUINT8(save_p); players[i].throwdir = READSINT8(save_p); @@ -713,6 +720,39 @@ static void P_NetUnArchivePlayers(void) players[i].botvars.turnconfirm = READSINT8(save_p); players[i].botvars.spindashconfirm = READUINT32(save_p); + // itemroulette_t + players[i].itemRoulette.active = READUINT8(save_p); + players[i].itemRoulette.itemListCap = READUINT32(save_p); + players[i].itemRoulette.itemListLen = READUINT32(save_p); + + if (players[i].itemRoulette.itemList == NULL) + { + players[i].itemRoulette.itemList = Z_Calloc( + sizeof(SINT8) * players[i].itemRoulette.itemListCap, + PU_LEVEL, + &players[i].itemRoulette.itemList + ); + } + else + { + players[i].itemRoulette.itemList = Z_Realloc( + players[i].itemRoulette.itemList, + sizeof(SINT8) * players[i].itemRoulette.itemListCap, + PU_LEVEL, + &players[i].itemRoulette.itemList + ); + } + + for (j = 0; (unsigned)j < players[i].itemRoulette.itemListLen; j++) + { + players[i].itemRoulette.itemList[j] = READSINT8(save_p); + } + + players[i].itemRoulette.index = READUINT32(save_p); + players[i].itemRoulette.speed = READUINT32(save_p); + players[i].itemRoulette.tics = READUINT32(save_p); + players[i].itemRoulette.elapsed = READUINT32(save_p); + //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } } diff --git a/src/typedef.h b/src/typedef.h index db751340c..0c19fa2ac 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -37,6 +37,7 @@ TYPEDEF (discordRequest_t); TYPEDEF (respawnvars_t); TYPEDEF (botvars_t); TYPEDEF (skybox_t); +TYPEDEF (itemroulette_t); TYPEDEF (player_t); // d_clisrv.h From 534026764c01ff9153b12c0ec4065426a9e3e398 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 00:22:14 -0500 Subject: [PATCH 02/24] Make Eggman Mark actually work --- src/d_player.h | 1 - src/k_roulette.c | 37 +++++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index fdcb5bdc4..a8b70f1a7 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -131,7 +131,6 @@ Do with it whatever you want. Run this macro, then #undef FOREACH afterward */ #define KART_ITEM_ITERATOR \ - FOREACH (EGGEXPLODE, -2),\ FOREACH (SAD, -1),\ FOREACH (NONE, 0),\ FOREACH (SNEAKER, 1),\ diff --git a/src/k_roulette.c b/src/k_roulette.c index df16eba5f..c5a493365 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -738,6 +738,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->elapsed = 0; roulette->tics = roulette->speed = 3; // Some default speed roulette->active = true; + roulette->eggman = false; } static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t item) @@ -898,9 +899,7 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) void K_StartEggmanRoulette(player_t *const player) { itemroulette_t *const roulette = &player->itemRoulette; - - K_InitRoulette(roulette); - K_PushToRouletteItemList(roulette, KITEM_EGGEXPLODE); + K_StartItemRoulette(player, roulette); roulette->eggman = true; } @@ -964,14 +963,32 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) // Finally, if you get past this check, now you can actually start calculating what item you get. if (confirmItem == true && (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT|PF_USERINGS)) == 0) { - kartitems_t finalItem = roulette->itemList[ roulette->index ]; - - K_KartGetItemResult(player, finalItem); - player->karthud[khud_itemblink] = TICRATE; - - if (P_IsDisplayPlayer(player) && !demo.freecam) + if (roulette->eggman == true) { - S_StartSound(NULL, sfx_itrolf); + // FATASS JUMPSCARE instead of your actual item + player->eggmanexplode = 4*TICRATE; + + //player->karthud[khud_itemblink] = TICRATE; + //player->karthud[khud_itemblinkmode] = 1; + + if (P_IsDisplayPlayer(player) && !demo.freecam) + { + S_StartSound(NULL, sfx_itrole); + } + } + else + { + kartitems_t finalItem = roulette->itemList[ roulette->index ]; + + K_KartGetItemResult(player, finalItem); + + player->karthud[khud_itemblink] = TICRATE; + player->karthud[khud_itemblinkmode] = 0; + + if (P_IsDisplayPlayer(player) && !demo.freecam) + { + S_StartSound(NULL, sfx_itrolf); + } } // We're done, disable the roulette From 202c5056648240cc61429dd831eea185d058129a Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 02:18:18 -0500 Subject: [PATCH 03/24] Implement roulette visuals --- src/d_player.h | 1 + src/k_hud.c | 104 ++++++++++++++++++++++++++++++----------------- src/k_kart.c | 5 +++ src/k_roulette.c | 27 +++++++++--- src/k_roulette.h | 3 ++ 5 files changed, 97 insertions(+), 43 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index a8b70f1a7..842edbc0e 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -225,6 +225,7 @@ typedef enum // Item box khud_itemblink, // Item flashing after roulette, serves as a mashing indicator khud_itemblinkmode, // Type of flashing: 0 = white (normal), 1 = red (mashing), 2 = rainbow (enhanced items) + khud_rouletteoffset,// Roulette stop height // Rings khud_ringframe, // Ring spin frame diff --git a/src/k_hud.c b/src/k_hud.c index f92c94a6f..5ba874913 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1023,7 +1023,7 @@ 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); - patch_t *localpatch = kp_nodraw; + patch_t *localpatch[3] = {kp_nodraw}; patch_t *localbg = ((offset) ? kp_itembg[2] : kp_itembg[0]); patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]); INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... @@ -1036,28 +1036,42 @@ static void K_drawKartItem(void) UINT8 *colmap = NULL; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff + fixed_t rouletteOffset = 0; + const INT32 rouletteBox = 36; + INT32 i; + if (stplyr->itemRoulette.active == true) { - const SINT8 item = K_ItemResultToType(stplyr->itemRoulette.itemList[ stplyr->itemRoulette.index ]); + rouletteOffset = K_GetRouletteOffset(&stplyr->itemRoulette, rendertimefrac); if (stplyr->skincolor) { localcolor = stplyr->skincolor; } - switch (item) + for (i = 0; i < 3; i++) { - case KITEM_INVINCIBILITY: - localpatch = localinv; - break; + const SINT8 indexOfs = i-1; + const size_t index = (stplyr->itemRoulette.index + indexOfs) % stplyr->itemRoulette.itemListLen; - case KITEM_ORBINAUT: - localpatch = kp_orbinaut[3 + offset]; - break; + const SINT8 result = stplyr->itemRoulette.itemList[index]; + const SINT8 item = K_ItemResultToType(result); + const UINT8 amt = K_ItemResultToAmount(result); - default: - localpatch = K_GetCachedItemPatch(item, offset); - break; + switch (item) + { + case KITEM_INVINCIBILITY: + localpatch[i] = localinv; + break; + + case KITEM_ORBINAUT: + localpatch[i] = kp_orbinaut[(offset ? 4 : min(amt-1, 3))]; + break; + + default: + localpatch[i] = K_GetCachedItemPatch(item, offset); + break; + } } } else @@ -1066,23 +1080,26 @@ static void K_drawKartItem(void) // The only actual reason is to make sneakers line up this way in the code below // This shouldn't have any actual baring over how it functions // Hyudoro is first, because we're drawing it on top of the player's current item + + rouletteOffset = stplyr->karthud[khud_rouletteoffset]; + if (stplyr->stealingtimer < 0) { if (leveltime & 2) - localpatch = kp_hyudoro[offset]; + localpatch[0] = kp_hyudoro[offset]; else - localpatch = kp_nodraw; + localpatch[0] = kp_nodraw; } else if ((stplyr->stealingtimer > 0) && (leveltime & 2)) { - localpatch = kp_hyudoro[offset]; + localpatch[0] = kp_hyudoro[offset]; } else if (stplyr->eggmanexplode > 1) { if (leveltime & 1) - localpatch = kp_eggman[offset]; + localpatch[0] = kp_eggman[offset]; else - localpatch = kp_nodraw; + localpatch[0] = kp_nodraw; } else if (stplyr->ballhogcharge > 0) { @@ -1090,9 +1107,9 @@ static void K_drawKartItem(void) maxl = (((stplyr->itemamount-1) * BALLHOGINCREMENT) + 1); if (leveltime & 1) - localpatch = kp_ballhog[offset]; + localpatch[0] = kp_ballhog[offset]; else - localpatch = kp_nodraw; + localpatch[0] = kp_nodraw; } else if (stplyr->rocketsneakertimer > 1) { @@ -1100,16 +1117,16 @@ static void K_drawKartItem(void) maxl = (itemtime*3) - barlength; if (leveltime & 1) - localpatch = kp_rocketsneaker[offset]; + localpatch[0] = kp_rocketsneaker[offset]; else - localpatch = kp_nodraw; + localpatch[0] = kp_nodraw; } else if (stplyr->sadtimer > 0) { if (leveltime & 2) - localpatch = kp_sadface[offset]; + localpatch[0] = kp_sadface[offset]; else - localpatch = kp_nodraw; + localpatch[0] = kp_nodraw; } else { @@ -1119,12 +1136,12 @@ static void K_drawKartItem(void) switch (stplyr->itemtype) { case KITEM_INVINCIBILITY: - localpatch = localinv; + localpatch[0] = localinv; localbg = kp_itembg[offset+1]; break; case KITEM_ORBINAUT: - localpatch = kp_orbinaut[(offset ? 4 : min(stplyr->itemamount-1, 3))]; + localpatch[0] = kp_orbinaut[(offset ? 4 : min(stplyr->itemamount-1, 3))]; break; case KITEM_SPB: @@ -1135,15 +1152,15 @@ static void K_drawKartItem(void) /*FALLTHRU*/ default: - localpatch = K_GetCachedItemPatch(stplyr->itemtype, offset); + localpatch[0] = K_GetCachedItemPatch(stplyr->itemtype, offset); - if (localpatch == NULL) - localpatch = kp_nodraw; // diagnose underflows + if (localpatch[0] == NULL) + localpatch[0] = kp_nodraw; // diagnose underflows break; } if ((stplyr->pflags & PF_ITEMOUT) && !(leveltime & 1)) - localpatch = kp_nodraw; + localpatch[0] = kp_nodraw; } if (stplyr->karthud[khud_itemblink] && (leveltime & 1)) @@ -1194,18 +1211,31 @@ static void K_drawKartItem(void) V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|fflags, localbg); - //V_SetClipRect((fx + 10) << FRACBITS, (fy + 10) << FRACBITS, 30 << FRACBITS, 30 << FRACBITS, V_HUDTRANS|V_SLIDEIN|fflags); - - // Then, the numbers: - if (stplyr->itemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) + if (stplyr->itemRoulette.active == true) { + V_SetClipRect((fx + 7) << FRACBITS, (fy + 7) << FRACBITS, rouletteBox << FRACBITS, rouletteBox << FRACBITS, V_HUDTRANS|V_SLIDEIN|fflags); + + // Need to draw these in a particular order, for sorting. + V_DrawFixedPatch(fx<itemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) + { + // Then, the numbers: V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]); // flip this graphic for p2 and p4 in split and shift it. - V_DrawFixedPatch(fx<itemamount)); else V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SLIDEIN|fflags, va("x%d", stplyr->itemamount)); + } else { V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|V_SLIDEIN|fflags, kp_itemx); @@ -1213,9 +1243,9 @@ static void K_drawKartItem(void) } } else - V_DrawFixedPatch(fx<karthud[khud_itemblink] = 0; } + if (player->karthud[khud_rouletteoffset] != 0) + { + player->karthud[khud_rouletteoffset] = FixedMul(player->karthud[khud_rouletteoffset], FRACUNIT*3/4); + } + if (!(gametyperules & GTR_SPHERES)) { if (player->mo && player->mo->hitlag <= 0) diff --git a/src/k_roulette.c b/src/k_roulette.c index c5a493365..7e3d7a3b7 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -146,14 +146,12 @@ static kartitems_t K_KartItemReelBreakTheCapsules[] = KITEM_NONE }; -#if 0 static kartitems_t K_KartItemReelBoss[] = { KITEM_ORBINAUT, KITEM_BANANA, KITEM_NONE }; -#endif boolean K_ItemEnabled(SINT8 item) { @@ -809,7 +807,16 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) // SPECIAL CASE No. 2: // Use a special, pre-determined item reel for Time Attack / Free Play - if (modeattacking || playing <= 1) + if (bossinfo.boss == true) + { + for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, K_KartItemReelBoss[i]); + } + + return; + } + else if (modeattacking || playing <= 1) { switch (gametype) { @@ -819,7 +826,6 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) for (i = 0; K_KartItemReelTimeAttack[i] != KITEM_NONE; i++) { K_PushToRouletteItemList(roulette, K_KartItemReelTimeAttack[i]); - CONS_Printf("Added %d\n", K_KartItemReelTimeAttack[i]); } break; } @@ -828,7 +834,6 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) for (i = 0; K_KartItemReelBreakTheCapsules[i] != KITEM_NONE; i++) { K_PushToRouletteItemList(roulette, K_KartItemReelBreakTheCapsules[i]); - CONS_Printf("Added %d\n", K_KartItemReelBreakTheCapsules[i]); } break; } @@ -875,7 +880,7 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) { rngRoll = P_RandomKey(PR_ITEM_ROULETTE, totalSpawnChance); - for (i = 0; i < NUMKARTRESULTS && spawnChance[i] <= rngRoll; i++) + for (i = 1; i < NUMKARTRESULTS && spawnChance[i] <= rngRoll; i++) { continue; } @@ -924,6 +929,14 @@ static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) player->itemamount = K_ItemResultToAmount(getitem); } +fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta) +{ + const fixed_t curTic = (roulette->tics << FRACBITS) - renderDelta; + const fixed_t midTic = roulette->speed * (FRACUNIT >> 1); + + return FixedMul(FixedDiv(midTic - curTic, ((roulette->speed + 1) << FRACBITS)), ROULETTE_SPACING); +} + void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) { itemroulette_t *const roulette = &player->itemRoulette; @@ -970,6 +983,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) //player->karthud[khud_itemblink] = TICRATE; //player->karthud[khud_itemblinkmode] = 1; + //player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT); if (P_IsDisplayPlayer(player) && !demo.freecam) { @@ -984,6 +998,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) player->karthud[khud_itemblink] = TICRATE; player->karthud[khud_itemblinkmode] = 0; + player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT); if (P_IsDisplayPlayer(player) && !demo.freecam) { diff --git a/src/k_roulette.h b/src/k_roulette.h index 8fd302abf..81e12aae3 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -30,6 +30,9 @@ boolean K_ForcedSPB(player_t *const player); void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette); void K_StartEggmanRoulette(player_t *const player); +#define ROULETTE_SPACING (36 << FRACBITS) +fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta); + void K_KartItemRoulette(player_t *const player, ticcmd_t *cmd); #endif // __K_ROULETTE_H__ From 10145b75d1dc4c10c7f4ad4caf6088490e06e798 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 02:59:58 -0500 Subject: [PATCH 04/24] Fix incorrect item reel in actual races --- src/k_roulette.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 7e3d7a3b7..09bd671d7 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -99,7 +99,7 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 }; -static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = +static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] = { //K L { 2, 1 }, // Sneaker @@ -309,7 +309,7 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool INT32 shieldType = KSHIELD_NONE; I_Assert(item > KITEM_NONE); // too many off by one scenarioes. - I_Assert(cv_items[NUMKARTRESULTS-2] != NULL); // Make sure this exists + I_Assert(item < NUMKARTRESULTS); if (K_ItemEnabled(item) == false) { @@ -554,7 +554,8 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(pingame)); } - return FixedInt(FixedRound(newOdds)); + newOdds = FixedInt(FixedRound(newOdds)); + return newOdds; } //{ SRB2kart Roulette Code - Distance Based, yes waypoints @@ -575,7 +576,7 @@ UINT8 K_FindUseodds(player_t *const player, UINT32 playerDist) if (gametype == GT_BATTLE && i > 1) { oddsValid[i] = false; - break; + continue; } for (j = 1; j < NUMKARTRESULTS; j++) @@ -856,7 +857,7 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) for (i = 1; i < NUMKARTRESULTS; i++) { - UINT8 thisItemsOdds = K_KartGetItemOdds( + INT32 thisItemsOdds = K_KartGetItemOdds( useOdds, i, player->distancetofinish, player->bot, (player->bot && player->botvars.rival) @@ -879,7 +880,6 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) while (totalSpawnChance > 0) { rngRoll = P_RandomKey(PR_ITEM_ROULETTE, totalSpawnChance); - for (i = 1; i < NUMKARTRESULTS && spawnChance[i] <= rngRoll; i++) { continue; @@ -896,7 +896,15 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) K_PushToRouletteItemList(roulette, KITEM_SUPERRING); } - spawnChance[i]--; + for (; i < NUMKARTRESULTS; i++) + { + // Be sure to fix the remaining items' odds too. + if (spawnChance[i] > 0) + { + spawnChance[i]--; + } + } + totalSpawnChance--; } } From c8f3533b00a6df3dcdf324a14287845c5506f122 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 04:01:09 -0500 Subject: [PATCH 05/24] Roulette speed adjustments - Speeds up the farther in the course you are - Speeds up the further in the front you are - Slows down before the 20 second mark --- src/k_roulette.c | 86 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 09bd671d7..62d8e7fd1 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -62,11 +62,15 @@ #define SPBFORCEDIST (14*DISTVAR) // Distance when the game stops giving you bananas -#define ENDDIST (12*DISTVAR) +#define ENDDIST (18*DISTVAR) // Consistent seed used for item reels #define ITEM_REEL_SEED (0x22D5FAA8) +#define ROULETTE_SPEED_SLOWEST (12) +#define ROULETTE_SPEED_FASTEST (2) +#define ROULETTE_SPEED_DIST (224*DISTVAR) + static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker @@ -759,17 +763,66 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t roulette->itemListLen++; } -static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *const roulette, UINT8 playing) +static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *const roulette) { // TODO: Change speed based on two factors: // - Get faster when your distancetofinish is closer to 1st place's distancetofinish. (winning) // - Get faster based on overall distancetofinish (race progress) // Slowest speed should be 12 tics, fastest should be 3 tics. - (void)player; - (void)playing; + fixed_t frontRun = 0; + fixed_t progress = 0; + fixed_t total = 0; - roulette->tics = roulette->speed = 7; + UINT8 playing = 0; + + UINT32 firstDist = UINT32_MAX; + UINT32 ourDist = UINT32_MAX; + + size_t i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + playing++; + + if (players[i].position == 1) + { + firstDist = players[i].distancetofinish; + } + } + + ourDist = K_ScaleItemDistance(player->distancetofinish, playing); + firstDist = K_ScaleItemDistance(firstDist, playing); + + if (ourDist > ENDDIST) + { + // Being farther in the course makes your roulette faster. + progress = min(FRACUNIT, FixedDiv(ourDist - ENDDIST, ROULETTE_SPEED_DIST)); + } + + if (ourDist > firstDist) + { + // Frontrunning makes your roulette faster. + frontRun = min(FRACUNIT, FixedDiv(ourDist - firstDist, ENDDIST)); + } + + // Combine our two factors together. + total = min(FRACUNIT, (frontRun / 2) + (progress / 2)); + + if (leveltime < starttime + 20*TICRATE) + { + // Don't impact as much at the start. + // This makes it so that everyone gets to enjoy the lowest speed at the start. + fixed_t lerp = FRACUNIT - FixedDiv(max(0, leveltime - starttime), 10*TICRATE); + total += FixedMul(lerp, FRACUNIT - total); + } + + roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); } void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) @@ -785,18 +838,7 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) size_t i; K_InitRoulette(roulette); - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] == false || players[i].spectator == true) - { - continue; - } - - playing++; - } - - K_CalculateRouletteSpeed(player, roulette, playing); + K_CalculateRouletteSpeed(player, roulette); // SPECIAL CASE No. 1: // Give only the debug item if specified @@ -808,6 +850,16 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) // SPECIAL CASE No. 2: // Use a special, pre-determined item reel for Time Attack / Free Play + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + playing++; + } + if (bossinfo.boss == true) { for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) From e45cef44df8b05d823991e513a58eb285f4ed63b Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 04:35:51 -0500 Subject: [PATCH 06/24] Make bots stop roulette instead of waiting it out Done in kind of a lazy way, might revisit later. --- src/k_botitem.c | 18 +++--------------- src/k_roulette.c | 4 ++++ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/k_botitem.c b/src/k_botitem.c index e3054aa69..fe0837c84 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1394,27 +1394,15 @@ static void K_BotItemRings(player_t *player, ticcmd_t *cmd) --------------------------------------------------*/ static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) { - boolean mash = false; - if (K_ItemButtonWasDown(player) == true) { return; } - if (player->rings < 0 && K_ItemEnabled(KITEM_SUPERRING) == true) - { - // Uh oh, we need a loan! - // It'll be better in the long run for bots to lose an item set for 10 free rings. - mash = true; - } + // TODO: Would be nice to implement smarter behavior + // for selecting items. - // TODO: Mash based on how far behind you are, when items are - // almost garantueed to be in your favor. - - if (mash == true) - { - cmd->buttons |= BT_ATTACK; - } + cmd->buttons |= BT_ATTACK; } /*-------------------------------------------------- diff --git a/src/k_roulette.c b/src/k_roulette.c index 62d8e7fd1..5a0d73ebb 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -823,6 +823,10 @@ static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *con } roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); + + // Make them select their item after a little while. + // One of the few instances of bot RNG, would be nice to remove it. + player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); } void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) From 13d7c791f554d52613eb94861f90bff1bf3bb568 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 04:36:32 -0500 Subject: [PATCH 07/24] Make the slowest speed slower Was designed for Snap's tiny item sprites, for the huge ones it still feels pretty fast. The fastest speed is unchanged, though :) --- src/k_roulette.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 5a0d73ebb..124d0a67c 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -67,7 +67,7 @@ // Consistent seed used for item reels #define ITEM_REEL_SEED (0x22D5FAA8) -#define ROULETTE_SPEED_SLOWEST (12) +#define ROULETTE_SPEED_SLOWEST (20) #define ROULETTE_SPEED_FASTEST (2) #define ROULETTE_SPEED_DIST (224*DISTVAR) From 8d2eb9220d4905a10db92c35467c669cbd53ed10 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 11:46:03 -0500 Subject: [PATCH 08/24] Make Super Ring flood happen at 0 rings too --- src/k_roulette.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 124d0a67c..f154e261e 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -945,9 +945,9 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) // If we're in ring debt, pad out the reel with // a BUNCH of Super Rings. - if (K_ItemEnabled(KITEM_SUPERRING) - && player->rings < 0 - && !(gametyperules & GTR_SPHERES)) + if (K_ItemEnabled(KITEM_SUPERRING) == true + && player->rings <= 0 + && (gametyperules & GTR_SPHERES) == 0) { K_PushToRouletteItemList(roulette, KITEM_SUPERRING); } From 63f6b18d59c9f9e719710a319a3a63dd6f3bc4f8 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 11:55:28 -0500 Subject: [PATCH 09/24] No user for roulette itemlist Fixes the occasional Z_Free complaint --- src/k_roulette.c | 9 ++++----- src/p_saveg.c | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index f154e261e..2a62bf7df 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -730,16 +730,15 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->itemList = Z_Calloc( sizeof(SINT8) * roulette->itemListCap, PU_LEVEL, - &roulette->itemList + NULL ); } - memset(roulette->itemList, KITEM_NONE, sizeof(SINT8) * roulette->itemListCap); roulette->itemListLen = 0; - roulette->index = 0; + roulette->elapsed = 0; - roulette->tics = roulette->speed = 3; // Some default speed + roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST; // Some default speed roulette->active = true; roulette->eggman = false; } @@ -755,7 +754,7 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t roulette->itemList, sizeof(SINT8) * roulette->itemListCap, PU_LEVEL, - &roulette->itemList + NULL ); } diff --git a/src/p_saveg.c b/src/p_saveg.c index fee092df9..0587b96f8 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -730,7 +730,7 @@ static void P_NetUnArchivePlayers(void) players[i].itemRoulette.itemList = Z_Calloc( sizeof(SINT8) * players[i].itemRoulette.itemListCap, PU_LEVEL, - &players[i].itemRoulette.itemList + NULL ); } else @@ -739,7 +739,7 @@ static void P_NetUnArchivePlayers(void) players[i].itemRoulette.itemList, sizeof(SINT8) * players[i].itemRoulette.itemListCap, PU_LEVEL, - &players[i].itemRoulette.itemList + NULL ); } From 9e3ded610d33d55663efbeeb742b49a595c7e046 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 16:19:45 -0500 Subject: [PATCH 10/24] Get rid of my TODO notes I implemented it, lol --- src/k_roulette.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 2a62bf7df..2357cde62 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -764,11 +764,6 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *const roulette) { - // TODO: Change speed based on two factors: - // - Get faster when your distancetofinish is closer to 1st place's distancetofinish. (winning) - // - Get faster based on overall distancetofinish (race progress) - // Slowest speed should be 12 tics, fastest should be 3 tics. - fixed_t frontRun = 0; fixed_t progress = 0; fixed_t total = 0; From b0537de79e41de2c31495948be8e1b41acfecfcb Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 16:44:24 -0500 Subject: [PATCH 11/24] Re-implement debugitemodds --- src/d_player.h | 1 + src/k_hud.c | 92 ++++++++++++------------------------------------ src/k_roulette.c | 7 ++-- src/p_saveg.c | 20 +++++++---- 4 files changed, 41 insertions(+), 79 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 842edbc0e..2249d9f46 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -339,6 +339,7 @@ struct itemroulette_t size_t itemListLen; SINT8 *itemList; + UINT8 useOdds; size_t index; UINT8 sound; diff --git a/src/k_hud.c b/src/k_hud.c index 5ba874913..2a78c09de 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4468,13 +4468,7 @@ K_drawMiniPing (void) static void K_drawDistributionDebugger(void) { -#if 1 - // TODO: Create a roulette struct and initialize it, - // and use that to draw the data, instead of recalculating - // most of it. - return; -#else - patch_t *items[NUMKARTRESULTS] = { + patch_t *patches[NUMKARTRESULTS] = { kp_sadface[1], kp_sneaker[1], kp_rocketsneaker[1], @@ -4506,87 +4500,47 @@ static void K_drawDistributionDebugger(void) kp_orbinaut[4], kp_jawz[1] }; - UINT8 useodds = 0; - UINT8 pingame = 0, bestbumper = 0; - UINT32 pdis = 0; - INT32 i; - INT32 x = -9, y = -9; + + itemroulette_t rouletteData = {0}; + + const INT32 space = 32; + const INT32 pad = 9; + + INT32 x = -pad, y = -pad; + size_t i; if (stplyr != &players[displayplayers[0]]) // only for p1 - return; - - if (K_ForcedSPB(stplyr) == true) { - V_DrawScaledPatch(x, y, V_SNAPTOTOP, items[KITEM_SPB]); - V_DrawThinString(x+11, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, "EX"); return; } - // The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice - for (i = 0; i < MAXPLAYERS; i++) + K_StartItemRoulette(stplyr, &rouletteData); + + for (i = 0; i < rouletteData.itemListLen; i++) { - if (!playeringame[i] || players[i].spectator) - continue; - pingame++; - if (players[i].bumpers > bestbumper) - bestbumper = players[i].bumpers; - } + const kartitems_t item = rouletteData.itemList[i]; + UINT8 amount = 1; - // lovely double loop...... - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator - && players[i].position == 1) - { - // This player is first! Yay! - pdis = stplyr->distancetofinish - players[i].distancetofinish; - break; - } - } - - pdis = K_ScaleItemDistance(pdis, pingame); - - if (stplyr->bot && stplyr->botvars.rival) - { - // Rival has better odds :) - pdis = (15 * pdis) / 14; - } - - useodds = K_FindUseodds(stplyr, pdis); - - for (i = 1; i < NUMKARTRESULTS; i++) - { - INT32 itemodds = K_KartGetItemOdds( - useodds, i, - stplyr->distancetofinish, - 0, - stplyr->bot, (stplyr->bot && stplyr->botvars.rival) - ); - INT32 amount = 1; - - if (itemodds <= 0) - continue; - - V_DrawScaledPatch(x, y, V_SNAPTOTOP, items[i]); - V_DrawThinString(x+11, y+31, V_SNAPTOTOP, va("%d", itemodds)); + V_DrawScaledPatch(x, y, V_SNAPTOTOP, patches[item]); // Display amount for multi-items - amount = K_ItemResultToAmount(i); + amount = K_ItemResultToAmount(item); if (amount > 1) { V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("x%d", amount)); } - x += 32; - if (x >= 297) + y += space; + if (y > BASEVIDHEIGHT - space - pad) { - x = -9; - y += 32; + x += space; + y = -pad; } } - V_DrawString(0, 0, V_SNAPTOTOP, va("USEODDS %d", useodds)); -#endif + V_DrawString(0, 0, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("Table #%d", rouletteData.useOdds)); + + Z_Free(rouletteData.itemList); } static void K_DrawWaypointDebugger(void) diff --git a/src/k_roulette.c b/src/k_roulette.c index 2357cde62..5951b4f42 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -736,9 +736,11 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->itemListLen = 0; roulette->index = 0; + roulette->useOdds = UINT8_MAX; roulette->elapsed = 0; roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST; // Some default speed + roulette->active = true; roulette->eggman = false; } @@ -828,7 +830,6 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) UINT8 playing = 0; UINT32 playerDist = UINT32_MAX; - UINT8 useOdds = 0; UINT32 spawnChance[NUMKARTRESULTS] = {0}; UINT32 totalSpawnChance = 0; size_t rngRoll = 0; @@ -903,12 +904,12 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) playerDist = K_GetItemRouletteDistance(player, playing); - useOdds = K_FindUseodds(player, playerDist); + roulette->useOdds = K_FindUseodds(player, playerDist); for (i = 1; i < NUMKARTRESULTS; i++) { INT32 thisItemsOdds = K_KartGetItemOdds( - useOdds, i, + roulette->useOdds, i, player->distancetofinish, player->bot, (player->bot && player->botvars.rival) ); diff --git a/src/p_saveg.c b/src/p_saveg.c index 0587b96f8..99a06782d 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -416,10 +416,13 @@ static void P_NetArchivePlayers(void) { WRITESINT8(save_p, players[i].itemRoulette.itemList[j]); } + WRITEUINT8(save_p, players[i].itemRoulette.useOdds); WRITEUINT32(save_p, players[i].itemRoulette.index); + WRITEUINT8(save_p, players[i].itemRoulette.sound); WRITEUINT32(save_p, players[i].itemRoulette.speed); WRITEUINT32(save_p, players[i].itemRoulette.tics); WRITEUINT32(save_p, players[i].itemRoulette.elapsed); + WRITEUINT8(save_p, players[i].itemRoulette.eggman); } } @@ -721,9 +724,9 @@ static void P_NetUnArchivePlayers(void) players[i].botvars.spindashconfirm = READUINT32(save_p); // itemroulette_t - players[i].itemRoulette.active = READUINT8(save_p); - players[i].itemRoulette.itemListCap = READUINT32(save_p); - players[i].itemRoulette.itemListLen = READUINT32(save_p); + players[i].itemRoulette.active = (boolean)READUINT8(save_p); + players[i].itemRoulette.itemListCap = (size_t)READUINT32(save_p); + players[i].itemRoulette.itemListLen = (size_t)READUINT32(save_p); if (players[i].itemRoulette.itemList == NULL) { @@ -748,10 +751,13 @@ static void P_NetUnArchivePlayers(void) players[i].itemRoulette.itemList[j] = READSINT8(save_p); } - players[i].itemRoulette.index = READUINT32(save_p); - players[i].itemRoulette.speed = READUINT32(save_p); - players[i].itemRoulette.tics = READUINT32(save_p); - players[i].itemRoulette.elapsed = READUINT32(save_p); + players[i].itemRoulette.useOdds = READUINT8(save_p); + players[i].itemRoulette.index = (size_t)READUINT32(save_p); + players[i].itemRoulette.sound = READUINT8(save_p); + players[i].itemRoulette.speed = (tic_t)READUINT32(save_p); + players[i].itemRoulette.tics = (tic_t)READUINT32(save_p); + players[i].itemRoulette.elapsed = (tic_t)READUINT32(save_p); + players[i].itemRoulette.eggman = (boolean)READUINT8(save_p); //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } From a73e62c163fe94b7a90d9805923de6d8f6c23b41 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 16:59:29 -0500 Subject: [PATCH 12/24] Re-add roulette itemList user Turns out the Z_Free error is something else in r_patch.c, seems unrelated to what I made. --- src/k_roulette.c | 4 ++-- src/p_saveg.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 5951b4f42..ad0c754e4 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -730,7 +730,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->itemList = Z_Calloc( sizeof(SINT8) * roulette->itemListCap, PU_LEVEL, - NULL + &roulette->itemList ); } @@ -756,7 +756,7 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t roulette->itemList, sizeof(SINT8) * roulette->itemListCap, PU_LEVEL, - NULL + &roulette->itemList ); } diff --git a/src/p_saveg.c b/src/p_saveg.c index 99a06782d..cb4aa7577 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -733,7 +733,7 @@ static void P_NetUnArchivePlayers(void) players[i].itemRoulette.itemList = Z_Calloc( sizeof(SINT8) * players[i].itemRoulette.itemListCap, PU_LEVEL, - NULL + &players[i].itemRoulette.itemList ); } else @@ -742,7 +742,7 @@ static void P_NetUnArchivePlayers(void) players[i].itemRoulette.itemList, sizeof(SINT8) * players[i].itemRoulette.itemListCap, PU_LEVEL, - NULL + &players[i].itemRoulette.itemList ); } From 8534703de6278a350e7f507e0861f3678eabaf58 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 17:43:32 -0500 Subject: [PATCH 13/24] Further improve item debugger visibility - Half the scale of the item graphics, and reduce the spacing, so more of the reel can fit in one column. - Move the item table text over to the right, depending on how many columns were drawn. - Display the item roulette speed, as well. --- src/k_hud.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 2a78c09de..6dc556f80 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4503,10 +4503,12 @@ static void K_drawDistributionDebugger(void) itemroulette_t rouletteData = {0}; - const INT32 space = 32; - const INT32 pad = 9; + const fixed_t scale = (FRACUNIT >> 1); + const fixed_t space = 24 * scale; + const fixed_t pad = 9 * scale; - INT32 x = -pad, y = -pad; + fixed_t x = -pad; + fixed_t y = -pad; size_t i; if (stplyr != &players[displayplayers[0]]) // only for p1 @@ -4521,24 +4523,33 @@ static void K_drawDistributionDebugger(void) const kartitems_t item = rouletteData.itemList[i]; UINT8 amount = 1; - V_DrawScaledPatch(x, y, V_SNAPTOTOP, patches[item]); + if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) + { + x += space; + y = -pad; + } + + V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, patches[item], NULL); // Display amount for multi-items amount = K_ItemResultToAmount(item); if (amount > 1) { - V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("x%d", amount)); + V_DrawStringScaled( + x + (18 * scale), + y + (23 * scale), + scale, FRACUNIT, FRACUNIT, + V_ALLOWLOWERCASE|V_SNAPTOTOP, + NULL, HU_FONT, + va("x%d", amount) + ); } y += space; - if (y > BASEVIDHEIGHT - space - pad) - { - x += space; - y = -pad; - } } - V_DrawString(0, 0, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("Table #%d", rouletteData.useOdds)); + V_DrawString((x >> FRACBITS) + 20, 2, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); + V_DrawString((x >> FRACBITS) + 20, 10, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); Z_Free(rouletteData.itemList); } From 7713ce0ebbbd34fdda274f04c2a2e59807894d83 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 12 Dec 2022 19:41:33 -0500 Subject: [PATCH 14/24] Fix speed being messed up near level start Used the wrong constant, leftover from an earlier experiment that was only partially reverted. Fixes "ghost" roulettes. --- src/k_roulette.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index ad0c754e4..dc0ae76c7 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -814,7 +814,7 @@ static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *con { // Don't impact as much at the start. // This makes it so that everyone gets to enjoy the lowest speed at the start. - fixed_t lerp = FRACUNIT - FixedDiv(max(0, leveltime - starttime), 10*TICRATE); + fixed_t lerp = FRACUNIT - FixedDiv(max(0, leveltime - starttime), 20*TICRATE); total += FixedMul(lerp, FRACUNIT - total); } From 59ee8177d7faa9b6d269d96b8fcd553083c411d9 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 13 Dec 2022 00:00:37 -0500 Subject: [PATCH 15/24] Try some things for roulette sync - PU_STATIC instead of PU_LEVEL, since player_t is always kept around anyway. - Don't alloc itemList when cap is 0 - Read/write 0 when itemList is NULL --- src/k_roulette.c | 4 ++-- src/p_saveg.c | 52 ++++++++++++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index dc0ae76c7..d07246a66 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -729,7 +729,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->itemListCap = 8; roulette->itemList = Z_Calloc( sizeof(SINT8) * roulette->itemListCap, - PU_LEVEL, + PU_STATIC, &roulette->itemList ); } @@ -755,7 +755,7 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t roulette->itemList = Z_Realloc( roulette->itemList, sizeof(SINT8) * roulette->itemListCap, - PU_LEVEL, + PU_STATIC, &roulette->itemList ); } diff --git a/src/p_saveg.c b/src/p_saveg.c index cb4aa7577..b69c63a5b 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -412,9 +412,16 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].itemRoulette.active); WRITEUINT32(save_p, players[i].itemRoulette.itemListCap); WRITEUINT32(save_p, players[i].itemRoulette.itemListLen); - for (j = 0; (unsigned)j < players[i].itemRoulette.itemListLen; j++) + for (j = 0; j < (signed)players[i].itemRoulette.itemListLen; j++) { - WRITESINT8(save_p, players[i].itemRoulette.itemList[j]); + if (players[i].itemRoulette.itemList == NULL) + { + WRITESINT8(save_p, KITEM_NONE); + } + else + { + WRITESINT8(save_p, players[i].itemRoulette.itemList[j]); + } } WRITEUINT8(save_p, players[i].itemRoulette.useOdds); WRITEUINT32(save_p, players[i].itemRoulette.index); @@ -728,27 +735,34 @@ static void P_NetUnArchivePlayers(void) players[i].itemRoulette.itemListCap = (size_t)READUINT32(save_p); players[i].itemRoulette.itemListLen = (size_t)READUINT32(save_p); - if (players[i].itemRoulette.itemList == NULL) + if (players[i].itemRoulette.itemListCap > 0) { - players[i].itemRoulette.itemList = Z_Calloc( - sizeof(SINT8) * players[i].itemRoulette.itemListCap, - PU_LEVEL, - &players[i].itemRoulette.itemList - ); - } - else - { - players[i].itemRoulette.itemList = Z_Realloc( - players[i].itemRoulette.itemList, - sizeof(SINT8) * players[i].itemRoulette.itemListCap, - PU_LEVEL, - &players[i].itemRoulette.itemList - ); + if (players[i].itemRoulette.itemList == NULL) + { + players[i].itemRoulette.itemList = Z_Calloc( + sizeof(SINT8) * players[i].itemRoulette.itemListCap, + PU_STATIC, + &players[i].itemRoulette.itemList + ); + } + else + { + players[i].itemRoulette.itemList = Z_Realloc( + players[i].itemRoulette.itemList, + sizeof(SINT8) * players[i].itemRoulette.itemListCap, + PU_STATIC, + &players[i].itemRoulette.itemList + ); + } } - for (j = 0; (unsigned)j < players[i].itemRoulette.itemListLen; j++) + for (j = 0; j < (signed)players[i].itemRoulette.itemListLen; j++) { - players[i].itemRoulette.itemList[j] = READSINT8(save_p); + SINT8 item = READSINT8(save_p); + if (players[i].itemRoulette.itemList != NULL) + { + players[i].itemRoulette.itemList[j] = item; + } } players[i].itemRoulette.useOdds = READUINT8(save_p); From c1845673615cb1e5878950a03072848bca285ea7 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 13 Dec 2022 12:06:52 -0500 Subject: [PATCH 16/24] No longer dynamically allocate roulette list Done with the issues trying to sync this memory over the wire, so now it's just a long static array... --- src/d_player.h | 10 +++++++++ src/k_roulette.c | 26 ++++++++++++++++++++++- src/p_saveg.c | 54 ++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 2249d9f46..fc45174d2 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -331,13 +331,23 @@ struct skybox_t { }; // player_t struct for item roulette variables + +// Doing this the right way is causing problems. +// so FINE, it's a static length now. +#define ITEM_LIST_SIZE (NUMKARTRESULTS * 20) + struct itemroulette_t { boolean active; +#ifdef ITEM_LIST_SIZE + size_t itemListLen; + SINT8 itemList[ITEM_LIST_SIZE]; +#else size_t itemListCap; size_t itemListLen; SINT8 *itemList; +#endif UINT8 useOdds; size_t index; diff --git a/src/k_roulette.c b/src/k_roulette.c index d07246a66..b136113b6 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -724,6 +724,7 @@ boolean K_ForcedSPB(player_t *const player) static void K_InitRoulette(itemroulette_t *const roulette) { +#ifndef ITEM_LIST_SIZE if (roulette->itemList == NULL) { roulette->itemListCap = 8; @@ -732,7 +733,13 @@ static void K_InitRoulette(itemroulette_t *const roulette) PU_STATIC, &roulette->itemList ); + + if (roulette->itemList == NULL) + { + I_Error("Not enough memory for item roulette list\n"); + } } +#endif roulette->itemListLen = 0; roulette->index = 0; @@ -747,6 +754,13 @@ static void K_InitRoulette(itemroulette_t *const roulette) static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t item) { +#ifdef ITEM_LIST_SIZE + if (roulette->itemListLen >= ITEM_LIST_SIZE) + { + I_Error("Out of space for item reel! Go and make ITEM_LIST_SIZE bigger I guess?\n"); + return; + } +#else I_Assert(roulette->itemList != NULL); if (roulette->itemListLen >= roulette->itemListCap) @@ -758,7 +772,13 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t PU_STATIC, &roulette->itemList ); + + if (roulette->itemList == NULL) + { + I_Error("Not enough memory for item roulette list\n"); + } } +#endif roulette->itemList[ roulette->itemListLen ] = item; roulette->itemListLen++; @@ -1008,7 +1028,11 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) return; } - if (roulette->itemList == NULL || roulette->itemListLen == 0) + if (roulette->itemListLen == 0 +#ifndef ITEM_LIST_SIZE + || roulette->itemList == NULL +#endif + ) { // Invalid roulette setup. // Escape before we run into issues. diff --git a/src/p_saveg.c b/src/p_saveg.c index b69c63a5b..46aa64147 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -96,7 +96,7 @@ static void P_NetArchivePlayers(void) { INT32 i, j; UINT16 flags; -// size_t q; + size_t q; WRITEUINT32(save_p, ARCHIVEBLOCK_PLAYERS); @@ -410,19 +410,39 @@ static void P_NetArchivePlayers(void) // itemroulette_t WRITEUINT8(save_p, players[i].itemRoulette.active); - WRITEUINT32(save_p, players[i].itemRoulette.itemListCap); + +#ifdef ITEM_LIST_SIZE WRITEUINT32(save_p, players[i].itemRoulette.itemListLen); - for (j = 0; j < (signed)players[i].itemRoulette.itemListLen; j++) + + for (q = 0; q < ITEM_LIST_SIZE; q++) { - if (players[i].itemRoulette.itemList == NULL) + if (q >= players[i].itemRoulette.itemListLen) { WRITESINT8(save_p, KITEM_NONE); } else { - WRITESINT8(save_p, players[i].itemRoulette.itemList[j]); + WRITESINT8(save_p, players[i].itemRoulette.itemList[q]); } } +#else + if (players[i].itemRoulette.itemList == NULL) + { + WRITEUINT32(save_p, 0); + WRITEUINT32(save_p, 0); + } + else + { + WRITEUINT32(save_p, players[i].itemRoulette.itemListCap); + WRITEUINT32(save_p, players[i].itemRoulette.itemListLen); + + for (q = 0; q < players[i].itemRoulette.itemListLen; q++) + { + WRITESINT8(save_p, players[i].itemRoulette.itemList[q]); + } + } +#endif + WRITEUINT8(save_p, players[i].itemRoulette.useOdds); WRITEUINT32(save_p, players[i].itemRoulette.index); WRITEUINT8(save_p, players[i].itemRoulette.sound); @@ -437,6 +457,7 @@ static void P_NetUnArchivePlayers(void) { INT32 i, j; UINT16 flags; + size_t q; if (READUINT32(save_p) != ARCHIVEBLOCK_PLAYERS) I_Error("Bad $$$.sav at archive block Players"); @@ -732,6 +753,15 @@ static void P_NetUnArchivePlayers(void) // itemroulette_t players[i].itemRoulette.active = (boolean)READUINT8(save_p); + +#ifdef ITEM_LIST_SIZE + players[i].itemRoulette.itemListLen = (size_t)READUINT32(save_p); + + for (q = 0; q < ITEM_LIST_SIZE; q++) + { + players[i].itemRoulette.itemList[q] = READSINT8(save_p); + } +#else players[i].itemRoulette.itemListCap = (size_t)READUINT32(save_p); players[i].itemRoulette.itemListLen = (size_t)READUINT32(save_p); @@ -754,16 +784,18 @@ static void P_NetUnArchivePlayers(void) &players[i].itemRoulette.itemList ); } - } - for (j = 0; j < (signed)players[i].itemRoulette.itemListLen; j++) - { - SINT8 item = READSINT8(save_p); - if (players[i].itemRoulette.itemList != NULL) + if (players[i].itemRoulette.itemList == NULL) { - players[i].itemRoulette.itemList[j] = item; + I_Error("Not enough memory for item roulette list\n"); + } + + for (q = 0; q < players[i].itemRoulette.itemListLen; q++) + { + players[i].itemRoulette.itemList[q] = READSINT8(save_p); } } +#endif players[i].itemRoulette.useOdds = READUINT8(save_p); players[i].itemRoulette.index = (size_t)READUINT32(save_p); From 2a546df3fbc7caa3012b2dba92215769cec3eaa7 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 13 Dec 2022 12:44:11 -0500 Subject: [PATCH 17/24] Fix debugger breaking, reduce size --- src/d_player.h | 2 +- src/k_hud.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/d_player.h b/src/d_player.h index fc45174d2..1a447d8de 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -334,7 +334,7 @@ struct skybox_t { // Doing this the right way is causing problems. // so FINE, it's a static length now. -#define ITEM_LIST_SIZE (NUMKARTRESULTS * 20) +#define ITEM_LIST_SIZE (NUMKARTRESULTS << 2) struct itemroulette_t { diff --git a/src/k_hud.c b/src/k_hud.c index 6dc556f80..4e73b85a0 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4551,7 +4551,9 @@ static void K_drawDistributionDebugger(void) V_DrawString((x >> FRACBITS) + 20, 2, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); V_DrawString((x >> FRACBITS) + 20, 10, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); +#ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); +#endif } static void K_DrawWaypointDebugger(void) From 6b8a011aa1561137e8997f552236067e456ab8b3 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 13 Dec 2022 18:02:21 -0500 Subject: [PATCH 18/24] Flicker other items when selecting Makes the lerp back to the middle less jarring, since the items that were just there won't disappear. --- src/k_hud.c | 166 +++++++++++++++++++++++++++++++-------------------- src/k_kart.c | 11 +++- 2 files changed, 112 insertions(+), 65 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 4e73b85a0..30c41e227 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1023,7 +1023,7 @@ 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); - patch_t *localpatch[3] = {kp_nodraw}; + patch_t *localpatch[3] = { kp_nodraw }; patch_t *localbg = ((offset) ? kp_itembg[2] : kp_itembg[0]); patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]); INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... @@ -1031,24 +1031,17 @@ static void K_drawKartItem(void) INT32 itembar = 0; INT32 maxl = 0; // itembar's normal highest value const INT32 barlength = (r_splitscreen > 1 ? 12 : 26); - UINT16 localcolor = SKINCOLOR_NONE; - SINT8 colormode = TC_RAINBOW; - UINT8 *colmap = NULL; + UINT16 localcolor[3] = { stplyr->skincolor }; + SINT8 colormode[3] = { TC_RAINBOW }; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff fixed_t rouletteOffset = 0; const INT32 rouletteBox = 36; INT32 i; - if (stplyr->itemRoulette.active == true) + if (stplyr->itemRoulette.itemListLen > 0) { - rouletteOffset = K_GetRouletteOffset(&stplyr->itemRoulette, rendertimefrac); - - if (stplyr->skincolor) - { - localcolor = stplyr->skincolor; - } - + // Init with item roulette stuff. for (i = 0; i < 3; i++) { const SINT8 indexOfs = i-1; @@ -1074,6 +1067,11 @@ static void K_drawKartItem(void) } } } + + if (stplyr->itemRoulette.active == true) + { + rouletteOffset = K_GetRouletteOffset(&stplyr->itemRoulette, rendertimefrac); + } else { // I'm doing this a little weird and drawing mostly in reverse order @@ -1081,25 +1079,26 @@ static void K_drawKartItem(void) // This shouldn't have any actual baring over how it functions // Hyudoro is first, because we're drawing it on top of the player's current item + localcolor[1] = SKINCOLOR_NONE; rouletteOffset = stplyr->karthud[khud_rouletteoffset]; if (stplyr->stealingtimer < 0) { if (leveltime & 2) - localpatch[0] = kp_hyudoro[offset]; + localpatch[1] = kp_hyudoro[offset]; else - localpatch[0] = kp_nodraw; + localpatch[1] = kp_nodraw; } else if ((stplyr->stealingtimer > 0) && (leveltime & 2)) { - localpatch[0] = kp_hyudoro[offset]; + localpatch[1] = kp_hyudoro[offset]; } else if (stplyr->eggmanexplode > 1) { if (leveltime & 1) - localpatch[0] = kp_eggman[offset]; + localpatch[1] = kp_eggman[offset]; else - localpatch[0] = kp_nodraw; + localpatch[1] = kp_nodraw; } else if (stplyr->ballhogcharge > 0) { @@ -1107,9 +1106,9 @@ static void K_drawKartItem(void) maxl = (((stplyr->itemamount-1) * BALLHOGINCREMENT) + 1); if (leveltime & 1) - localpatch[0] = kp_ballhog[offset]; + localpatch[1] = kp_ballhog[offset]; else - localpatch[0] = kp_nodraw; + localpatch[1] = kp_nodraw; } else if (stplyr->rocketsneakertimer > 1) { @@ -1117,16 +1116,16 @@ static void K_drawKartItem(void) maxl = (itemtime*3) - barlength; if (leveltime & 1) - localpatch[0] = kp_rocketsneaker[offset]; + localpatch[1] = kp_rocketsneaker[offset]; else - localpatch[0] = kp_nodraw; + localpatch[1] = kp_nodraw; } else if (stplyr->sadtimer > 0) { if (leveltime & 2) - localpatch[0] = kp_sadface[offset]; + localpatch[1] = kp_sadface[offset]; else - localpatch[0] = kp_nodraw; + localpatch[1] = kp_nodraw; } else { @@ -1136,12 +1135,12 @@ static void K_drawKartItem(void) switch (stplyr->itemtype) { case KITEM_INVINCIBILITY: - localpatch[0] = localinv; + localpatch[1] = localinv; localbg = kp_itembg[offset+1]; break; case KITEM_ORBINAUT: - localpatch[0] = kp_orbinaut[(offset ? 4 : min(stplyr->itemamount-1, 3))]; + localpatch[1] = kp_orbinaut[(offset ? 4 : min(stplyr->itemamount-1, 3))]; break; case KITEM_SPB: @@ -1152,34 +1151,41 @@ static void K_drawKartItem(void) /*FALLTHRU*/ default: - localpatch[0] = K_GetCachedItemPatch(stplyr->itemtype, offset); + localpatch[1] = K_GetCachedItemPatch(stplyr->itemtype, offset); - if (localpatch[0] == NULL) - localpatch[0] = kp_nodraw; // diagnose underflows + if (localpatch[1] == NULL) + localpatch[1] = kp_nodraw; // diagnose underflows break; } if ((stplyr->pflags & PF_ITEMOUT) && !(leveltime & 1)) - localpatch[0] = kp_nodraw; + localpatch[1] = kp_nodraw; } if (stplyr->karthud[khud_itemblink] && (leveltime & 1)) { - colormode = TC_BLINK; + colormode[1] = TC_BLINK; switch (stplyr->karthud[khud_itemblinkmode]) { case 2: - localcolor = K_RainbowColor(leveltime); + localcolor[1] = K_RainbowColor(leveltime); break; case 1: - localcolor = SKINCOLOR_RED; + localcolor[1] = SKINCOLOR_RED; break; default: - localcolor = SKINCOLOR_WHITE; + localcolor[1] = SKINCOLOR_WHITE; break; } } + else + { + // Hide the other items. + // Effectively lets the other roulette items + // show flicker away after you select. + localpatch[0] = localpatch[2] = kp_nodraw; + } } // pain and suffering defined below @@ -1206,45 +1212,77 @@ static void K_drawKartItem(void) } } - if (localcolor != SKINCOLOR_NONE) - colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); - V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|fflags, localbg); + // Need to draw these in a particular order, for sorting. + V_SetClipRect( + (fx + 7) << FRACBITS, (fy + 7) << FRACBITS, + rouletteBox << FRACBITS, rouletteBox << FRACBITS, + V_SLIDEIN|fflags + ); + + V_DrawFixedPatch( + fx<itemRoulette.active == true) { - V_SetClipRect((fx + 7) << FRACBITS, (fy + 7) << FRACBITS, rouletteBox << FRACBITS, rouletteBox << FRACBITS, V_HUDTRANS|V_SLIDEIN|fflags); - - // Need to draw these in a particular order, for sorting. - V_DrawFixedPatch(fx<itemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) - { - // Then, the numbers: - V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]); // flip this graphic for p2 and p4 in split and shift it. - - V_DrawFixedPatch(fx<itemamount)); - else - V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SLIDEIN|fflags, va("x%d", stplyr->itemamount)); - } - else - { - V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|V_SLIDEIN|fflags, kp_itemx); - V_DrawKartString(fx+38, fy+36, V_HUDTRANS|V_SLIDEIN|fflags, va("%d", stplyr->itemamount)); - } - } else { - V_DrawFixedPatch(fx<itemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) + { + // Then, the numbers: + V_DrawScaledPatch( + fx + (flipamount ? 48 : 0), fy, + V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), + kp_itemmulsticker[offset] + ); // flip this graphic for p2 and p4 in split and shift it. + + V_DrawFixedPatch( + fx<itemamount)); + else + V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SLIDEIN|fflags, va("x%d", stplyr->itemamount)); + } + else + { + V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|V_SLIDEIN|fflags, kp_itemx); + V_DrawKartString(fx+38, fy+36, V_HUDTRANS|V_SLIDEIN|fflags, va("%d", stplyr->itemamount)); + } + } + else + { + V_DrawFixedPatch( + fx<karthud[khud_rouletteoffset] != 0) { - player->karthud[khud_rouletteoffset] = FixedMul(player->karthud[khud_rouletteoffset], FRACUNIT*3/4); + if (abs(player->karthud[khud_rouletteoffset]) < (FRACUNIT >> 1)) + { + // Snap to 0, since the change is getting very small. + player->karthud[khud_rouletteoffset] = 0; + } + else + { + // Lerp to 0. + player->karthud[khud_rouletteoffset] = FixedMul(player->karthud[khud_rouletteoffset], FRACUNIT*3/4); + } } if (!(gametyperules & GTR_SPHERES)) From 8d43acccb7ac9fb5b7038b2993f4cf9920a4ed67 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 13 Dec 2022 18:03:44 -0500 Subject: [PATCH 19/24] Use predetermined roulette speed for Time Attack --- src/k_roulette.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index b136113b6..07633a7e0 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -70,6 +70,7 @@ #define ROULETTE_SPEED_SLOWEST (20) #define ROULETTE_SPEED_FASTEST (2) #define ROULETTE_SPEED_DIST (224*DISTVAR) +#define ROULETTE_SPEED_TIMEATTACK (9) static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { @@ -797,6 +798,10 @@ static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *con size_t i; + // Make them select their item after a little while. + // One of the few instances of bot RNG, would be nice to remove it. + player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); + for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] == false || players[i].spectator == true) @@ -812,6 +817,13 @@ static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *con } } + if (modeattacking || playing <= 1) + { + // Time Attack rules; use a consistent speed. + roulette->tics = roulette->speed = ROULETTE_SPEED_TIMEATTACK; + return; + } + ourDist = K_ScaleItemDistance(player->distancetofinish, playing); firstDist = K_ScaleItemDistance(firstDist, playing); @@ -834,15 +846,18 @@ static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *con { // Don't impact as much at the start. // This makes it so that everyone gets to enjoy the lowest speed at the start. - fixed_t lerp = FRACUNIT - FixedDiv(max(0, leveltime - starttime), 20*TICRATE); - total += FixedMul(lerp, FRACUNIT - total); + if (leveltime < starttime) + { + total = FRACUNIT; + } + else + { + const fixed_t lerp = FixedDiv(leveltime - starttime, 20*TICRATE); + total = FRACUNIT + FixedMul(lerp, total - FRACUNIT); + } } roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); - - // Make them select their item after a little while. - // One of the few instances of bot RNG, would be nice to remove it. - player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); } void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) From f7e53ec81128a16fe0b15652cd4d07c4885cb7cd Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 15 Dec 2022 01:23:23 -0500 Subject: [PATCH 20/24] Cleanup lots of the older roulette code - Most of it now requires a reference to itemroulette_t, rather than copying all of the variables over. - Increased the effect of Frantic Items, as the old scaling was made extremely low because item distance was exponential back then. - A lot of variables are pre-calculated in itemroulette_t instead of redoing the calculations every time (player count, first place's dist, etc) - Make most of these functions only accessible by k_roulette.c - Even when single items get forced into your roulette, the Super Ring Debt effect can happen - Make the game support setting single items from other gametypes (Pogo Spring-only races work now) - Fix some item distances not accounting for scale - Prevent multiple of certain items from rolling at once; Shrinks (not a big deal) and SPBs (OH GOD NO) --- src/d_player.h | 7 +- src/k_hud.c | 7 + src/k_kart.c | 12 +- src/k_roulette.c | 471 ++++++++++++++++++++++++----------------------- src/k_roulette.h | 10 +- src/p_saveg.c | 2 + 6 files changed, 266 insertions(+), 243 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 1a447d8de..81a80d641 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -334,7 +334,7 @@ struct skybox_t { // Doing this the right way is causing problems. // so FINE, it's a static length now. -#define ITEM_LIST_SIZE (NUMKARTRESULTS << 2) +#define ITEM_LIST_SIZE (NUMKARTRESULTS << 3) struct itemroulette_t { @@ -350,6 +350,11 @@ struct itemroulette_t #endif UINT8 useOdds; + UINT8 playing, exiting; + UINT32 dist, baseDist; + UINT32 firstDist, secondDist; + UINT32 secondToFirst; + size_t index; UINT8 sound; diff --git a/src/k_hud.c b/src/k_hud.c index 30c41e227..32904beaa 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4589,6 +4589,13 @@ static void K_drawDistributionDebugger(void) V_DrawString((x >> FRACBITS) + 20, 2, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); V_DrawString((x >> FRACBITS) + 20, 10, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); + V_DrawString((x >> FRACBITS) + 20, 22, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); + V_DrawString((x >> FRACBITS) + 20, 30, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); + + V_DrawString((x >> FRACBITS) + 20, 42, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); + V_DrawString((x >> FRACBITS) + 20, 50, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); + V_DrawString((x >> FRACBITS) + 20, 58, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); + #ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); #endif diff --git a/src/k_kart.c b/src/k_kart.c index 235f838f8..b512a64c3 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6053,6 +6053,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (type == 0) { + itemroulette_t rouletteData = {0}; UINT8 useodds = 0; INT32 spawnchance[NUMKARTRESULTS]; INT32 totalspawnchance = 0; @@ -6062,12 +6063,12 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 useodds = amount; + K_StartItemRoulette(stplyr, &rouletteData); + for (i = 1; i < NUMKARTRESULTS; i++) { - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, i, - UINT32_MAX, - false, false) + spawnchance[i] = ( + totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i) ); } @@ -10498,8 +10499,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (spbplace == -1 || player->position != spbplace) player->pflags &= ~PF_RINGLOCK; // reset ring lock - if (player->itemtype == KITEM_SPB - || player->itemtype == KITEM_SHRINK) + if (K_ItemSingularity(player->itemtype) == true) { K_SetItemCooldown(player->itemtype, 20*TICRATE); } diff --git a/src/k_roulette.c b/src/k_roulette.c index 07633a7e0..b77659be6 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -59,17 +59,19 @@ #define SPBSTARTDIST (8*DISTVAR) // Distance when SPB is forced onto the next person who rolls an item -#define SPBFORCEDIST (14*DISTVAR) +#define SPBFORCEDIST (16*DISTVAR) // Distance when the game stops giving you bananas -#define ENDDIST (18*DISTVAR) +#define ENDDIST (14*DISTVAR) // Consistent seed used for item reels #define ITEM_REEL_SEED (0x22D5FAA8) +#define FRANTIC_ITEM_SCALE (FRACUNIT*6/5) + #define ROULETTE_SPEED_SLOWEST (20) #define ROULETTE_SPEED_FASTEST (2) -#define ROULETTE_SPEED_DIST (224*DISTVAR) +#define ROULETTE_SPEED_DIST (150*DISTVAR) #define ROULETTE_SPEED_TIMEATTACK (9) static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = @@ -176,7 +178,23 @@ boolean K_ItemEnabled(SINT8 item) return cv_items[item - 1].value; } -fixed_t K_ItemOddsScale(UINT8 playerCount) +boolean K_ItemSingularity(kartitems_t item) +{ + switch (item) + { + case KITEM_SPB: + case KITEM_SHRINK: + { + return true; + } + default: + { + return false; + } + } +} + +static fixed_t K_ItemOddsScale(UINT8 playerCount) { const UINT8 basePlayer = 8; // The player count we design most of the game around. fixed_t playerScaling = 0; @@ -205,18 +223,23 @@ fixed_t K_ItemOddsScale(UINT8 playerCount) return playerScaling; } -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) +static UINT32 K_UndoMapScaling(UINT32 distance) { if (mapobjectscale != FRACUNIT) { // Bring back to normal scale. - distance = FixedDiv(distance, mapobjectscale); + return FixedDiv(distance, mapobjectscale); } + return distance; +} + +static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) +{ if (franticitems == true) { // Frantic items pretends everyone's farther apart, for crazier items. - distance = (15 * distance) / 14; + distance = FixedMul(distance, FRANTIC_ITEM_SCALE); } // Items get crazier with the fewer players that you have. @@ -228,7 +251,7 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) return distance; } -UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 pingame) +static UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 numPlayers) { UINT32 pdis = 0; @@ -274,12 +297,13 @@ UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 pingame) } } - pdis = K_ScaleItemDistance(pdis, pingame); + pdis = K_UndoMapScaling(pdis); + pdis = K_ScaleItemDistance(pdis, numPlayers); if (player->bot && player->botvars.rival) { // Rival has better odds :) - pdis = (15 * pdis) / 14; + pdis = FixedMul(pdis, FRANTIC_ITEM_SCALE); } return pdis; @@ -292,30 +316,33 @@ UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 pingame) \return void */ -INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, boolean rival) +INT32 K_KartGetItemOdds(player_t *const player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) { - fixed_t newOdds; - INT32 i; + boolean bot = false; + boolean rival = false; + UINT8 position = 0; - UINT8 pingame = 0, pexiting = 0; - - player_t *first = NULL; - player_t *second = NULL; - - UINT32 firstDist = UINT32_MAX; - UINT32 secondDist = UINT32_MAX; - UINT32 secondToFirst = 0; - boolean isFirst = false; + INT32 shieldType = KSHIELD_NONE; boolean powerItem = false; boolean cooldownOnStart = false; boolean notNearEnd = false; - INT32 shieldType = KSHIELD_NONE; + fixed_t newOdds = 0; + size_t i, j; + + I_Assert(roulette != NULL); I_Assert(item > KITEM_NONE); // too many off by one scenarioes. I_Assert(item < NUMKARTRESULTS); + if (player != NULL) + { + bot = player->bot; + rival = (bot == true && player->botvars.rival == true); + position = player->position; + } + if (K_ItemEnabled(item) == false) { return 0; @@ -345,6 +372,58 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool */ (void)bot; + shieldType = K_GetShieldFromItem(item); + switch (shieldType) + { + case KSHIELD_NONE: + /* Marble Garden Top is not REALLY + a Sonic 3 shield */ + case KSHIELD_TOP: + { + break; + } + + default: + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + if (shieldType == K_GetShieldFromItem(players[i].itemtype)) + { + // Don't allow more than one of each shield type at a time + return 0; + } + } + } + } + + if (K_ItemSingularity(item) == true) + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + if (players[i].itemRoulette.active == true) + { + for (j = 0; j < players[i].itemRoulette.itemListLen; j++) + { + if (players[i].itemRoulette.itemList[j] == item) + { + // Don't add if someone is already rolling for it. + return 0; + } + } + } + } + } + if (gametype == GT_BATTLE) { I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table @@ -358,73 +437,6 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool newOdds <<= FRACBITS; - shieldType = K_GetShieldFromItem(item); - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - { - continue; - } - - if (!(gametyperules & GTR_BUMPERS) || players[i].bumpers) - { - pingame++; - } - - if (players[i].exiting) - { - pexiting++; - } - - switch (shieldType) - { - case KSHIELD_NONE: - /* Marble Garden Top is not REALLY - a Sonic 3 shield */ - case KSHIELD_TOP: - { - break; - } - - default: - { - if (shieldType == K_GetShieldFromItem(players[i].itemtype)) - { - // Don't allow more than one of each shield type at a time - return 0; - } - } - } - - if (players[i].position == 1) - { - first = &players[i]; - } - - if (players[i].position == 2) - { - second = &players[i]; - } - } - - if (first != NULL) // calculate 2nd's distance from 1st, for SPB - { - firstDist = first->distancetofinish; - isFirst = (ourDist <= firstDist); - } - - if (second != NULL) - { - secondDist = second->distancetofinish; - } - - if (first != NULL && second != NULL) - { - secondToFirst = secondDist - firstDist; - secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); // Reversed scaling, so 16P is like 1v1, and 1v1 is like 16P - } - switch (item) { case KITEM_BANANA: @@ -440,7 +452,6 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool case KITEM_LANDMINE: case KITEM_DROPTARGET: case KITEM_BALLHOG: - case KITEM_HYUDORO: case KRITEM_TRIPLESNEAKER: case KRITEM_TRIPLEORBINAUT: case KRITEM_QUADORBINAUT: @@ -450,6 +461,7 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool break; } + case KITEM_HYUDORO: case KRITEM_TRIPLEBANANA: { powerItem = true; @@ -473,17 +485,23 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool cooldownOnStart = true; notNearEnd = true; - if (firstDist < ENDDIST*2 // No SPB when 1st is almost done - || isFirst == true) // No SPB for 1st ever + if ((gametyperules & GTR_CIRCUIT) == 0) { - newOdds = 0; + // Needs to be a race. + return 0; + } + + if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done + || position == 1) // No SPB for 1st ever + { + return 0; } else { - const UINT32 dist = max(0, ((signed)secondToFirst) - SPBSTARTDIST); + const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST); const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const UINT8 maxOdds = 20; - fixed_t multiplier = (dist * FRACUNIT) / distRange; + const fixed_t maxOdds = 20 << FRACBITS; + fixed_t multiplier = FixedDiv(dist, distRange); if (multiplier < 0) { @@ -495,34 +513,40 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool multiplier = FRACUNIT; } - newOdds = FixedMul(maxOdds << FRACBITS, multiplier); + newOdds = FixedMul(maxOdds, multiplier); } break; } case KITEM_SHRINK: + { cooldownOnStart = true; powerItem = true; notNearEnd = true; - if (pingame-1 <= pexiting) + if (roulette->playing - 1 <= roulette->exiting) { - newOdds = 0; + return 0; } break; + } case KITEM_LIGHTNINGSHIELD: + { cooldownOnStart = true; powerItem = true; if (spbplace != -1) { - newOdds = 0; + return 0; } break; + } default: + { break; + } } if (newOdds == 0) @@ -536,7 +560,7 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) newOdds = 0; } - else if ((notNearEnd == true) && (ourDist < ENDDIST)) + else if ((notNearEnd == true) && (roulette->baseDist < ENDDIST)) { // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) newOdds = 0; @@ -556,16 +580,14 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, bool newOdds *= 2; } - newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(pingame)); + newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing)); } newOdds = FixedInt(FixedRound(newOdds)); return newOdds; } -//{ SRB2kart Roulette Code - Distance Based, yes waypoints - -UINT8 K_FindUseodds(player_t *const player, UINT32 playerDist) +static UINT8 K_FindUseodds(player_t *const player, itemroulette_t *const roulette) { UINT8 i; UINT8 useOdds = 0; @@ -586,11 +608,7 @@ UINT8 K_FindUseodds(player_t *const player, UINT32 playerDist) for (j = 1; j < NUMKARTRESULTS; j++) { - if (K_KartGetItemOdds( - i, j, - player->distancetofinish, - player->bot, (player->bot && player->botvars.rival) - ) > 0) + if (K_KartGetItemOdds(player, roulette, i, j) > 0) { break; } @@ -636,7 +654,7 @@ UINT8 K_FindUseodds(player_t *const player, UINT32 playerDist) dist = FixedMul(DISTVAR << FRACBITS, pos) >> FRACBITS; index = FixedInt(FixedRound(pos)); - if (playerDist <= (unsigned)dist) + if (roulette->dist <= (unsigned)dist) { useOdds = distTable[index]; break; @@ -649,14 +667,8 @@ UINT8 K_FindUseodds(player_t *const player, UINT32 playerDist) return useOdds; } -boolean K_ForcedSPB(player_t *const player) +static boolean K_ForcedSPB(player_t *const player, itemroulette_t *const roulette) { - player_t *first = NULL; - player_t *second = NULL; - UINT32 secondToFirst = UINT32_MAX; - UINT8 pingame = 0; - UINT8 i; - if (K_ItemEnabled(KITEM_SPB) == false) { return false; @@ -682,49 +694,20 @@ boolean K_ForcedSPB(player_t *const player) return false; } - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - { - continue; - } - - if (players[i].exiting) - { - return false; - } - - pingame++; - - if (players[i].position == 1) - { - first = &players[i]; - } - - if (players[i].position == 2) - { - second = &players[i]; - } - } - #if 0 - if (pingame <= 2) + if (roulette->playing <= 2) { return false; } #endif - if (first != NULL && second != NULL) - { - secondToFirst = second->distancetofinish - first->distancetofinish; - secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); - } - - return (secondToFirst >= SPBFORCEDIST); + return (roulette->secondToFirst >= SPBFORCEDIST); } static void K_InitRoulette(itemroulette_t *const roulette) { + size_t i; + #ifndef ITEM_LIST_SIZE if (roulette->itemList == NULL) { @@ -744,13 +727,50 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->itemListLen = 0; roulette->index = 0; + roulette->useOdds = UINT8_MAX; + roulette->baseDist = roulette->dist = 0; + roulette->playing = roulette->exiting = 0; + roulette->firstDist = roulette->secondDist = UINT32_MAX; + roulette->secondToFirst = 0; roulette->elapsed = 0; roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST; // Some default speed roulette->active = true; roulette->eggman = false; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + roulette->playing++; + + if (players[i].exiting) + { + roulette->exiting++; + } + + if (players[i].position == 1) + { + roulette->firstDist = K_UndoMapScaling(players[i].distancetofinish); + } + + if (players[i].position == 2) + { + roulette->secondDist = K_UndoMapScaling(players[i].distancetofinish); + } + } + + // Calculate 2nd's distance from 1st, for SPB + if (roulette->firstDist != UINT32_MAX && roulette->secondDist != UINT32_MAX) + { + roulette->secondToFirst = roulette->secondDist - roulette->firstDist; + roulette->secondToFirst = K_ScaleItemDistance(roulette->secondToFirst, 16 - roulette->playing); // Reversed scaling + } } static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t item) @@ -785,58 +805,47 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t roulette->itemListLen++; } +static void K_AddItemToReel(player_t *const player, itemroulette_t *const roulette, kartitems_t item) +{ + K_PushToRouletteItemList(roulette, item); + + // If we're in ring debt, pad out the reel with + // a BUNCH of Super Rings. + if (K_ItemEnabled(KITEM_SUPERRING) == true + && player->rings <= 0 + && (gametyperules & GTR_SPHERES) == 0) + { + K_PushToRouletteItemList(roulette, KITEM_SUPERRING); + } +} + static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *const roulette) { fixed_t frontRun = 0; fixed_t progress = 0; fixed_t total = 0; - UINT8 playing = 0; - - UINT32 firstDist = UINT32_MAX; - UINT32 ourDist = UINT32_MAX; - - size_t i; - // Make them select their item after a little while. // One of the few instances of bot RNG, would be nice to remove it. player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] == false || players[i].spectator == true) - { - continue; - } - - playing++; - - if (players[i].position == 1) - { - firstDist = players[i].distancetofinish; - } - } - - if (modeattacking || playing <= 1) + if (modeattacking || roulette->playing <= 1) { // Time Attack rules; use a consistent speed. roulette->tics = roulette->speed = ROULETTE_SPEED_TIMEATTACK; return; } - ourDist = K_ScaleItemDistance(player->distancetofinish, playing); - firstDist = K_ScaleItemDistance(firstDist, playing); - - if (ourDist > ENDDIST) + if (roulette->baseDist > ENDDIST) { // Being farther in the course makes your roulette faster. - progress = min(FRACUNIT, FixedDiv(ourDist - ENDDIST, ROULETTE_SPEED_DIST)); + progress = min(FRACUNIT, FixedDiv(roulette->baseDist - ENDDIST, ROULETTE_SPEED_DIST)); } - if (ourDist > firstDist) + if (roulette->baseDist > roulette->firstDist) { // Frontrunning makes your roulette faster. - frontRun = min(FRACUNIT, FixedDiv(ourDist - firstDist, ENDDIST)); + frontRun = min(FRACUNIT, FixedDiv(roulette->baseDist - roulette->firstDist, ENDDIST)); } // Combine our two factors together. @@ -862,16 +871,19 @@ static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *con void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) { - UINT8 playing = 0; - UINT32 playerDist = UINT32_MAX; - UINT32 spawnChance[NUMKARTRESULTS] = {0}; UINT32 totalSpawnChance = 0; size_t rngRoll = 0; + UINT8 numItems = 0; + kartitems_t singleItem = KITEM_SAD; + size_t i; K_InitRoulette(roulette); + + roulette->baseDist = K_UndoMapScaling(player->distancetofinish); + K_CalculateRouletteSpeed(player, roulette); // SPECIAL CASE No. 1: @@ -884,16 +896,6 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) // SPECIAL CASE No. 2: // Use a special, pre-determined item reel for Time Attack / Free Play - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] == false || players[i].spectator == true) - { - continue; - } - - playing++; - } - if (bossinfo.boss == true) { for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) @@ -903,7 +905,7 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) return; } - else if (modeattacking || playing <= 1) + else if (modeattacking || roulette->playing <= 1) { switch (gametype) { @@ -931,36 +933,58 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) // SPECIAL CASE No. 3: // Only give the SPB if conditions are right - if (K_ForcedSPB(player) == true) + if (K_ForcedSPB(player, roulette) == true) { - K_PushToRouletteItemList(roulette, KITEM_SPB); + K_AddItemToReel(player, roulette, KITEM_SPB); return; } - playerDist = K_GetItemRouletteDistance(player, playing); - - roulette->useOdds = K_FindUseodds(player, playerDist); - - for (i = 1; i < NUMKARTRESULTS; i++) - { - INT32 thisItemsOdds = K_KartGetItemOdds( - roulette->useOdds, i, - player->distancetofinish, - player->bot, (player->bot && player->botvars.rival) - ); - - spawnChance[i] = (totalSpawnChance += thisItemsOdds); - } - // SPECIAL CASE No. 4: - // All items are off, so give a placeholder item - if (totalSpawnChance == 0) + // If only one item is enabled, always use it + for (i = 1; i < NUMKARTRESULTS; i++) { - K_PushToRouletteItemList(roulette, KITEM_SAD); + if (K_ItemEnabled(i) == true) + { + numItems++; + if (numItems > 1) + { + break; + } + + singleItem = i; + } + } + + if (numItems < 2) + { + // singleItem = KITEM_SAD by default, + // so it will be used when all items are turned off. + K_AddItemToReel(player, roulette, singleItem); return; } - // We always want the same result when making the same item reel. + // Special cases are all handled, we can now + // actually calculate actual item reels. + roulette->dist = K_GetItemRouletteDistance(player, roulette->playing); + roulette->useOdds = K_FindUseodds(player, roulette); + + for (i = 1; i < NUMKARTRESULTS; i++) + { + spawnChance[i] = ( + totalSpawnChance += K_KartGetItemOdds(player, roulette, roulette->useOdds, i) + ); + } + + if (totalSpawnChance == 0) + { + // This shouldn't happen, but if it does, early exit. + // Maybe can happen if you enable multiple items for + // another gametype, so we give the singleItem as a fallback. + K_AddItemToReel(player, roulette, singleItem); + return; + } + + // Create the same item reel given the same inputs. P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); while (totalSpawnChance > 0) @@ -971,16 +995,7 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) continue; } - K_PushToRouletteItemList(roulette, i); - - // If we're in ring debt, pad out the reel with - // a BUNCH of Super Rings. - if (K_ItemEnabled(KITEM_SUPERRING) == true - && player->rings <= 0 - && (gametyperules & GTR_SPHERES) == 0) - { - K_PushToRouletteItemList(roulette, KITEM_SUPERRING); - } + K_AddItemToReel(player, roulette, i); for (; i < NUMKARTRESULTS; i++) { @@ -1011,7 +1026,7 @@ void K_StartEggmanRoulette(player_t *const player) */ static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) { - if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) + if (K_ItemSingularity(getitem) == true) { K_SetItemCooldown(getitem, 20*TICRATE); } diff --git a/src/k_roulette.h b/src/k_roulette.h index 81e12aae3..3d52123f9 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -17,15 +17,9 @@ #include "d_player.h" boolean K_ItemEnabled(SINT8 item); +boolean K_ItemSingularity(kartitems_t item); -fixed_t K_ItemOddsScale(UINT8 playerCount); -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers); -UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 pingame); - -INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, boolean bot, boolean rival); -UINT8 K_FindUseodds(player_t *const player, UINT32 playerDist); - -boolean K_ForcedSPB(player_t *const player); +INT32 K_KartGetItemOdds(player_t *const player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette); void K_StartEggmanRoulette(player_t *const player); diff --git a/src/p_saveg.c b/src/p_saveg.c index 46aa64147..3ce18aa5f 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -444,6 +444,7 @@ static void P_NetArchivePlayers(void) #endif WRITEUINT8(save_p, players[i].itemRoulette.useOdds); + WRITEUINT32(save_p, players[i].itemRoulette.dist); WRITEUINT32(save_p, players[i].itemRoulette.index); WRITEUINT8(save_p, players[i].itemRoulette.sound); WRITEUINT32(save_p, players[i].itemRoulette.speed); @@ -798,6 +799,7 @@ static void P_NetUnArchivePlayers(void) #endif players[i].itemRoulette.useOdds = READUINT8(save_p); + players[i].itemRoulette.dist = READUINT32(save_p); players[i].itemRoulette.index = (size_t)READUINT32(save_p); players[i].itemRoulette.sound = READUINT8(save_p); players[i].itemRoulette.speed = (tic_t)READUINT32(save_p); From ffe9d5cd1d20d7dee3f89c9270554d0c34c6b254 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 15 Dec 2022 01:41:34 -0500 Subject: [PATCH 21/24] Properly support small splitscreen itembox --- src/k_hud.c | 32 +++++++++++++++++++------------- src/k_roulette.h | 1 + 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 32904beaa..49bb6aa07 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1030,13 +1030,14 @@ static void K_drawKartItem(void) const INT32 numberdisplaymin = ((!offset && stplyr->itemtype == KITEM_ORBINAUT) ? 5 : 2); INT32 itembar = 0; INT32 maxl = 0; // itembar's normal highest value - const INT32 barlength = (r_splitscreen > 1 ? 12 : 26); + const INT32 barlength = (offset ? 12 : 26); UINT16 localcolor[3] = { stplyr->skincolor }; SINT8 colormode[3] = { TC_RAINBOW }; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff fixed_t rouletteOffset = 0; - const INT32 rouletteBox = 36; + fixed_t rouletteSpace = ROULETTE_SPACING; + vector2_t rouletteCrop = {7, 7}; INT32 i; if (stplyr->itemRoulette.itemListLen > 0) @@ -1189,13 +1190,7 @@ static void K_drawKartItem(void) } // pain and suffering defined below - if (r_splitscreen < 2) // don't change shit for THIS splitscreen. - { - fx = ITEM_X; - fy = ITEM_Y; - fflags = V_SNAPTOTOP|V_SNAPTOLEFT|V_SPLITSCREEN; - } - else // now we're having a fun game. + if (offset) { if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... { @@ -1210,24 +1205,35 @@ static void K_drawKartItem(void) fflags = V_SNAPTORIGHT|V_SNAPTOTOP|V_SPLITSCREEN; flipamount = true; } + + rouletteSpace = ROULETTE_SPACING_SPLITSCREEN; + rouletteOffset = FixedMul(rouletteOffset, FixedDiv(ROULETTE_SPACING_SPLITSCREEN, ROULETTE_SPACING)); + rouletteCrop.x = 16; + rouletteCrop.y = 15; + } + else + { + fx = ITEM_X; + fy = ITEM_Y; + fflags = V_SNAPTOTOP|V_SNAPTOLEFT|V_SPLITSCREEN; } V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|fflags, localbg); // Need to draw these in a particular order, for sorting. V_SetClipRect( - (fx + 7) << FRACBITS, (fy + 7) << FRACBITS, - rouletteBox << FRACBITS, rouletteBox << FRACBITS, + (fx + rouletteCrop.x) << FRACBITS, (fy + rouletteCrop.y) << FRACBITS, + rouletteSpace, rouletteSpace, V_SLIDEIN|fflags ); V_DrawFixedPatch( - fx< Date: Thu, 15 Dec 2022 01:44:09 -0500 Subject: [PATCH 22/24] Remove item roulette between maps --- src/g_game.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 91d309638..ab4b6bc9c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2322,8 +2322,13 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE)); // SRB2kart + memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette)); + memcpy(&respawn, &players[player].respawn, sizeof (respawn)); + if (betweenmaps || leveltime < introtime) { + itemRoulette.active = false; + itemtype = 0; itemamount = 0; growshrinktimer = 0; @@ -2400,9 +2405,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) P_SetTarget(&players[player].follower, NULL); } - memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette)); - memcpy(&respawn, &players[player].respawn, sizeof (respawn)); - p = &players[player]; memset(p, 0, sizeof (*p)); From 6554c8bfb1f7e2746a867713b20cbe9d106ddf62 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 15 Dec 2022 02:13:46 -0500 Subject: [PATCH 23/24] Improve scope handling of the roulette All of the player_t references are now full-const instead of const pointer after a certain point. This is because I've made two mistakes so far of modifying the player with this, when it's supposed to be safe to call for HUD as well. Also uses this split to add a more efficient way to prevent multi-Shrink/SPB. Also handles NULL player better, for the sake of Battle's K_CreatePaperItem. --- src/k_hud.c | 2 +- src/k_kart.c | 2 +- src/k_roulette.c | 91 +++++++++++++++++++++++++++--------------------- src/k_roulette.h | 5 +-- src/p_enemy.c | 2 +- src/p_inter.c | 2 +- 6 files changed, 59 insertions(+), 45 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 49bb6aa07..7c1d94e42 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4560,7 +4560,7 @@ static void K_drawDistributionDebugger(void) return; } - K_StartItemRoulette(stplyr, &rouletteData); + K_FillItemRouletteData(stplyr, &rouletteData); for (i = 0; i < rouletteData.itemListLen; i++) { diff --git a/src/k_kart.c b/src/k_kart.c index b512a64c3..3bce71c54 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6063,7 +6063,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 useodds = amount; - K_StartItemRoulette(stplyr, &rouletteData); + K_FillItemRouletteData(NULL, &rouletteData); for (i = 1; i < NUMKARTRESULTS; i++) { diff --git a/src/k_roulette.c b/src/k_roulette.c index b77659be6..6d59f1ec3 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -251,10 +251,15 @@ static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) return distance; } -static UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 numPlayers) +static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers) { UINT32 pdis = 0; + if (player == NULL) + { + return 0; + } + #if 0 if (specialStage.active == true) { @@ -316,7 +321,7 @@ static UINT32 K_GetItemRouletteDistance(player_t *const player, UINT8 numPlayers \return void */ -INT32 K_KartGetItemOdds(player_t *const player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) +INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) { boolean bot = false; boolean rival = false; @@ -329,7 +334,7 @@ INT32 K_KartGetItemOdds(player_t *const player, itemroulette_t *const roulette, boolean notNearEnd = false; fixed_t newOdds = 0; - size_t i, j; + size_t i; I_Assert(roulette != NULL); @@ -401,29 +406,6 @@ INT32 K_KartGetItemOdds(player_t *const player, itemroulette_t *const roulette, } } - if (K_ItemSingularity(item) == true) - { - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] == false || players[i].spectator == true) - { - continue; - } - - if (players[i].itemRoulette.active == true) - { - for (j = 0; j < players[i].itemRoulette.itemListLen; j++) - { - if (players[i].itemRoulette.itemList[j] == item) - { - // Don't add if someone is already rolling for it. - return 0; - } - } - } - } - } - if (gametype == GT_BATTLE) { I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table @@ -587,7 +569,7 @@ INT32 K_KartGetItemOdds(player_t *const player, itemroulette_t *const roulette, return newOdds; } -static UINT8 K_FindUseodds(player_t *const player, itemroulette_t *const roulette) +static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulette) { UINT8 i; UINT8 useOdds = 0; @@ -667,7 +649,7 @@ static UINT8 K_FindUseodds(player_t *const player, itemroulette_t *const roulett return useOdds; } -static boolean K_ForcedSPB(player_t *const player, itemroulette_t *const roulette) +static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette) { if (K_ItemEnabled(KITEM_SPB) == false) { @@ -679,6 +661,11 @@ static boolean K_ForcedSPB(player_t *const player, itemroulette_t *const roulett return false; } + if (player == NULL) + { + return false; + } + if (player->position <= 1) { return false; @@ -805,10 +792,15 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t roulette->itemListLen++; } -static void K_AddItemToReel(player_t *const player, itemroulette_t *const roulette, kartitems_t item) +static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item) { K_PushToRouletteItemList(roulette, item); + if (player == NULL) + { + return; + } + // If we're in ring debt, pad out the reel with // a BUNCH of Super Rings. if (K_ItemEnabled(KITEM_SUPERRING) == true @@ -819,16 +811,12 @@ static void K_AddItemToReel(player_t *const player, itemroulette_t *const roulet } } -static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *const roulette) +static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) { fixed_t frontRun = 0; fixed_t progress = 0; fixed_t total = 0; - // Make them select their item after a little while. - // One of the few instances of bot RNG, would be nice to remove it. - player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); - if (modeattacking || roulette->playing <= 1) { // Time Attack rules; use a consistent speed. @@ -869,7 +857,7 @@ static void K_CalculateRouletteSpeed(player_t *const player, itemroulette_t *con roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); } -void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) +void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette) { UINT32 spawnChance[NUMKARTRESULTS] = {0}; UINT32 totalSpawnChance = 0; @@ -882,9 +870,11 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) K_InitRoulette(roulette); - roulette->baseDist = K_UndoMapScaling(player->distancetofinish); - - K_CalculateRouletteSpeed(player, roulette); + if (player != NULL) + { + roulette->baseDist = K_UndoMapScaling(player->distancetofinish); + K_CalculateRouletteSpeed(roulette); + } // SPECIAL CASE No. 1: // Give only the debug item if specified @@ -1010,10 +1000,33 @@ void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette) } } +void K_StartItemRoulette(player_t *const player) +{ + itemroulette_t *const roulette = &player->itemRoulette; + size_t i; + + K_FillItemRouletteData(player, roulette); + + // Make the bots select their item after a little while. + // One of the few instances of bot RNG, would be nice to remove it. + player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); + + // Prevent further duplicates of items that + // are intended to only have one out at a time. + for (i = 0; i < roulette->itemListLen; i++) + { + kartitems_t item = roulette->itemList[i]; + if (K_ItemSingularity(item) == true) + { + K_SetItemCooldown(item, TICRATE<<4); + } + } +} + void K_StartEggmanRoulette(player_t *const player) { itemroulette_t *const roulette = &player->itemRoulette; - K_StartItemRoulette(player, roulette); + K_StartItemRoulette(player); roulette->eggman = true; } diff --git a/src/k_roulette.h b/src/k_roulette.h index b1664643c..fbc6d4a4b 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -19,9 +19,10 @@ boolean K_ItemEnabled(SINT8 item); boolean K_ItemSingularity(kartitems_t item); -INT32 K_KartGetItemOdds(player_t *const player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); +INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); +void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette); -void K_StartItemRoulette(player_t *const player, itemroulette_t *const roulette); +void K_StartItemRoulette(player_t *const player); void K_StartEggmanRoulette(player_t *const player); #define ROULETTE_SPACING (36 << FRACBITS) diff --git a/src/p_enemy.c b/src/p_enemy.c index 636eb500a..3c45a1d2e 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -13048,7 +13048,7 @@ void A_ItemPop(mobj_t *actor) } else if (locvar1 == 0) { - K_StartItemRoulette(actor->target->player, &actor->target->player->itemRoulette); + K_StartItemRoulette(actor->target->player); } // Here at mapload in battle? diff --git a/src/p_inter.c b/src/p_inter.c index 2c05f5732..6400b9d35 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -412,7 +412,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->fuse || !P_CanPickupItem(player, 1) || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)) return; - K_StartItemRoulette(player, &player->itemRoulette); + K_StartItemRoulette(player); // Karma fireworks for (i = 0; i < 5; i++) From 05413eeb2d93d5cb0d0725016ab95d8158fd4cd8 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 15 Dec 2022 16:19:06 -0500 Subject: [PATCH 24/24] Add proper comments for each function (I think this is the only code style I want to actively enforce :p) --- src/k_roulette.c | 211 ++++++++++++++++++++++++++++++++++++++++++----- src/k_roulette.h | 153 ++++++++++++++++++++++++++++++++-- 2 files changed, 336 insertions(+), 28 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 6d59f1ec3..ec15f4e9b 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -160,7 +160,12 @@ static kartitems_t K_KartItemReelBoss[] = KITEM_NONE }; -boolean K_ItemEnabled(SINT8 item) +/*-------------------------------------------------- + boolean K_ItemEnabled(kartitems_t item) + + See header file for description. +--------------------------------------------------*/ +boolean K_ItemEnabled(kartitems_t item) { if (item < 1 || item >= NUMKARTRESULTS) { @@ -178,6 +183,11 @@ boolean K_ItemEnabled(SINT8 item) return cv_items[item - 1].value; } +/*-------------------------------------------------- + boolean K_ItemSingularity(kartitems_t item) + + See header file for description. +--------------------------------------------------*/ boolean K_ItemSingularity(kartitems_t item) { switch (item) @@ -194,6 +204,19 @@ boolean K_ItemSingularity(kartitems_t item) } } +/*-------------------------------------------------- + static fixed_t K_ItemOddsScale(UINT8 playerCount) + + A multiplier for odds and distances to scale + them with the player count. + + Input Arguments:- + playerCount - Number of players in the game. + + Return:- + Fixed point number, to multiply odds or + distances by. +--------------------------------------------------*/ static fixed_t K_ItemOddsScale(UINT8 playerCount) { const UINT8 basePlayer = 8; // The player count we design most of the game around. @@ -223,6 +246,18 @@ static fixed_t K_ItemOddsScale(UINT8 playerCount) return playerScaling; } +/*-------------------------------------------------- + static UINT32 K_UndoMapScaling(UINT32 distance) + + Takes a raw map distance and adjusts it to + be in x1 scale. + + Input Arguments:- + distance - Original distance. + + Return:- + Distance unscaled by mapobjectscale. +--------------------------------------------------*/ static UINT32 K_UndoMapScaling(UINT32 distance) { if (mapobjectscale != FRACUNIT) @@ -234,6 +269,19 @@ static UINT32 K_UndoMapScaling(UINT32 distance) return distance; } +/*-------------------------------------------------- + static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) + + Adjust item distance for lobby-size scaling + as well as Frantic Items. + + Input Arguments:- + distance - Original distance. + numPlayers - Number of players in the game. + + Return:- + New distance after scaling. +--------------------------------------------------*/ static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) { if (franticitems == true) @@ -251,6 +299,19 @@ static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) return distance; } +/*-------------------------------------------------- + static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers) + + Gets a player's distance used for the item + roulette, including all scaling factors. + + Input Arguments:- + player - The player to get the distance of. + numPlayers - Number of players in the game. + + Return:- + The player's finalized item distance. +--------------------------------------------------*/ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers) { UINT32 pdis = 0; @@ -314,13 +375,11 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers return pdis; } -/** \brief Item Roulette for Kart - - \param player player object passed from P_KartPlayerThink - - \return void -*/ +/*-------------------------------------------------- + INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) + See header file for description. +--------------------------------------------------*/ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) { boolean bot = false; @@ -569,6 +628,21 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, return newOdds; } +/*-------------------------------------------------- + static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulette) + + Gets which item bracket the player is in. + This can be adjusted depending on which + items being turned off. + + Input Arguments:- + player - The player the roulette is for. + roulette - The item roulette data. + + Return:- + The item bracket the player is in, as an + index to the array. +--------------------------------------------------*/ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulette) { UINT8 i; @@ -649,6 +723,20 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett return useOdds; } +/*-------------------------------------------------- + static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette) + + Determines special conditions where we want + to forcefully give the player an SPB. + + Input Arguments:- + player - The player the roulette is for. + roulette - The item roulette data. + + Return:- + true if we want to give the player a forced SPB, + otherwise false. +--------------------------------------------------*/ static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette) { if (K_ItemEnabled(KITEM_SPB) == false) @@ -691,6 +779,17 @@ static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulett return (roulette->secondToFirst >= SPBFORCEDIST); } +/*-------------------------------------------------- + static void K_InitRoulette(itemroulette_t *const roulette) + + Initializes the data for a new item roulette. + + Input Arguments:- + roulette - The item roulette data to initialize. + + Return:- + N/A +--------------------------------------------------*/ static void K_InitRoulette(itemroulette_t *const roulette) { size_t i; @@ -760,6 +859,19 @@ static void K_InitRoulette(itemroulette_t *const roulette) } } +/*-------------------------------------------------- + static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t item) + + Pushes a new item to the end of the item + roulette's item list. + + Input Arguments:- + roulette - The item roulette data to modify. + item - The item to push to the list. + + Return:- + N/A +--------------------------------------------------*/ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t item) { #ifdef ITEM_LIST_SIZE @@ -792,6 +904,23 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t roulette->itemListLen++; } +/*-------------------------------------------------- + static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item) + + Adds an item to a player's item reel. Unlike + pushing directly with K_PushToRouletteItemList, + this function handles special behaviors (like + padding with extra Super Rings). + + Input Arguments:- + player - The player to add to the item roulette. + This is valid to be NULL. + roulette - The player's item roulette data. + item - The item to push to the list. + + Return:- + N/A +--------------------------------------------------*/ static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item) { K_PushToRouletteItemList(roulette, item); @@ -811,6 +940,19 @@ static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulet } } +/*-------------------------------------------------- + static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) + + Determines the speed for the item roulette, + adjusted for progress in the race and front + running. + + Input Arguments:- + roulette - The item roulette data to modify. + + Return:- + N/A +--------------------------------------------------*/ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) { fixed_t frontRun = 0; @@ -857,6 +999,11 @@ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); } +/*-------------------------------------------------- + void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette) + + See header file for description. +--------------------------------------------------*/ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette) { UINT32 spawnChance[NUMKARTRESULTS] = {0}; @@ -1000,6 +1147,11 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } } +/*-------------------------------------------------- + void K_StartItemRoulette(player_t *const player) + + See header file for description. +--------------------------------------------------*/ void K_StartItemRoulette(player_t *const player) { itemroulette_t *const roulette = &player->itemRoulette; @@ -1023,6 +1175,11 @@ void K_StartItemRoulette(player_t *const player) } } +/*-------------------------------------------------- + void K_StartEggmanRoulette(player_t *const player) + + See header file for description. +--------------------------------------------------*/ void K_StartEggmanRoulette(player_t *const player) { itemroulette_t *const roulette = &player->itemRoulette; @@ -1030,13 +1187,32 @@ void K_StartEggmanRoulette(player_t *const player) roulette->eggman = true; } -/** \brief Item Roulette for Kart +/*-------------------------------------------------- + fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta) - \param player player - \param getitem what item we're looking for + See header file for description. +--------------------------------------------------*/ +fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta) +{ + const fixed_t curTic = (roulette->tics << FRACBITS) - renderDelta; + const fixed_t midTic = roulette->speed * (FRACUNIT >> 1); - \return void -*/ + return FixedMul(FixedDiv(midTic - curTic, ((roulette->speed + 1) << FRACBITS)), ROULETTE_SPACING); +} + +/*-------------------------------------------------- + static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) + + Initializes a player's item to what was + received from the roulette. + + Input Arguments:- + player - The player receiving the item. + getitem - The item to give to the player. + + Return:- + N/A +--------------------------------------------------*/ static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) { if (K_ItemSingularity(getitem) == true) @@ -1051,14 +1227,11 @@ static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) player->itemamount = K_ItemResultToAmount(getitem); } -fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta) -{ - const fixed_t curTic = (roulette->tics << FRACBITS) - renderDelta; - const fixed_t midTic = roulette->speed * (FRACUNIT >> 1); - - return FixedMul(FixedDiv(midTic - curTic, ((roulette->speed + 1) << FRACBITS)), ROULETTE_SPACING); -} +/*-------------------------------------------------- + void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) + See header file for description. +--------------------------------------------------*/ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) { itemroulette_t *const roulette = &player->itemRoulette; diff --git a/src/k_roulette.h b/src/k_roulette.h index fbc6d4a4b..0cef89f7a 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -16,19 +16,154 @@ #include "doomtype.h" #include "d_player.h" -boolean K_ItemEnabled(SINT8 item); -boolean K_ItemSingularity(kartitems_t item); - -INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); -void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette); - -void K_StartItemRoulette(player_t *const player); -void K_StartEggmanRoulette(player_t *const player); - #define ROULETTE_SPACING (36 << FRACBITS) #define ROULETTE_SPACING_SPLITSCREEN (16 << FRACBITS) + +/*-------------------------------------------------- + boolean K_ItemEnabled(kartitems_t item); + + Determines whenever or not an item should + be enabled. Accounts for situations where + rules should not be able to be changed. + + Input Arguments:- + item - The item to check. + + Return:- + true if the item is enabled, otherwise false. +--------------------------------------------------*/ + +boolean K_ItemEnabled(kartitems_t item); + + +/*-------------------------------------------------- + boolean K_ItemSingularity(kartitems_t item); + + Determines whenever or not this item should + be using special cases to prevent more than + one existing at a time. + + Input Arguments:- + item - The item to check. + + Return:- + true to use the special rules, otherwise false. +--------------------------------------------------*/ + +boolean K_ItemSingularity(kartitems_t item); + + +/*-------------------------------------------------- + INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); + + Gets the frequency an item should show up in + an item bracket, and adjusted for special + factors (such as Frantic Items). + + Input Arguments:- + player - The player we intend to give the item to later. + Can be NULL for generic use. + roulette - The roulette data that we intend to + insert this item into. + pos - The item bracket we are in. + item - The item to give. + + Return:- + The number of items we want to insert + into the roulette. +--------------------------------------------------*/ + +INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); + + +/*-------------------------------------------------- + void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette); + + Fills out the item roulette struct when it is + initially created. This function needs to be + HUD-safe for the item debugger, so the player + cannot be modified at this stage. + + Input Arguments:- + player - The player this roulette data is for. + Can be NULL for generic use. + roulette - The roulette data struct to fill out. + + Return:- + N/A +--------------------------------------------------*/ + +void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette); + + +/*-------------------------------------------------- + void K_StartItemRoulette(player_t *const player); + + Starts the item roulette sequence for a player. + This stage can only be used by gameplay, thus + this handles gameplay modifications as well. + + Input Arguments:- + player - The player to start the item roulette for. + + Return:- + N/A +--------------------------------------------------*/ + +void K_StartItemRoulette(player_t *const player); + + +/*-------------------------------------------------- + void K_StartEggmanRoulette(player_t *const player); + + Starts the Eggman Mark roulette sequence for + a player. Looks identical to a regular item + roulette, but gives you the Eggman explosion + countdown instead when confirming it. + + Input Arguments:- + player - The player to start the Eggman roulette for. + + Return:- + N/A +--------------------------------------------------*/ + +void K_StartEggmanRoulette(player_t *const player); + + +/*-------------------------------------------------- + fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta); + + Gets the Y offset, for use in the roulette HUD. + A separate function since it is used both by the + HUD itself, as well as when confirming an item. + + Input Arguments:- + roulette - The roulette we are drawing for. + renderDelta - Fractional tic delta, when used for HUD. + + Return:- + The Y offset when drawing the item. +--------------------------------------------------*/ + fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta); + +/*-------------------------------------------------- + void K_KartItemRoulette(player_t *const player, ticcmd_t *cmd); + + Handles ticking a player's item roulette, + and player input for stopping it. + + Input Arguments:- + player - The player to run the item roulette for. + cmd - The player's controls. + + Return:- + N/A +--------------------------------------------------*/ + void K_KartItemRoulette(player_t *const player, ticcmd_t *cmd); + #endif // __K_ROULETTE_H__