mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
1531 lines
36 KiB
C
1531 lines
36 KiB
C
// 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 (16*DISTVAR)
|
|
|
|
// Distance when the game stops giving you bananas
|
|
#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 (150*DISTVAR)
|
|
#define ROULETTE_SPEED_TIMEATTACK (9)
|
|
|
|
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, 1, 2, 2, 0, 0 }, // Garden Top
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0 }, // Gachabom
|
|
{ 0, 0, 2, 3, 3, 1, 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
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0 } // Gachabom x3
|
|
};
|
|
|
|
static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] =
|
|
{
|
|
{ 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 }, // Gachabom
|
|
{ 0, 0 }, // Sneaker x2
|
|
{ 0, 1 }, // Sneaker x3
|
|
{ 0, 0 }, // Banana x3
|
|
{ 2, 0 }, // Orbinaut x3
|
|
{ 1, 1 }, // Orbinaut x4
|
|
{ 5, 1 }, // Jawz x2
|
|
{ 0, 0 } // Gachabom x3
|
|
};
|
|
|
|
static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] =
|
|
{
|
|
{ 1, 1, 0, 0 }, // Sneaker
|
|
{ 0, 0, 0, 0 }, // Rocket Sneaker
|
|
{ 0, 0, 0, 0 }, // Invincibility
|
|
{ 0, 0, 0, 0 }, // Banana
|
|
{ 0, 0, 0, 0 }, // Eggman Monitor
|
|
{ 1, 1, 0, 0 }, // Orbinaut
|
|
{ 1, 1, 0, 0 }, // Jawz
|
|
{ 0, 0, 0, 0 }, // Mine
|
|
{ 0, 0, 0, 0 }, // Land Mine
|
|
{ 0, 0, 0, 0 }, // Ballhog
|
|
{ 0, 0, 0, 1 }, // Self-Propelled Bomb
|
|
{ 0, 0, 0, 0 }, // Grow
|
|
{ 0, 0, 0, 0 }, // Shrink
|
|
{ 0, 0, 0, 0 }, // Lightning Shield
|
|
{ 0, 0, 0, 0 }, // Bubble Shield
|
|
{ 0, 0, 0, 0 }, // Flame Shield
|
|
{ 0, 0, 0, 0 }, // Hyudoro
|
|
{ 0, 0, 0, 0 }, // Pogo Spring
|
|
{ 0, 0, 0, 0 }, // Super Ring
|
|
{ 0, 0, 0, 0 }, // Kitchen Sink
|
|
{ 0, 0, 0, 0 }, // Drop Target
|
|
{ 0, 0, 0, 0 }, // Garden Top
|
|
{ 0, 0, 0, 0 }, // Gachabom
|
|
{ 0, 1, 1, 0 }, // Sneaker x2
|
|
{ 0, 0, 1, 1 }, // Sneaker x3
|
|
{ 0, 0, 0, 0 }, // Banana x3
|
|
{ 0, 1, 1, 0 }, // Orbinaut x3
|
|
{ 0, 0, 1, 1 }, // Orbinaut x4
|
|
{ 0, 0, 1, 1 }, // Jawz x2
|
|
{ 0, 0, 0, 0 } // Gachabom x3
|
|
};
|
|
|
|
static kartitems_t K_KartItemReelSpecialEnd[] =
|
|
{
|
|
KITEM_SUPERRING,
|
|
KITEM_NONE
|
|
};
|
|
|
|
static kartitems_t K_KartItemReelTimeAttack[] =
|
|
{
|
|
KITEM_SNEAKER,
|
|
KITEM_SUPERRING,
|
|
KITEM_NONE
|
|
};
|
|
|
|
static kartitems_t K_KartItemReelSPBAttack[] =
|
|
{
|
|
KITEM_GACHABOM,
|
|
KITEM_SUPERRING,
|
|
KITEM_NONE
|
|
};
|
|
|
|
static kartitems_t K_KartItemReelBreakTheCapsules[] =
|
|
{
|
|
KITEM_GACHABOM,
|
|
KRITEM_TRIPLEGACHABOM,
|
|
KITEM_NONE
|
|
};
|
|
|
|
static kartitems_t K_KartItemReelBoss[] =
|
|
{
|
|
KITEM_ORBINAUT,
|
|
KITEM_BANANA,
|
|
KITEM_ORBINAUT,
|
|
KITEM_BANANA,
|
|
KITEM_GACHABOM,
|
|
KITEM_NONE
|
|
};
|
|
|
|
/*--------------------------------------------------
|
|
boolean K_ItemEnabled(kartitems_t item)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
boolean K_ItemEnabled(kartitems_t 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;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
boolean K_ItemSingularity(kartitems_t item)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
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)
|
|
|
|
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.
|
|
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;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
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)
|
|
{
|
|
// Bring back to normal scale.
|
|
return FixedDiv(distance, mapobjectscale);
|
|
}
|
|
|
|
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)
|
|
{
|
|
// Frantic items pretends everyone's farther apart, for crazier items.
|
|
distance = FixedMul(distance, FRANTIC_ITEM_SCALE);
|
|
}
|
|
|
|
// Items get crazier with the fewer players that you have.
|
|
distance = FixedMul(
|
|
distance,
|
|
FRACUNIT + (K_ItemOddsScale(numPlayers) / 2)
|
|
);
|
|
|
|
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;
|
|
|
|
if (player == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (specialstageinfo.valid == 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
|
|
{
|
|
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_UndoMapScaling(pdis);
|
|
pdis = K_ScaleItemDistance(pdis, numPlayers);
|
|
|
|
if (player->bot && player->botvars.rival)
|
|
{
|
|
// Rival has better odds :)
|
|
pdis = FixedMul(pdis, FRANTIC_ITEM_SCALE);
|
|
}
|
|
|
|
return pdis;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static boolean K_DenyShieldOdds(kartitems_t item)
|
|
|
|
Checks if this type of shield already exists in
|
|
another player's inventory.
|
|
|
|
Input Arguments:-
|
|
item - The item type of the shield.
|
|
|
|
Return:-
|
|
Whether this item is a shield and may not be awarded
|
|
at this time.
|
|
--------------------------------------------------*/
|
|
static boolean K_DenyShieldOdds(kartitems_t item)
|
|
{
|
|
const INT32 shieldType = K_GetShieldFromItem(item);
|
|
size_t i;
|
|
|
|
if ((gametyperules & GTR_CIRCUIT) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (shieldType == KSHIELD_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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 true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position)
|
|
|
|
Adjust odds of SPB according to distances of first and
|
|
second place players.
|
|
|
|
Input Arguments:-
|
|
roulette - The roulette data that we intend to
|
|
insert this item into.
|
|
position - Position of player to consider for these
|
|
odds.
|
|
|
|
Return:-
|
|
New item odds.
|
|
--------------------------------------------------*/
|
|
static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position)
|
|
{
|
|
I_Assert(roulette != NULL);
|
|
|
|
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)roulette->secondToFirst) - SPBSTARTDIST);
|
|
const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST;
|
|
const fixed_t maxOdds = 20 << FRACBITS;
|
|
fixed_t multiplier = FixedDiv(dist, distRange);
|
|
|
|
if (multiplier < 0)
|
|
{
|
|
multiplier = 0;
|
|
}
|
|
|
|
if (multiplier > FRACUNIT)
|
|
{
|
|
multiplier = FRACUNIT;
|
|
}
|
|
|
|
return FixedMul(maxOdds, multiplier);
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
boolean powerItem;
|
|
boolean cooldownOnStart;
|
|
boolean notNearEnd;
|
|
|
|
// gameplay state
|
|
boolean rival; // player is a bot Rival
|
|
} itemconditions_t;
|
|
|
|
/*--------------------------------------------------
|
|
static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette)
|
|
|
|
Adjust item odds to certain group conditions.
|
|
|
|
Input Arguments:-
|
|
newOdds - The item odds to adjust.
|
|
conditions - The conditions state.
|
|
roulette - The roulette data that we intend to
|
|
insert this item into.
|
|
|
|
Return:-
|
|
New item odds.
|
|
--------------------------------------------------*/
|
|
static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette)
|
|
{
|
|
// None if this applies outside of Race modes (for now?)
|
|
if ((gametyperules & GTR_CIRCUIT) == 0)
|
|
{
|
|
return newOdds;
|
|
}
|
|
|
|
if ((conditions->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 ((conditions->notNearEnd == true) && (roulette != NULL && roulette->baseDist < ENDDIST))
|
|
{
|
|
// This item should not appear at the end of a race. (Usually trap items that lose their effectiveness)
|
|
newOdds = 0;
|
|
}
|
|
else if (conditions->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 (conditions->rival == true)
|
|
{
|
|
// The Rival bot gets frantic-like items, also :p
|
|
newOdds *= 2;
|
|
}
|
|
|
|
if (roulette != NULL)
|
|
{
|
|
newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing));
|
|
}
|
|
}
|
|
|
|
return newOdds;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
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;
|
|
UINT8 position = 0;
|
|
|
|
itemconditions_t conditions = {
|
|
.powerItem = false,
|
|
.cooldownOnStart = false,
|
|
.notNearEnd = false,
|
|
.rival = false,
|
|
};
|
|
|
|
fixed_t newOdds = 0;
|
|
|
|
I_Assert(item > KITEM_NONE); // too many off by one scenarioes.
|
|
I_Assert(item < NUMKARTRESULTS);
|
|
|
|
if (player != NULL)
|
|
{
|
|
bot = player->bot;
|
|
conditions.rival = (bot == true && player->botvars.rival == true);
|
|
position = player->position;
|
|
}
|
|
|
|
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 (K_DenyShieldOdds(item))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
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 if (specialstageinfo.valid == true)
|
|
{
|
|
I_Assert(pos < 4); // Ditto
|
|
newOdds = K_KartItemOddsSpecial[item-1][pos];
|
|
}
|
|
else
|
|
{
|
|
I_Assert(pos < 8); // Ditto
|
|
newOdds = K_KartItemOddsRace[item-1][pos];
|
|
}
|
|
|
|
newOdds <<= FRACBITS;
|
|
|
|
switch (item)
|
|
{
|
|
case KITEM_BANANA:
|
|
case KITEM_EGGMAN:
|
|
case KITEM_SUPERRING:
|
|
{
|
|
conditions.notNearEnd = true;
|
|
break;
|
|
}
|
|
|
|
case KITEM_ROCKETSNEAKER:
|
|
case KITEM_JAWZ:
|
|
case KITEM_LANDMINE:
|
|
case KITEM_DROPTARGET:
|
|
case KITEM_BALLHOG:
|
|
case KRITEM_TRIPLESNEAKER:
|
|
case KRITEM_TRIPLEORBINAUT:
|
|
case KRITEM_QUADORBINAUT:
|
|
case KRITEM_DUALJAWZ:
|
|
{
|
|
conditions.powerItem = true;
|
|
break;
|
|
}
|
|
|
|
case KITEM_HYUDORO:
|
|
case KRITEM_TRIPLEBANANA:
|
|
{
|
|
conditions.powerItem = true;
|
|
conditions.notNearEnd = true;
|
|
break;
|
|
}
|
|
|
|
case KITEM_INVINCIBILITY:
|
|
case KITEM_MINE:
|
|
case KITEM_GROW:
|
|
case KITEM_BUBBLESHIELD:
|
|
case KITEM_FLAMESHIELD:
|
|
{
|
|
conditions.cooldownOnStart = true;
|
|
conditions.powerItem = true;
|
|
break;
|
|
}
|
|
|
|
case KITEM_SPB:
|
|
{
|
|
conditions.cooldownOnStart = true;
|
|
conditions.notNearEnd = true;
|
|
|
|
if (roulette != NULL &&
|
|
(gametyperules & GTR_CIRCUIT) &&
|
|
specialstageinfo.valid == false)
|
|
{
|
|
newOdds = K_AdjustSPBOdds(roulette, position);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KITEM_SHRINK:
|
|
{
|
|
conditions.cooldownOnStart = true;
|
|
conditions.powerItem = true;
|
|
conditions.notNearEnd = true;
|
|
|
|
if (roulette != NULL &&
|
|
(gametyperules & GTR_CIRCUIT) &&
|
|
roulette->playing - 1 <= roulette->exiting)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KITEM_LIGHTNINGSHIELD:
|
|
{
|
|
conditions.cooldownOnStart = true;
|
|
conditions.powerItem = true;
|
|
|
|
if ((gametyperules & GTR_CIRCUIT) && spbplace != -1)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newOdds == 0)
|
|
{
|
|
// Nothing else we want to do with odds matters at this point :p
|
|
return newOdds;
|
|
}
|
|
|
|
newOdds = FixedInt(FixedRound(K_AdjustItemOddsToConditions(newOdds, &conditions, 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;
|
|
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;
|
|
continue;
|
|
}
|
|
else if (specialstageinfo.valid == true && i > 3)
|
|
{
|
|
oddsValid[i] = false;
|
|
continue;
|
|
}
|
|
|
|
for (j = 1; j < NUMKARTRESULTS; j++)
|
|
{
|
|
if (K_KartGetItemOdds(player, roulette, i, j) > 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
|
|
{
|
|
if (specialstageinfo.valid == true) // Special Stages
|
|
{
|
|
SETUPDISTTABLE(0,2);
|
|
SETUPDISTTABLE(1,2);
|
|
SETUPDISTTABLE(2,3);
|
|
SETUPDISTTABLE(3,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);
|
|
}
|
|
|
|
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 (roulette->dist <= (unsigned)dist)
|
|
{
|
|
useOdds = distTable[index];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef SETUPDISTTABLE
|
|
|
|
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)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!(gametyperules & GTR_CIRCUIT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (specialstageinfo.valid == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (player == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (player->position <= 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (spbplace != -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (itemCooldowns[KITEM_SPB - 1] > 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if 0
|
|
if (roulette->playing <= 2)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
|
|
#ifndef ITEM_LIST_SIZE
|
|
if (roulette->itemList == NULL)
|
|
{
|
|
roulette->itemListCap = 8;
|
|
roulette->itemList = Z_Calloc(
|
|
sizeof(SINT8) * roulette->itemListCap,
|
|
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;
|
|
|
|
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_TIMEATTACK; // 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 (specialstageinfo.valid == true)
|
|
{
|
|
UINT32 dis = K_UndoMapScaling(players[i].distancetofinish);
|
|
if (dis < roulette->secondDist)
|
|
{
|
|
roulette->secondDist = dis;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (players[i].position == 1)
|
|
{
|
|
roulette->firstDist = K_UndoMapScaling(players[i].distancetofinish);
|
|
}
|
|
|
|
if (players[i].position == 2)
|
|
{
|
|
roulette->secondDist = K_UndoMapScaling(players[i].distancetofinish);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (specialstageinfo.valid == true)
|
|
{
|
|
roulette->firstDist = K_UndoMapScaling(K_GetSpecialUFODistance());
|
|
}
|
|
|
|
// Calculate 2nd's distance from 1st, for SPB
|
|
if (roulette->firstDist != UINT32_MAX && roulette->secondDist != UINT32_MAX
|
|
&& roulette->secondDist > roulette->firstDist)
|
|
{
|
|
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)
|
|
|
|
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
|
|
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)
|
|
{
|
|
roulette->itemListCap *= 2;
|
|
roulette->itemList = Z_Realloc(
|
|
roulette->itemList,
|
|
sizeof(SINT8) * roulette->itemListCap,
|
|
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++;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
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);
|
|
|
|
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
|
|
&& player->rings <= 0
|
|
&& player->position == 1
|
|
&& (gametyperules & GTR_SPHERES) == 0)
|
|
{
|
|
K_PushToRouletteItemList(roulette, KITEM_SUPERRING);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
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;
|
|
fixed_t progress = 0;
|
|
fixed_t total = 0;
|
|
|
|
if (K_TimeAttackRules() == true)
|
|
{
|
|
// Time Attack rules; use a consistent speed.
|
|
roulette->tics = roulette->speed = ROULETTE_SPEED_TIMEATTACK;
|
|
return;
|
|
}
|
|
|
|
if (roulette->baseDist > ENDDIST)
|
|
{
|
|
// Being farther in the course makes your roulette faster.
|
|
progress = min(FRACUNIT, FixedDiv(roulette->baseDist - ENDDIST, ROULETTE_SPEED_DIST));
|
|
}
|
|
|
|
if (roulette->baseDist > roulette->firstDist)
|
|
{
|
|
// Frontrunning makes your roulette faster.
|
|
frontRun = min(FRACUNIT, FixedDiv(roulette->baseDist - roulette->firstDist, ENDDIST));
|
|
}
|
|
|
|
// Combine our two factors together.
|
|
total = min(FRACUNIT, (frontRun / 2) + (progress / 2));
|
|
|
|
if (leveltime < starttime + 30*TICRATE)
|
|
{
|
|
// Don't impact as much at the start.
|
|
// This makes it so that everyone gets to enjoy the lowest speed at the start.
|
|
if (leveltime < starttime)
|
|
{
|
|
total = FRACUNIT;
|
|
}
|
|
else
|
|
{
|
|
const fixed_t lerp = FixedDiv(leveltime - starttime, 30*TICRATE);
|
|
total = FRACUNIT + FixedMul(lerp, total - FRACUNIT);
|
|
}
|
|
}
|
|
|
|
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};
|
|
UINT32 totalSpawnChance = 0;
|
|
size_t rngRoll = 0;
|
|
|
|
UINT8 numItems = 0;
|
|
kartitems_t singleItem = KITEM_SAD;
|
|
|
|
size_t i;
|
|
|
|
K_InitRoulette(roulette);
|
|
|
|
if (player != NULL)
|
|
{
|
|
roulette->baseDist = K_UndoMapScaling(player->distancetofinish);
|
|
K_CalculateRouletteSpeed(roulette);
|
|
}
|
|
|
|
// 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 / End of Sealed Stars
|
|
if (specialstageinfo.valid)
|
|
{
|
|
if (K_GetPossibleSpecialTarget() == NULL)
|
|
{
|
|
for (i = 0; K_KartItemReelSpecialEnd[i] != KITEM_NONE; i++)
|
|
{
|
|
K_PushToRouletteItemList(roulette, K_KartItemReelSpecialEnd[i]);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else if (gametyperules & GTR_BOSS)
|
|
{
|
|
for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++)
|
|
{
|
|
K_PushToRouletteItemList(roulette, K_KartItemReelBoss[i]);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (K_TimeAttackRules() == true)
|
|
{
|
|
kartitems_t *presetlist = K_KartItemReelTimeAttack;
|
|
|
|
// If the objective is not to go fast, it's to cause serious damage.
|
|
if (gametyperules & GTR_PRISONS)
|
|
{
|
|
presetlist = K_KartItemReelBreakTheCapsules;
|
|
}
|
|
else if (modeattacking & ATTACKING_SPB)
|
|
{
|
|
presetlist = K_KartItemReelSPBAttack;
|
|
}
|
|
|
|
for (i = 0; presetlist[i] != KITEM_NONE; i++)
|
|
{
|
|
K_PushToRouletteItemList(roulette, presetlist[i]);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// SPECIAL CASE No. 3:
|
|
// Only give the SPB if conditions are right
|
|
if (K_ForcedSPB(player, roulette) == true)
|
|
{
|
|
K_AddItemToReel(player, roulette, KITEM_SPB);
|
|
return;
|
|
}
|
|
|
|
// SPECIAL CASE No. 4:
|
|
// If only one item is enabled, always use it
|
|
for (i = 1; i < NUMKARTRESULTS; i++)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
rngRoll = P_RandomKey(PR_ITEM_ROULETTE, totalSpawnChance);
|
|
for (i = 1; i < NUMKARTRESULTS && spawnChance[i] <= rngRoll; i++)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
K_AddItemToReel(player, roulette, i);
|
|
|
|
for (; i < NUMKARTRESULTS; i++)
|
|
{
|
|
// Be sure to fix the remaining items' odds too.
|
|
if (spawnChance[i] > 0)
|
|
{
|
|
spawnChance[i]--;
|
|
}
|
|
}
|
|
|
|
totalSpawnChance--;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
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;
|
|
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)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
void K_StartEggmanRoulette(player_t *const player)
|
|
{
|
|
itemroulette_t *const roulette = &player->itemRoulette;
|
|
K_StartItemRoulette(player);
|
|
roulette->eggman = true;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta)
|
|
|
|
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 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)
|
|
{
|
|
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)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
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->itemListLen == 0
|
|
#ifndef ITEM_LIST_SIZE
|
|
|| roulette->itemList == NULL
|
|
#endif
|
|
)
|
|
{
|
|
// 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 || (roulette->eggman && 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)
|
|
{
|
|
if (roulette->eggman == true)
|
|
{
|
|
// FATASS JUMPSCARE instead of your actual item
|
|
player->eggmanexplode = 6*TICRATE;
|
|
|
|
//player->karthud[khud_itemblink] = TICRATE;
|
|
//player->karthud[khud_itemblinkmode] = 1;
|
|
//player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT);
|
|
|
|
if (P_IsDisplayPlayer(player) && !demo.freecam)
|
|
{
|
|
S_StartSound(NULL, sfx_itrole);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// D2 fudge factor. Roulette was originally designed and tested with this delay.
|
|
UINT8 fudgedDelay = (player->cmd.latency <= 2) ? 0 : player->cmd.latency - 2;
|
|
while (fudgedDelay > 0)
|
|
{
|
|
UINT8 gap = (roulette->speed - roulette->tics); // How long has the roulette been on this entry?
|
|
if (fudgedDelay > gap) // Did the roulette tick over in-flight?
|
|
{
|
|
fudgedDelay = fudgedDelay - gap; // We're compensating for this gap's worth of delay, so cut it down.
|
|
roulette->index = roulette->index == 0 ? roulette->itemListLen - 1 : roulette->index - 1; // Roll the roulette index back...
|
|
roulette->tics = 0; // And just in case our delay is SO high that a fast roulette needs to roll back again...
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// And one more nudge for the remaining delay.
|
|
roulette->tics = (roulette->tics + fudgedDelay) % roulette->speed;
|
|
|
|
kartitems_t finalItem = roulette->itemList[ roulette->index ];
|
|
|
|
K_KartGetItemResult(player, finalItem);
|
|
|
|
player->karthud[khud_itemblink] = TICRATE;
|
|
player->karthud[khud_itemblinkmode] = 0;
|
|
player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT);
|
|
|
|
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--;
|
|
}
|
|
}
|