Merge remote-tracking branch 'origin/master' into ta-rebalance-gaiden

This commit is contained in:
Antonio Martinez 2025-08-07 21:20:00 -04:00
commit fad24db244
23 changed files with 1381 additions and 260 deletions

View file

@ -123,6 +123,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
lua_hudlib.c lua_hudlib.c
lua_hudlib_drawlist.c lua_hudlib_drawlist.c
lua_followerlib.c lua_followerlib.c
lua_itemroulettelib.c
lua_profile.cpp lua_profile.cpp
k_kart.c k_kart.c
k_respawn.c k_respawn.c

View file

@ -426,6 +426,7 @@ struct botvars_t
UINT8 difficulty; // Bot's difficulty setting UINT8 difficulty; // Bot's difficulty setting
INT16 diffincrease; // In GP: bot difficulty will increase this much next round INT16 diffincrease; // In GP: bot difficulty will increase this much next round
boolean rival; // If true, they're the GP rival boolean rival; // If true, they're the GP rival
boolean foe; // If true, in contention for top X
// All entries above persist between rounds and must be recorded in demos // All entries above persist between rounds and must be recorded in demos
@ -516,22 +517,24 @@ struct skybox_t {
// player_t struct for item roulette variables // player_t struct for item roulette variables
// Doing this the right way is causing problems. // In case of dynamic alloc failure, break glass:
// so FINE, it's a static length now. // #define ITEM_LIST_SIZE (NUMKARTRESULTS << 3)
#define ITEM_LIST_SIZE (NUMKARTRESULTS << 3)
typedef struct itemlist_t
{
size_t len;
#ifdef ITEM_LIST_SIZE
SINT8 items[ITEM_LIST_SIZE];
#else
SINT8 *items;
size_t cap;
#endif
} itemlist_t;
struct itemroulette_t struct itemroulette_t
{ {
boolean active; boolean active;
itemlist_t itemList;
#ifdef ITEM_LIST_SIZE
size_t itemListLen;
SINT8 itemList[ITEM_LIST_SIZE];
#else
size_t itemListCap;
size_t itemListLen;
SINT8 *itemList;
#endif
UINT8 playing, exiting; UINT8 playing, exiting;
UINT32 preexpdist, dist, baseDist; UINT32 preexpdist, dist, baseDist;

View file

@ -5248,6 +5248,21 @@ struct int_const_s const INT_CONST[] = {
{"ENDOFPOWERUPS",ENDOFPOWERUPS}, {"ENDOFPOWERUPS",ENDOFPOWERUPS},
{"LASTPOWERUP",LASTPOWERUP}, {"LASTPOWERUP",LASTPOWERUP},
{"NUMPOWERUPS",NUMPOWERUPS}, {"NUMPOWERUPS",NUMPOWERUPS},
// kartslotmachine_t
{"KSM_BAR", KSM_BAR},
{"KSM_DOUBLEBAR", KSM_DOUBLEBAR},
{"KSM_TRIPLEBAR", KSM_TRIPLEBAR},
{"KSM_RING", KSM_RING},
{"KSM_SEVEN", KSM_SEVEN},
{"KSM_JACKPOT", KSM_JACKPOT},
{"KSM__MAX", KSM__MAX},
// itemflags_t
{"IF_USERINGS", IF_USERINGS},
{"IF_ITEMOUT", IF_ITEMOUT},
{"IF_EGGMANOUT", IF_EGGMANOUT},
{"IF_HOLDREADY", IF_HOLDREADY},
// kartshields_t // kartshields_t
{"KSHIELD_NONE",KSHIELD_NONE}, {"KSHIELD_NONE",KSHIELD_NONE},

View file

@ -321,6 +321,7 @@ void G_ReadDemoExtraData(void)
players[p].botvars.difficulty = READUINT8(demobuf.p); players[p].botvars.difficulty = READUINT8(demobuf.p);
players[p].botvars.diffincrease = READINT16(demobuf.p); // needed to avoid having to duplicate logic players[p].botvars.diffincrease = READINT16(demobuf.p); // needed to avoid having to duplicate logic
players[p].botvars.rival = (boolean)READUINT8(demobuf.p); players[p].botvars.rival = (boolean)READUINT8(demobuf.p);
players[p].botvars.foe = (boolean)READUINT8(demobuf.p);
} }
} }
if (extradata & DXD_PLAYSTATE) if (extradata & DXD_PLAYSTATE)
@ -497,6 +498,7 @@ void G_WriteDemoExtraData(void)
WRITEUINT8(demobuf.p, players[i].botvars.difficulty); WRITEUINT8(demobuf.p, players[i].botvars.difficulty);
WRITEINT16(demobuf.p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic WRITEINT16(demobuf.p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic
WRITEUINT8(demobuf.p, (UINT8)players[i].botvars.rival); WRITEUINT8(demobuf.p, (UINT8)players[i].botvars.rival);
WRITEUINT8(demobuf.p, (UINT8)players[i].botvars.foe);
} }
} }
if (demo_extradata[i] & DXD_PLAYSTATE) if (demo_extradata[i] & DXD_PLAYSTATE)
@ -2132,6 +2134,7 @@ void G_BeginRecording(void)
WRITEUINT8(demobuf.p, player->botvars.difficulty); WRITEUINT8(demobuf.p, player->botvars.difficulty);
WRITEINT16(demobuf.p, player->botvars.diffincrease); // needed to avoid having to duplicate logic WRITEINT16(demobuf.p, player->botvars.diffincrease); // needed to avoid having to duplicate logic
WRITEUINT8(demobuf.p, (UINT8)player->botvars.rival); WRITEUINT8(demobuf.p, (UINT8)player->botvars.rival);
WRITEUINT8(demobuf.p, (UINT8)player->botvars.foe);
} }
// Name // Name
@ -3315,6 +3318,7 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
players[p].botvars.difficulty = READUINT8(demobuf.p); players[p].botvars.difficulty = READUINT8(demobuf.p);
players[p].botvars.diffincrease = READINT16(demobuf.p); // needed to avoid having to duplicate logic players[p].botvars.diffincrease = READINT16(demobuf.p); // needed to avoid having to duplicate logic
players[p].botvars.rival = (boolean)READUINT8(demobuf.p); players[p].botvars.rival = (boolean)READUINT8(demobuf.p);
players[p].botvars.foe = (boolean)READUINT8(demobuf.p);
} }
K_UpdateShrinkCheat(&players[p]); K_UpdateShrinkCheat(&players[p]);

View file

@ -2271,6 +2271,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
INT16 botdiffincrease; INT16 botdiffincrease;
boolean botrival; boolean botrival;
boolean botfoe;
boolean cangrabitems; boolean cangrabitems;
@ -2381,6 +2382,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
botdiffincrease = players[player].botvars.diffincrease; botdiffincrease = players[player].botvars.diffincrease;
botrival = players[player].botvars.rival; botrival = players[player].botvars.rival;
botfoe = players[player].botvars.foe;
totalring = players[player].totalring; totalring = players[player].totalring;
xtralife = players[player].xtralife; xtralife = players[player].xtralife;
@ -2641,6 +2643,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->spheres = spheres; p->spheres = spheres;
p->botvars.diffincrease = botdiffincrease; p->botvars.diffincrease = botdiffincrease;
p->botvars.rival = botrival; p->botvars.rival = botrival;
p->botvars.foe = botfoe;
p->xtralife = xtralife; p->xtralife = xtralife;
// SRB2kart // SRB2kart

View file

@ -408,6 +408,8 @@ void K_UpdateMatchRaceBots(void)
clear_bots(wantedbots); clear_bots(wantedbots);
} }
K_AssignFoes();
// We should have enough bots now :) // We should have enough bots now :)
#ifdef HAVE_DISCORDRPC #ifdef HAVE_DISCORDRPC
@ -615,7 +617,7 @@ fixed_t K_BotMapModifier(void)
--------------------------------------------------*/ --------------------------------------------------*/
static UINT32 K_BotRubberbandDistance(const player_t *player) static UINT32 K_BotRubberbandDistance(const player_t *player)
{ {
const UINT32 spacing = FixedDiv(640 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT; UINT32 spacing = FixedDiv(640 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT;
const UINT8 portpriority = player - players; const UINT8 portpriority = player - players;
UINT8 pos = 1; UINT8 pos = 1;
UINT8 i; UINT8 i;
@ -626,6 +628,11 @@ static UINT32 K_BotRubberbandDistance(const player_t *player)
return 0; return 0;
} }
/*
if (player->botvars.foe)
spacing /= 2;
*/
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (i == portpriority) if (i == portpriority)
@ -649,6 +656,11 @@ static UINT32 K_BotRubberbandDistance(const player_t *player)
continue; continue;
} }
if (player->botvars.foe && !players[i].botvars.foe)
{
continue;
}
// First check difficulty levels, then score, then settle it with port priority! // First check difficulty levels, then score, then settle it with port priority!
if (player->botvars.difficulty < players[i].botvars.difficulty) if (player->botvars.difficulty < players[i].botvars.difficulty)
{ {
@ -694,6 +706,8 @@ fixed_t K_BotRubberband(const player_t *player)
{ {
UINT8 levelreduce = std::min<UINT8>(3, player->botvars.difficulty/4); // How much to drop the "effective level" of bots that are consistently behind UINT8 levelreduce = std::min<UINT8>(3, player->botvars.difficulty/4); // How much to drop the "effective level" of bots that are consistently behind
expreduce = Easing_Linear((K_EffectiveGradingFactor(player) - MINGRADINGFACTOR) * 2, levelreduce*FRACUNIT, 0); expreduce = Easing_Linear((K_EffectiveGradingFactor(player) - MINGRADINGFACTOR) * 2, levelreduce*FRACUNIT, 0);
if (player->botvars.foe)
expreduce /= 2;
} }
fixed_t difficultyEase = (((player->botvars.difficulty - 1) * FRACUNIT) - expreduce) / (MAXBOTDIFFICULTY - 1); fixed_t difficultyEase = (((player->botvars.difficulty - 1) * FRACUNIT) - expreduce) / (MAXBOTDIFFICULTY - 1);
@ -805,7 +819,13 @@ fixed_t K_BotRubberband(const player_t *player)
scaled_dist = FixedDiv(scaled_dist, mapobjectscale); scaled_dist = FixedDiv(scaled_dist, mapobjectscale);
} }
constexpr UINT32 END_DIST = 2048 * 14; UINT32 END_DIST = 2048 * 14;
if (K_EffectiveGradingFactor(player) <= FRACUNIT)
{
END_DIST = Easing_Linear((K_EffectiveGradingFactor(player) - MINGRADINGFACTOR) * 2, END_DIST * 2, END_DIST);
}
if (scaled_dist < END_DIST) if (scaled_dist < END_DIST)
{ {
// At the end of tracks, start slowing down. // At the end of tracks, start slowing down.
@ -823,7 +843,7 @@ fixed_t K_BotRubberband(const player_t *player)
fixed_t K_UpdateRubberband(player_t *player) fixed_t K_UpdateRubberband(player_t *player)
{ {
fixed_t dest = K_BotRubberband(player); fixed_t dest = K_BotRubberband(player);
fixed_t deflect = player->botvars.recentDeflection; fixed_t deflect = player->botvars.recentDeflection;
if (deflect > BOTMAXDEFLECTION) if (deflect > BOTMAXDEFLECTION)
deflect = BOTMAXDEFLECTION; deflect = BOTMAXDEFLECTION;
@ -2146,7 +2166,7 @@ void K_UpdateBotGameplayVars(player_t *player)
UINT32 smo = BOTANGLESAMPLES - 1; UINT32 smo = BOTANGLESAMPLES - 1;
player->botvars.recentDeflection = (smo * player->botvars.recentDeflection / BOTANGLESAMPLES) + (dangle / BOTANGLESAMPLES); player->botvars.recentDeflection = (smo * player->botvars.recentDeflection / BOTANGLESAMPLES) + (dangle / BOTANGLESAMPLES);
player->botvars.lastAngle = mangle; player->botvars.lastAngle = mangle;
const botcontroller_t *botController = K_GetBotController(player->mo); const botcontroller_t *botController = K_GetBotController(player->mo);

View file

@ -299,7 +299,7 @@ static boolean K_RivalBotAggression(const player_t *bot, const player_t *target)
return false; return false;
} }
if (bot->botvars.rival == false && !cv_levelskull.value) if (!(bot->botvars.rival || bot->botvars.foe) && !cv_levelskull.value)
{ {
// Not the rival, we aren't self-aware. // Not the rival, we aren't self-aware.
return false; return false;
@ -1863,7 +1863,7 @@ static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player)
else else
{ {
botItemPriority_e currentPriority = K_GetBotItemPriority( botItemPriority_e currentPriority = K_GetBotItemPriority(
static_cast<kartitems_t>( player->itemRoulette.itemList[ player->itemRoulette.index ] ) static_cast<kartitems_t>( player->itemRoulette.itemList.items[ player->itemRoulette.index ] )
); );
if (player->botvars.roulettePriority == currentPriority) if (player->botvars.roulettePriority == currentPriority)
@ -1877,7 +1877,7 @@ static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player)
// reduce priority until we get to a valid one. // reduce priority until we get to a valid one.
player->botvars.rouletteTimeout++; player->botvars.rouletteTimeout++;
if (player->botvars.rouletteTimeout > player->itemRoulette.itemListLen * player->itemRoulette.speed) if (player->botvars.rouletteTimeout > player->itemRoulette.itemList.len * player->itemRoulette.speed)
{ {
player->botvars.roulettePriority--; player->botvars.roulettePriority--;
player->botvars.rouletteTimeout = 0; player->botvars.rouletteTimeout = 0;
@ -1995,9 +1995,9 @@ void K_BotPickItemPriority(player_t *player)
player->botvars.rouletteTimeout = 0; player->botvars.rouletteTimeout = 0;
// Check for items that are extremely high priority. // Check for items that are extremely high priority.
for (i = 0; i < player->itemRoulette.itemListLen; i++) for (i = 0; i < player->itemRoulette.itemList.len; i++)
{ {
botItemPriority_e priority = K_GetBotItemPriority( static_cast<kartitems_t>( player->itemRoulette.itemList[i] ) ); botItemPriority_e priority = K_GetBotItemPriority( static_cast<kartitems_t>( player->itemRoulette.itemList.items[i] ) );
if (priority < BOT_ITEM_PR__OVERRIDES) if (priority < BOT_ITEM_PR__OVERRIDES)
{ {

View file

@ -98,6 +98,113 @@ static UINT8 K_GetOffsetStartingDifficulty(const UINT8 startingdifficulty, UINT8
return startingdifficulty - offset; return startingdifficulty - offset;
} }
/*--------------------------------------------------
static INT16 K_RivalScore(player_t *bot)
Creates a "rival score" for a bot, used to determine which bot is the
most deserving of the rival status.
Input Arguments:-
bot - Player to check.
Return:-
"Rival score" value.
--------------------------------------------------*/
static INT16 K_RivalScore(player_t *bot)
{
const UINT16 difficulty = bot->botvars.difficulty;
const UINT16 score = bot->score;
SINT8 roundnum = 1, roundsleft = 1;
UINT16 lowestscore = UINT16_MAX;
UINT8 lowestdifficulty = MAXBOTDIFFICULTY;
UINT8 i;
if (grandprixinfo.cup != NULL && roundqueue.size > 0)
{
roundnum = roundqueue.roundnum;
roundsleft = grandprixinfo.cup->numlevels - roundnum;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
if (players[i].score < lowestscore)
{
lowestscore = players[i].score;
}
if (players[i].bot == true && players[i].botvars.difficulty < lowestdifficulty)
{
lowestdifficulty = players[i].botvars.difficulty;
}
}
// In the early game, difficulty is more important.
// This will try to influence the higher difficulty bots to get rival more often & get even more points.
// However, when we're running low on matches left, we need to focus more on raw score!
return ((difficulty - lowestdifficulty) * roundsleft) + ((score - lowestscore) * roundnum);
}
static boolean CompareRivals(player_t *a, player_t *b)
{
if (a == NULL)
return false;
if (b == NULL)
return true;
if (K_RivalScore(a) != K_RivalScore(b))
{
// Rival Score is HIGH when bots are strong. Sort them first!
return (K_RivalScore(a) > K_RivalScore(b));
}
// Fuck it
return a > b;
}
void K_AssignFoes(void)
{
std::vector<player_t *> bots;
boolean addedplayer = false;
for (UINT8 i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] == false)
continue;
player_t *player = &players[i];
if (!player->spectator && player->bot)
{
addedplayer = true;
bots.push_back(player);
player->botvars.foe = false;
}
}
// Why the fuck is this blowing up sometimes
if (!addedplayer)
return;
std::stable_sort(bots.begin(), bots.end(), CompareRivals);
UINT8 i = 0;
for (auto &bot : bots)
{
if (bot != NULL)
bot->botvars.foe = true;
i++;
if (i > 2)
break;
}
}
/*-------------------------------------------------- /*--------------------------------------------------
void K_InitGrandPrixBots(void) void K_InitGrandPrixBots(void)
@ -254,6 +361,8 @@ void K_InitGrandPrixBots(void)
{ {
break; break;
} }
if (i <= 2)
players[newplayernum-1].botvars.foe = true;
} }
} }
@ -289,64 +398,13 @@ void K_LoadGrandPrixSaveGame(void)
K_SetBot(i, savedata.bots[i].skin, savedata.bots[i].difficulty, BOT_STYLE_NORMAL); K_SetBot(i, savedata.bots[i].skin, savedata.bots[i].difficulty, BOT_STYLE_NORMAL);
players[i].botvars.rival = savedata.bots[i].rival; players[i].botvars.rival = savedata.bots[i].rival;
players[i].botvars.foe = savedata.bots[i].foe;
players[i].score = savedata.bots[i].score; players[i].score = savedata.bots[i].score;
players[i].spectator = K_BotDefaultSpectator(); players[i].spectator = K_BotDefaultSpectator();
} }
} }
/*--------------------------------------------------
static INT16 K_RivalScore(player_t *bot)
Creates a "rival score" for a bot, used to determine which bot is the
most deserving of the rival status.
Input Arguments:-
bot - Player to check.
Return:-
"Rival score" value.
--------------------------------------------------*/
static INT16 K_RivalScore(player_t *bot)
{
const UINT16 difficulty = bot->botvars.difficulty;
const UINT16 score = bot->score;
SINT8 roundnum = 1, roundsleft = 1;
UINT16 lowestscore = UINT16_MAX;
UINT8 lowestdifficulty = MAXBOTDIFFICULTY;
UINT8 i;
if (grandprixinfo.cup != NULL && roundqueue.size > 0)
{
roundnum = roundqueue.roundnum;
roundsleft = grandprixinfo.cup->numlevels - roundnum;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
if (players[i].score < lowestscore)
{
lowestscore = players[i].score;
}
if (players[i].bot == true && players[i].botvars.difficulty < lowestdifficulty)
{
lowestdifficulty = players[i].botvars.difficulty;
}
}
// In the early game, difficulty is more important.
// This will try to influence the higher difficulty bots to get rival more often & get even more points.
// However, when we're running low on matches left, we need to focus more on raw score!
return ((difficulty - lowestdifficulty) * roundsleft) + ((score - lowestscore) * roundnum);
}
/*-------------------------------------------------- /*--------------------------------------------------
void K_UpdateGrandPrixBots(void) void K_UpdateGrandPrixBots(void)
@ -379,6 +437,8 @@ void K_UpdateGrandPrixBots(void)
return; return;
} }
K_AssignFoes();
// Find the rival. // Find the rival.
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
@ -639,7 +699,7 @@ void K_IncreaseBotDifficulty(player_t *bot)
// RELAXED MODE: // RELAXED MODE:
// Continues don't drop bot difficulty, because we always advance. // Continues don't drop bot difficulty, because we always advance.
// Bots will still level up from standard advancement; we need a // Bots will still level up from standard advancement; we need a
// much steeper rank nudge to keep difficulty at the right level. // much steeper rank nudge to keep difficulty at the right level.
if (grandprixinfo.gamespeed == KARTSPEED_EASY) if (grandprixinfo.gamespeed == KARTSPEED_EASY)
{ {
switch(averageRank) switch(averageRank)
@ -689,8 +749,8 @@ static boolean CompareJoiners(player_t *a, player_t *b)
return (a->spectatewait < b->spectatewait); return (a->spectatewait < b->spectatewait);
} }
// They are equals, so just randomize // Fuck it
return (P_Random(PR_BOTS) & 1); return a > b;
} }
static boolean CompareReplacements(player_t *a, player_t *b) static boolean CompareReplacements(player_t *a, player_t *b)
@ -707,8 +767,8 @@ static boolean CompareReplacements(player_t *a, player_t *b)
return (a->position < b->position); return (a->position < b->position);
} }
// They are equals, so just randomize // Fuck it
return (P_Random(PR_BOTS) & 1); return a > b;
} }
/*-------------------------------------------------- /*--------------------------------------------------

View file

@ -200,6 +200,8 @@ boolean K_CanChangeRules(boolean allowdemos);
boolean K_BotDefaultSpectator(void); boolean K_BotDefaultSpectator(void);
void K_AssignFoes(void);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View file

@ -1630,15 +1630,15 @@ static void K_drawKartItem(void)
boolean flashOnOne = false; boolean flashOnOne = false;
boolean flashOnTwo = false; boolean flashOnTwo = false;
if (stplyr->itemRoulette.itemListLen > 0) if (stplyr->itemRoulette.itemList.len > 0)
{ {
// Init with item roulette stuff. // Init with item roulette stuff.
for (i = 0; i < 3; i++) for (i = 0; i < 3; i++)
{ {
const SINT8 indexOfs = i-1; const SINT8 indexOfs = i-1;
const size_t index = (stplyr->itemRoulette.itemListLen + (stplyr->itemRoulette.index + indexOfs)) % stplyr->itemRoulette.itemListLen; const size_t index = (stplyr->itemRoulette.itemList.len + (stplyr->itemRoulette.index + indexOfs)) % stplyr->itemRoulette.itemList.len;
const SINT8 result = stplyr->itemRoulette.itemList[index]; const SINT8 result = stplyr->itemRoulette.itemList.items[index];
const SINT8 item = K_ItemResultToType(result); const SINT8 item = K_ItemResultToType(result);
const boolean usingDebugItemAmount = cv_kartdebugitem.value != KITEM_NONE && cv_kartdebugitem.value == item && cv_kartdebugamount.value > 1; const boolean usingDebugItemAmount = cv_kartdebugitem.value != KITEM_NONE && cv_kartdebugitem.value == item && cv_kartdebugamount.value > 1;
const UINT8 amt = usingDebugItemAmount ? cv_kartdebugamount.value : K_ItemResultToAmount(result, &stplyr->itemRoulette); const UINT8 amt = usingDebugItemAmount ? cv_kartdebugamount.value : K_ItemResultToAmount(result, &stplyr->itemRoulette);
@ -1825,7 +1825,7 @@ static void K_drawKartItem(void)
} }
UINT8 *boxmap = NULL; UINT8 *boxmap = NULL;
if (stplyr->itemRoulette.active && (stplyr->itemRoulette.speed - stplyr->itemRoulette.tics < 3) && stplyr->itemRoulette.index == 0 && stplyr->itemRoulette.itemListLen > 1) if (stplyr->itemRoulette.active && (stplyr->itemRoulette.speed - stplyr->itemRoulette.tics < 3) && stplyr->itemRoulette.index == 0 && stplyr->itemRoulette.itemList.len > 1)
{ {
boxmap = R_GetTranslationColormap(TC_ALLWHITE, SKINCOLOR_WHITE, GTC_CACHE); boxmap = R_GetTranslationColormap(TC_ALLWHITE, SKINCOLOR_WHITE, GTC_CACHE);
} }
@ -2144,15 +2144,15 @@ static void K_drawKartSlotMachine(void)
vector2_t rouletteCrop = {10, 10}; vector2_t rouletteCrop = {10, 10};
INT32 i; INT32 i;
if (stplyr->itemRoulette.itemListLen > 0) if (stplyr->itemRoulette.itemList.len > 0)
{ {
// Init with item roulette stuff. // Init with item roulette stuff.
for (i = 0; i < 3; i++) for (i = 0; i < 3; i++)
{ {
const SINT8 indexOfs = i-1; const SINT8 indexOfs = i-1;
const size_t index = (stplyr->itemRoulette.itemListLen + (stplyr->itemRoulette.index + indexOfs)) % stplyr->itemRoulette.itemListLen; const size_t index = (stplyr->itemRoulette.itemList.len + (stplyr->itemRoulette.index + indexOfs)) % stplyr->itemRoulette.itemList.len;
const SINT8 result = stplyr->itemRoulette.itemList[index]; const SINT8 result = stplyr->itemRoulette.itemList.items[index];
localpatch[i] = K_GetCachedSlotMachinePatch(result, offset); localpatch[i] = K_GetCachedSlotMachinePatch(result, offset);
} }
@ -5346,8 +5346,10 @@ static void K_DrawCPUTagForPlayer(fixed_t x, fixed_t y, player_t *p, UINT32 flag
K_DrawNameTagItemSpy(barx, bary, p, flags); K_DrawNameTagItemSpy(barx, bary, p, flags);
} }
UINT8 *foecol = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(SKINCOLOR_RED), GTC_CACHE);
UINT8 blink = ((leveltime / 7) & 1); UINT8 blink = ((leveltime / 7) & 1);
V_DrawFixedPatch(x, y, FRACUNIT, flags, kp_cpu[blink], NULL); V_DrawFixedPatch(x, y, FRACUNIT, flags, kp_cpu[blink], (p->botvars.foe) ? foecol : NULL);
} }
static void K_DrawNameTagForPlayer(fixed_t x, fixed_t y, player_t *p, UINT32 flags) static void K_DrawNameTagForPlayer(fixed_t x, fixed_t y, player_t *p, UINT32 flags)
@ -7202,7 +7204,7 @@ static void K_drawDistributionDebugger(void)
V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+58, V_SNAPTOTOP|V_SNAPTORIGHT, va("secondToFirst = %u", rouletteData.secondToFirst)); V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+58, V_SNAPTOTOP|V_SNAPTORIGHT, va("secondToFirst = %u", rouletteData.secondToFirst));
#ifndef ITEM_LIST_SIZE #ifndef ITEM_LIST_SIZE
Z_Free(rouletteData.itemList); Z_Free(rouletteData.itemList.items);
#endif #endif
} }
@ -7314,7 +7316,7 @@ static void K_DrawBotDebugger(void)
V_DrawSmallString(8, 14, 0, va("Difficulty: %d / %d", bot->botvars.difficulty, MAXBOTDIFFICULTY)); V_DrawSmallString(8, 14, 0, va("Difficulty: %d / %d", bot->botvars.difficulty, MAXBOTDIFFICULTY));
V_DrawSmallString(8, 18, 0, va("Difficulty increase: %d", bot->botvars.diffincrease)); V_DrawSmallString(8, 18, 0, va("Difficulty increase: %d", bot->botvars.diffincrease));
V_DrawSmallString(8, 22, 0, va("Rival: %d", (UINT8)(bot->botvars.rival == true))); V_DrawSmallString(8, 22, 0, va("Rival / Foe: %d / %d", (UINT8)(bot->botvars.rival == true), (UINT8)(bot->botvars.foe == true)));
V_DrawSmallString(8, 26, 0, va("Rubberbanding: %.02f", FIXED_TO_FLOAT(bot->botvars.rubberband) * 100.0f)); V_DrawSmallString(8, 26, 0, va("Rubberbanding: %.02f", FIXED_TO_FLOAT(bot->botvars.rubberband) * 100.0f));
V_DrawSmallString(8, 32, 0, va("Item delay: %d", bot->botvars.itemdelay)); V_DrawSmallString(8, 32, 0, va("Item delay: %d", bot->botvars.itemdelay));

View file

@ -145,6 +145,9 @@ boolean K_InRaceDuel(void)
fixed_t K_EffectiveGradingFactor(const player_t *player) fixed_t K_EffectiveGradingFactor(const player_t *player)
{ {
if (player == NULL)
return FRACUNIT; // K_FillItemRouletteData can OSTENSIBLY call this with null player for "generic" use.
fixed_t min = (franticitems) ? MINFRANTICFACTOR : MINGRADINGFACTOR; fixed_t min = (franticitems) ? MINFRANTICFACTOR : MINGRADINGFACTOR;
if (grandprixinfo.gp && grandprixinfo.masterbots && !K_PlayerUsesBotMovement(player)) if (grandprixinfo.gp && grandprixinfo.masterbots && !K_PlayerUsesBotMovement(player))
return min; return min;
@ -1622,6 +1625,8 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed
// Double speed for the rival! // Double speed for the rival!
if (player->botvars.rival || cv_levelskull.value) if (player->botvars.rival || cv_levelskull.value)
player->draftpower += add; player->draftpower += add;
else if (player->botvars.foe)
player->draftpower += add/2;
else if (dest->player->bot) // Reduce bot gluts. else if (dest->player->bot) // Reduce bot gluts.
player->draftpower -= 3*add/4; player->draftpower -= 3*add/4;
} }
@ -3558,6 +3563,10 @@ static fixed_t K_RingDurationBoost(const player_t *player)
// x2.0 for Rival // x2.0 for Rival
ret *= 2; ret *= 2;
} }
else if (player->botvars.foe)
{
ret = 3 * ret / 2;
}
} }
return ret; return ret;
@ -3980,6 +3989,11 @@ fixed_t K_GetKartSpeed(const player_t *player, boolean doboostpower, boolean dor
// +10% top speed for the rival // +10% top speed for the rival
finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10); finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10);
} }
else if (player->bot && player->botvars.foe)
{
// +5% for foes
finalspeed = FixedMul(finalspeed, 21*FRACUNIT/20);
}
} }
} }

View file

@ -404,17 +404,9 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result)
/*-------------------------------------------------- /*--------------------------------------------------
static fixed_t K_ItemOddsScale(UINT8 playerCount) static fixed_t K_ItemOddsScale(UINT8 playerCount)
A multiplier for odds and distances to scale See header file for description.
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) fixed_t K_ItemOddsScale(UINT8 playerCount)
{ {
const UINT8 basePlayer = 8; // The player count we design most of the game around. const UINT8 basePlayer = 8; // The player count we design most of the game around.
fixed_t playerScaling = 0; fixed_t playerScaling = 0;
@ -467,7 +459,7 @@ static UINT32 K_UndoMapScaling(UINT32 distance)
} }
/*-------------------------------------------------- /*--------------------------------------------------
static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers)
Adjust item distance for lobby-size scaling Adjust item distance for lobby-size scaling
as well as Frantic Items. as well as Frantic Items.
@ -480,10 +472,8 @@ static UINT32 K_UndoMapScaling(UINT32 distance)
Return:- Return:-
New distance after scaling. New distance after scaling.
--------------------------------------------------*/ --------------------------------------------------*/
static UINT32 K_ScaleItemDistance(const player_t *player, UINT32 distance, UINT8 numPlayers) UINT32 K_ScaleItemDistance(INT32 distance, UINT8 numPlayers)
{ {
(void)player;
#if 0 #if 0
if (franticitems == true) if (franticitems == true)
{ {
@ -498,24 +488,13 @@ static UINT32 K_ScaleItemDistance(const player_t *player, UINT32 distance, UINT8
FRACUNIT + (K_ItemOddsScale(numPlayers) / 2) FRACUNIT + (K_ItemOddsScale(numPlayers) / 2)
); );
// Distance is reduced based on the player's gradingfactor
// distance = FixedMul(distance, player->gradingfactor);
return distance; return distance;
} }
/*-------------------------------------------------- /*--------------------------------------------------
static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers) static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers)
Gets a player's distance used for the item See header file for description.
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.
--------------------------------------------------*/ --------------------------------------------------*/
UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers) UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers)
{ {
@ -567,7 +546,7 @@ UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers)
} }
pdis = K_UndoMapScaling(pdis); pdis = K_UndoMapScaling(pdis);
pdis = K_ScaleItemDistance(player, pdis, numPlayers); pdis = K_ScaleItemDistance(pdis, numPlayers);
if (player->bot && (player->botvars.rival || cv_levelskull.value)) if (player->bot && (player->botvars.rival || cv_levelskull.value))
{ {
@ -579,19 +558,11 @@ UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers)
} }
/*-------------------------------------------------- /*--------------------------------------------------
static boolean K_DenyShieldOdds(kartitems_t item) boolean K_DenyShieldOdds(kartitems_t item)
Checks if this type of shield already exists in See header file for description.
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) boolean K_DenyShieldOdds(kartitems_t item)
{ {
const INT32 shieldType = K_GetShieldFromItem(item); const INT32 shieldType = K_GetShieldFromItem(item);
size_t i; size_t i;
@ -704,18 +675,9 @@ INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item)
/*-------------------------------------------------- /*--------------------------------------------------
static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette) static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette)
Determines special conditions where we want See header file for description.
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) boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette)
{ {
if (K_ItemEnabled(KITEM_SPB) == false) if (K_ItemEnabled(KITEM_SPB) == false)
{ {
@ -778,23 +740,23 @@ static void K_InitRoulette(itemroulette_t *const roulette)
size_t i; size_t i;
#ifndef ITEM_LIST_SIZE #ifndef ITEM_LIST_SIZE
if (roulette->itemList == NULL) if (roulette->itemList.items == NULL)
{ {
roulette->itemListCap = 8; roulette->itemList.cap = 32;
roulette->itemList = Z_Calloc( roulette->itemList.items = Z_Calloc(
sizeof(SINT8) * roulette->itemListCap, sizeof(SINT8) * roulette->itemList.cap,
PU_STATIC, PU_STATIC,
&roulette->itemList NULL
); );
if (roulette->itemList == NULL) if (roulette->itemList.items == NULL)
{ {
I_Error("Not enough memory for item roulette list\n"); I_Error("Not enough memory for item roulette list\n");
} }
} }
#endif #endif
roulette->itemListLen = 0; roulette->itemList.len = 0;
roulette->index = 0; roulette->index = 0;
roulette->baseDist = roulette->dist = 0; roulette->baseDist = roulette->dist = 0;
@ -860,74 +822,65 @@ static void K_InitRoulette(itemroulette_t *const roulette)
&& roulette->secondDist > roulette->firstDist) && roulette->secondDist > roulette->firstDist)
{ {
roulette->secondToFirst = roulette->secondDist - roulette->firstDist; roulette->secondToFirst = roulette->secondDist - roulette->firstDist;
roulette->secondToFirst = K_ScaleItemDistance(&players[i], roulette->secondToFirst, 16 - roulette->playing); // Reversed scaling roulette->secondToFirst = K_ScaleItemDistance(roulette->secondToFirst, 16 - roulette->playing); // Reversed scaling
} }
} }
/*-------------------------------------------------- /*--------------------------------------------------
static void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item) void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item)
Pushes a new item to the end of the item See header file for description.
roulette's item list. Also accepts slot machine
values instead of items.
Input Arguments:-
roulette - The item roulette data to modify.
item - The item / slot machine index to push to the list.
Return:-
N/A
--------------------------------------------------*/ --------------------------------------------------*/
static void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item) void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item)
{ {
#ifdef ITEM_LIST_SIZE #ifdef ITEM_LIST_SIZE
if (roulette->itemListLen >= ITEM_LIST_SIZE) if (roulette->itemList.len >= ITEM_LIST_SIZE)
{ {
I_Error("Out of space for item reel! Go and make ITEM_LIST_SIZE bigger I guess?\n"); I_Error("Out of space for item reel! Go and make ITEM_LIST_SIZE bigger I guess?\n");
return; return;
} }
#else #else
I_Assert(roulette->itemList != NULL); I_Assert(roulette->itemList.items != NULL);
if (roulette->itemListLen >= roulette->itemListCap) if (!roulette->ringbox && item >= NUMKARTRESULTS)
{ {
roulette->itemListCap *= 2; CONS_Alert(CONS_WARNING, M_GetText("Item Roulette rejected an out-of-range item.\n"));
roulette->itemList = Z_Realloc( return;
roulette->itemList, }
sizeof(SINT8) * roulette->itemListCap,
if (roulette->ringbox && item >= KSM__MAX)
{
CONS_Alert(CONS_WARNING, M_GetText("Casino Roulette rejected an out-of-range item.\n"));
return;
}
if (roulette->itemList.len >= roulette->itemList.cap)
{
roulette->itemList.cap *= 2;
roulette->itemList.items = Z_Realloc(
roulette->itemList.items,
sizeof(SINT8) * roulette->itemList.cap,
PU_STATIC, PU_STATIC,
&roulette->itemList NULL
); );
if (roulette->itemList == NULL) if (roulette->itemList.items == NULL)
{ {
I_Error("Not enough memory for item roulette list\n"); I_Error("Not enough memory for item roulette list\n");
} }
} }
#endif #endif
roulette->itemList[ roulette->itemListLen ] = item; roulette->itemList.items[ roulette->itemList.len ] = item;
roulette->itemListLen++; roulette->itemList.len++;
} }
/*-------------------------------------------------- /*--------------------------------------------------
static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item) void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item)
Adds an item to a player's item reel. Unlike See header file for description.
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) void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item)
{ {
if (player && K_PlayerUsesBotMovement(player) && !K_BotUnderstandsItem(item)) if (player && K_PlayerUsesBotMovement(player) && !K_BotUnderstandsItem(item))
return; return;
@ -951,19 +904,11 @@ static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulet
} }
/*-------------------------------------------------- /*--------------------------------------------------
static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) void K_CalculateRouletteSpeed(itemroulette_t *const roulette)
Determines the speed for the item roulette, See header file for description.
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) void K_CalculateRouletteSpeed(itemroulette_t *const roulette)
{ {
fixed_t frontRun = 0; fixed_t frontRun = 0;
fixed_t progress = 0; fixed_t progress = 0;
@ -1244,7 +1189,7 @@ static boolean K_TimingPermitsItem(kartitems_t item, const itemroulette_t *roule
static void K_FixEmptyRoulette(const player_t *player, itemroulette_t *const roulette) static void K_FixEmptyRoulette(const player_t *player, itemroulette_t *const roulette)
{ {
if (roulette->itemListLen > 0) if (roulette->itemList.len > 0)
return; return;
if (K_PlayerUsesBotMovement(player)) // Bots can't use certain items. Give them _something_. if (K_PlayerUsesBotMovement(player)) // Bots can't use certain items. Give them _something_.
@ -1254,21 +1199,12 @@ static void K_FixEmptyRoulette(const player_t *player, itemroulette_t *const rou
} }
/*-------------------------------------------------- /*--------------------------------------------------
void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun) void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox)
See header file for description. See header file for description.
--------------------------------------------------*/ --------------------------------------------------*/
void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun) void K_FillItemRoulette(player_t *const player, itemroulette_t *const roulette, boolean ringbox)
{ {
UINT32 spawnChance[NUMKARTRESULTS] = {0};
UINT32 totalSpawnChance = 0;
size_t rngRoll = 0;
UINT8 numItems = 0;
kartitems_t singleItem = KITEM_SAD;
size_t i, j;
K_InitRoulette(roulette); K_InitRoulette(roulette);
if (player != NULL) if (player != NULL)
@ -1281,6 +1217,49 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
K_CalculateRouletteSpeed(roulette); K_CalculateRouletteSpeed(roulette);
} }
// Lua may want to intercept reelbuilder entirely.
LUA_HookPreFillItemRoulette(player, roulette, ringbox);
// If prehook did something, no need to continue.
if (roulette->itemList.len != 0) {
return;
}
K_FillItemRouletteData(player, roulette, ringbox, false);
// Lua can modify the final result.
LUA_HookFillItemRoulette(player, roulette, ringbox);
// If somehow there's no items, add sad.
if (roulette->itemList.len == 0) {
if (roulette->ringbox)
K_PushToRouletteItemList(roulette, KSM_BAR);
else
K_AddItemToReel(player, roulette, KITEM_SAD);
}
}
/*--------------------------------------------------
void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun)
See header file for description.
--------------------------------------------------*/
void K_FillItemRouletteData(player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun)
{
UINT32 spawnChance[NUMKARTRESULTS] = {0};
UINT32 totalSpawnChance = 0;
size_t rngRoll = 0;
UINT8 numItems = 0;
kartitems_t singleItem = KITEM_SAD;
size_t i, j;
if (roulette->itemList.items == NULL)
{
K_InitRoulette(roulette);
}
if (ringbox == true) if (ringbox == true)
{ {
// If this is being invoked by a Ring Box, it should literally never produce items. // If this is being invoked by a Ring Box, it should literally never produce items.
@ -1313,6 +1292,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
{ {
K_PushToRouletteItemList(roulette, K_KartItemReelSpecialEnd[i]); K_PushToRouletteItemList(roulette, K_KartItemReelSpecialEnd[i]);
} }
return; return;
} }
} }
@ -1792,10 +1772,10 @@ void K_StartItemRoulette(player_t *const player, boolean ringbox)
itemroulette_t *const roulette = &player->itemRoulette; itemroulette_t *const roulette = &player->itemRoulette;
size_t i; size_t i;
K_FillItemRouletteData(player, roulette, ringbox, false); K_FillItemRoulette(player, roulette, ringbox);
if (roulette->autoroulette) if (roulette->autoroulette)
roulette->index = P_RandomRange(PR_AUTOROULETTE, 0, roulette->itemListLen - 1); roulette->index = P_RandomRange(PR_AUTOROULETTE, 0, roulette->itemList.len - 1);
if (K_PlayerUsesBotMovement(player) == true) if (K_PlayerUsesBotMovement(player) == true)
{ {
@ -1804,9 +1784,9 @@ void K_StartItemRoulette(player_t *const player, boolean ringbox)
// Prevent further duplicates of items that // Prevent further duplicates of items that
// are intended to only have one out at a time. // are intended to only have one out at a time.
for (i = 0; i < roulette->itemListLen; i++) for (i = 0; i < roulette->itemList.len; i++)
{ {
kartitems_t item = roulette->itemList[i]; kartitems_t item = roulette->itemList.items[i];
if (K_ItemSingularity(item) == true) if (K_ItemSingularity(item) == true)
{ {
K_SetItemCooldown(item, TICRATE<<4); K_SetItemCooldown(item, TICRATE<<4);
@ -1880,19 +1860,11 @@ fixed_t K_GetSlotOffset(itemroulette_t *const roulette, fixed_t renderDelta, UIN
} }
/*-------------------------------------------------- /*--------------------------------------------------
static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) void K_KartGetItemResult(player_t *const player, kartitems_t getitem)
Initializes a player's item to what was See header file for description.
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) void K_KartGetItemResult(player_t *const player, kartitems_t getitem)
{ {
if (K_ItemSingularity(getitem) == true) if (K_ItemSingularity(getitem) == true)
{ {
@ -1935,9 +1907,9 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
return; return;
} }
if (roulette->itemListLen == 0 if (roulette->itemList.len == 0
#ifndef ITEM_LIST_SIZE #ifndef ITEM_LIST_SIZE
|| roulette->itemList == NULL || roulette->itemList.items == NULL
#endif #endif
) )
{ {
@ -2000,7 +1972,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
if (fudgedDelay > gap) // Did the roulette tick over in-flight? 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. 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->index = roulette->index == 0 ? roulette->itemList.len - 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... roulette->tics = 0; // And just in case our delay is SO high that a fast roulette needs to roll back again...
} }
else else
@ -2012,7 +1984,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
// And one more nudge for the remaining delay. // And one more nudge for the remaining delay.
roulette->tics = (roulette->tics + fudgedDelay) % roulette->speed; roulette->tics = (roulette->tics + fudgedDelay) % roulette->speed;
INT32 finalItem = roulette->itemList[ roulette->index ]; INT32 finalItem = roulette->itemList.items[ roulette->index ];
if (roulette->ringbox == true) if (roulette->ringbox == true)
{ {
@ -2057,7 +2029,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
if (roulette->tics == 0) if (roulette->tics == 0)
{ {
roulette->index = (roulette->index + 1) % roulette->itemListLen; roulette->index = (roulette->index + 1) % roulette->itemList.len;
roulette->tics = roulette->speed; roulette->tics = roulette->speed;
// This makes the roulette produce the random noises. // This makes the roulette produce the random noises.
@ -2070,7 +2042,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
else else
S_StartSound(NULL, sfx_itrol1 + roulette->sound); S_StartSound(NULL, sfx_itrol1 + roulette->sound);
if (roulette->index == 0 && roulette->itemListLen > 1) if (roulette->index == 0 && roulette->itemList.len > 1)
{ {
S_StartSound(NULL, sfx_kc50); S_StartSound(NULL, sfx_kc50);
S_StartSound(NULL, sfx_kc50); S_StartSound(NULL, sfx_kc50);

View file

@ -76,6 +76,90 @@ boolean K_ItemSingularity(kartitems_t item);
botItemPriority_e K_GetBotItemPriority(kartitems_t result); botItemPriority_e K_GetBotItemPriority(kartitems_t result);
/*--------------------------------------------------
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.
--------------------------------------------------*/
fixed_t K_ItemOddsScale(UINT8 playerCount);
/*--------------------------------------------------
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.
--------------------------------------------------*/
UINT32 K_ScaleItemDistance(INT32 distance, UINT8 numPlayers);
/*--------------------------------------------------
void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item)
Pushes a new item to the end of the item
roulette's item list. Also accepts slot machine
values instead of items.
Input Arguments:-
roulette - The item roulette data to modify.
item - The item / slot machine index to push to the list.
Return:-
N/A
--------------------------------------------------*/
void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item);
/*--------------------------------------------------
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
--------------------------------------------------*/
void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item);
/*--------------------------------------------------
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
--------------------------------------------------*/
void K_CalculateRouletteSpeed(itemroulette_t *const roulette);
/*-------------------------------------------------- /*--------------------------------------------------
INT32 K_KartGetBattleOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); INT32 K_KartGetBattleOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item);
@ -96,9 +180,27 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result);
INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item); INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item);
/*--------------------------------------------------
void K_FillItemRoulette(player_t *player, itemroulette_t *const roulette, boolean ringbox);
Entry point for roulette builder.
Includes Lua hooks.
Input Arguments:-
player - The player this roulette data is for.
Can be NULL for generic use.
roulette - The roulette data struct to fill out.
ringbox - Is this roulette fill triggered by a just-respawned Ring Box?
Return:-
N/A
--------------------------------------------------*/
void K_FillItemRoulette(player_t *player, itemroulette_t *const roulette, boolean ringbox);
/*-------------------------------------------------- /*--------------------------------------------------
void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun); void K_FillItemRouletteData(player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun);
Fills out the item roulette struct when it is Fills out the item roulette struct when it is
initially created. This function needs to be initially created. This function needs to be
@ -116,7 +218,7 @@ INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item);
N/A N/A
--------------------------------------------------*/ --------------------------------------------------*/
void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun); void K_FillItemRouletteData(player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun);
/*-------------------------------------------------- /*--------------------------------------------------
@ -224,8 +326,57 @@ fixed_t K_GetSlotOffset(itemroulette_t *const roulette, fixed_t renderDelta, UIN
void K_KartItemRoulette(player_t *const player, ticcmd_t *cmd); void K_KartItemRoulette(player_t *const player, ticcmd_t *cmd);
void K_KartGetItemResult(player_t *const player, kartitems_t getitem);
/*--------------------------------------------------
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.
--------------------------------------------------*/
UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers); UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers);
/*--------------------------------------------------
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.
--------------------------------------------------*/
boolean K_DenyShieldOdds(kartitems_t item);
/*--------------------------------------------------
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.
--------------------------------------------------*/
boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -32,6 +32,7 @@
#include "k_collide.h" #include "k_collide.h"
#include "k_color.h" #include "k_color.h"
#include "k_hud.h" #include "k_hud.h"
#include "k_grandprix.h"
#include "d_netcmd.h" // IsPlayerAdmin #include "d_netcmd.h" // IsPlayerAdmin
#include "k_menu.h" // Player Setup menu color stuff #include "k_menu.h" // Player Setup menu color stuff
#include "p_spec.h" // P_StartQuake #include "p_spec.h" // P_StartQuake
@ -46,6 +47,7 @@
#include "lua_hud.h" // hud_running errors #include "lua_hud.h" // hud_running errors
#include "taglist.h" // P_FindSpecialLineFromTag #include "taglist.h" // P_FindSpecialLineFromTag
#include "lua_hook.h" // hook_cmd_running errors #include "lua_hook.h" // hook_cmd_running errors
#include "k_roulette.h"
#define NOHUD if (hud_running)\ #define NOHUD if (hud_running)\
return luaL_error(L, "HUD rendering code should not call this function!");\ return luaL_error(L, "HUD rendering code should not call this function!");\
@ -232,6 +234,8 @@ static const struct {
{META_ACTIVATOR, "activator_t"}, {META_ACTIVATOR, "activator_t"},
{META_FOLLOWER, "follower_t"}, {META_FOLLOWER, "follower_t"},
{META_ITEMROULETTE, "itemroulette_t"},
{META_ITEMROULETTE_ITEMLIST, "itemroulette_t.itemlist_t"},
{NULL, NULL} {NULL, NULL}
}; };
@ -3920,6 +3924,68 @@ static int lib_kGetItemPatch(lua_State *L)
return 1; return 1;
} }
static int lib_kGetShieldFromItem(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
//HUDSAFE
lua_pushinteger(L, K_GetShieldFromItem(item));
return 1;
}
static int lib_kItemResultToType(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
//HUDSAFE
lua_pushinteger(L, K_ItemResultToType(item));
return 1;
}
static int lib_kItemResultToAmount(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
//HUDSAFE
lua_pushinteger(L, K_ItemResultToAmount(item, NULL));
return 1;
}
static int lib_kGetItemCooldown(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
NOHUD
INLEVEL
lua_pushinteger(L, K_GetItemCooldown(item));
return 1;
}
static int lib_kSetItemCooldown(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
tic_t time = luaL_checkinteger(L, 2);
NOHUD
INLEVEL
K_SetItemCooldown(item, time);
return 0;
}
static int lib_kTimeAttackRules(lua_State *L)
{
//HUDSAFE
lua_pushboolean(L, K_TimeAttackRules());
return 1;
}
static int lib_kCapsuleTimeAttackRules(lua_State *L)
{
//HUDSAFE
lua_pushboolean(L, K_CapsuleTimeAttackRules());
return 1;
}
static int lib_kGetCollideAngle(lua_State *L) static int lib_kGetCollideAngle(lua_State *L)
{ {
mobj_t *t1 = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); mobj_t *t1 = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@ -4014,6 +4080,12 @@ static int lib_kDeclareWeakspot(lua_State *L)
return 0; return 0;
} }
static int lib_kCheckBossIntro(lua_State *L)
{
lua_pushboolean(L, K_CheckBossIntro());
return 1;
}
static int lib_vsGetArena(lua_State *L) static int lib_vsGetArena(lua_State *L)
{ {
INT32 bossindex = luaL_checkinteger(L, 1); INT32 bossindex = luaL_checkinteger(L, 1);
@ -4057,6 +4129,419 @@ static int lib_vsRandomPointOnArena(lua_State *L)
return 2; return 2;
} }
static int lib_kItemEnabled(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
lua_pushboolean(L, K_ItemEnabled(item));
return 1;
}
static int lib_kItemSingularity(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
lua_pushboolean(L, K_ItemSingularity(item));
return 1;
}
static int lib_kGetBotItemPriority(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
lua_pushinteger(L, K_GetBotItemPriority(item));
return 1;
}
static int lib_kAddItemToReel(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
itemroulette_t *itemRoulette = NULL;
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
itemRoulette = &player->itemRoulette;
if (lua_isnumber(L, 2))
{
kartitems_t item = luaL_checkinteger(L, 2);
K_AddItemToReel(player, itemRoulette, item);
}
else if (lua_istable(L, 2))
{
luaL_checktype(L, 2, LUA_TTABLE);
size_t size = luaL_getn(L, 2);
for (size_t i = 1; i <= size; i++)
{
lua_rawgeti(L, 2, i);
if (lua_isnumber(L, -1))
{
kartitems_t item = luaL_checkinteger(L, -1);
K_AddItemToReel(player, itemRoulette, item);
}
else // Quit early, let the scripter know they messed up.
{
lua_pop(L, 1);
return luaL_error(L, "Non-integer value in table in index %d.", i);
}
lua_pop(L, 1);
}
}
else return LUA_ErrInvalid(L, "integer/table");
return 0;
}
static int lib_kPushToRouletteItemList(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
itemroulette_t *itemRoulette = NULL;
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
itemRoulette = &player->itemRoulette;
if (lua_isnumber(L, 2))
{
kartitems_t item = luaL_checkinteger(L, 2);
K_PushToRouletteItemList(itemRoulette, item);
}
else if (lua_istable(L, 2))
{
luaL_checktype(L, 2, LUA_TTABLE);
size_t size = luaL_getn(L, 2);
for (size_t i = 1; i <= size; i++)
{
lua_rawgeti(L, 2, i);
if (lua_isnumber(L, -1))
{
kartitems_t item = luaL_checkinteger(L, -1);
K_PushToRouletteItemList(itemRoulette, item);
}
else // Quit early, let the scripter know they messed up.
{
lua_pop(L, 1);
return luaL_error(L, "Non-integer value in table in index %d.", i);
}
lua_pop(L, 1);
}
}
else return LUA_ErrInvalid(L, "integer/table");
return 0;
}
static int lib_kStartItemRoulette(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
boolean ringbox = lua_optboolean(L, 2);
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
K_StartItemRoulette(player, ringbox);
return 0;
}
static int lib_kStartEggmanRoulette(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
K_StartEggmanRoulette(player);
return 0;
}
static int lib_kStopRoulette(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
K_StopRoulette(&player->itemRoulette);
return 0;
}
static int lib_kKartGetItemResult(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
kartitems_t item = luaL_checkinteger(L, 2);
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
K_KartGetItemResult(player, item);
return 0;
}
static int lib_kGetItemRouletteDistance(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
UINT8 numPlayers = luaL_checkinteger(L, 2);
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushinteger(L, K_GetItemRouletteDistance(player, numPlayers));
return 1;
}
static int lib_kFillItemRouletteData(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
itemroulette_t *itemRoulette = NULL;
boolean ringbox = lua_optboolean(L, 2);
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
itemRoulette = &player->itemRoulette;
K_FillItemRouletteData(player, itemRoulette, ringbox, false);
return 0;
}
static int lib_kForcedSPB(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushboolean(L, K_ForcedSPB(player, &player->itemRoulette));
return 1;
}
static int lib_kDenyShieldOdds(lua_State *L)
{
kartitems_t item = luaL_checkinteger(L, 1);
INLEVEL
lua_pushboolean(L, K_DenyShieldOdds(item));
return 1;
}
static int lib_kGetRouletteOffset(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
fixed_t renderDelta = luaL_optnumber(L, 2, FRACUNIT);
UINT8 fudge = luaL_optnumber(L, 3, player ? player->cmd.latency : 0);
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushfixed(L, K_GetRouletteOffset(&player->itemRoulette, renderDelta, fudge));
return 1;
}
static int lib_kGetSlotOffset(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
fixed_t renderDelta = luaL_optnumber(L, 2, FRACUNIT);
UINT8 fudge = luaL_optnumber(L, 3, player ? player->cmd.latency : 0);
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushfixed(L, K_GetSlotOffset(&player->itemRoulette, renderDelta, fudge));
return 1;
}
static int lib_kCalculateRouletteSpeed(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
K_CalculateRouletteSpeed(&player->itemRoulette);
return 0;
}
static int lib_kScaleItemDistance(lua_State *L)
{
UINT32 distance = luaL_checkinteger(L, 1);
UINT8 numPlayers = luaL_checkinteger(L, 2);
lua_pushfixed(L, K_ScaleItemDistance(distance, numPlayers));
return 1;
}
static int lib_kItemOddsScale(lua_State *L)
{
UINT8 playerCount = luaL_checkinteger(L, 1);
lua_pushfixed(L, K_ItemOddsScale(playerCount));
return 1;
}
static int lib_kWipeItemsInReel(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
itemroulette_t *itemRoulette = NULL;
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
itemRoulette = &player->itemRoulette;
itemRoulette->itemList.len = 0;
return 0;
}
static int lib_kSetItemInReelByIndex(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
itemroulette_t *itemRoulette = NULL;
size_t index = luaL_checkinteger(L, 2) - 1;
kartitems_t item = luaL_checkinteger(L, 3);
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
itemRoulette = &player->itemRoulette;
if (itemRoulette->itemList.len == 0)
return luaL_error(L, "There are no items in the roulette to set.");
if (index > itemRoulette->itemList.len - 1)
return luaL_error(L, "Roulette index %d out of bounds (should be between %d and %d).", index + 1, 1, itemRoulette->itemList.len);
itemRoulette->itemList.items[index] = item;
return 0;
}
static void AddOrPushToItemReel(player_t *player, itemroulette_t *roulette, kartitems_t item, boolean addRings)
{
if (addRings)
K_AddItemToReel(player, roulette, item);
else
K_PushToRouletteItemList(roulette, item);
}
static int lib_kAddItemToReelByIndex(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
itemroulette_t *itemRoulette = NULL;
size_t index = luaL_checkinteger(L, 2) - 1;
kartitems_t item = luaL_checkinteger(L, 3);
boolean addRings = lua_optboolean(L, 4);
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
itemRoulette = &player->itemRoulette;
// If the list is empty, just add the item silently and leave.
if (itemRoulette->itemList.len == 0) {
AddOrPushToItemReel(player, itemRoulette, item, addRings);
return 0;
}
if (index > itemRoulette->itemList.len - 1)
return luaL_error(L, "Roulette index %d out of bounds (should be between %d and %d).", index + 1, 1, itemRoulette->itemList.len);
size_t latterItemList = itemRoulette->itemList.len - index;
kartitems_t *latterItems = Z_Calloc(
sizeof(kartitems_t) * latterItemList,
PU_STATIC,
NULL
);
if (!latterItems)
I_Error("Out of memory during calloc for lib_kAddItemToReelByIndex.");
for (size_t i = 0; i < latterItemList; i++)
{
latterItems[i] = itemRoulette->itemList.items[index + i];
}
itemRoulette->itemList.len = index;
AddOrPushToItemReel(player, itemRoulette, item, addRings);
for (size_t i = 0; i < latterItemList; i++)
{
AddOrPushToItemReel(player, itemRoulette, latterItems[i], addRings);
}
Z_Free(latterItems);
return 0;
}
static int lib_kRemoveItemFromReelByIndex(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
itemroulette_t *itemRoulette = NULL;
size_t index = luaL_checkinteger(L, 2) - 1;
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
itemRoulette = &player->itemRoulette;
if (itemRoulette->itemList.len == 0)
return luaL_error(L, "There are no items in the roulette to delete.");
if (index > itemRoulette->itemList.len - 1)
return luaL_error(L, "Roulette index %d out of bounds (should be between %d and %d).", index + 1, 1, itemRoulette->itemList.len);
size_t latterItemList = itemRoulette->itemList.len - index - 1;
kartitems_t *latterItems = Z_Calloc(
sizeof(kartitems_t) * latterItemList,
PU_STATIC,
NULL
);
if (!latterItems)
I_Error("Out of memory during calloc for lib_kRemoveItemFromReelByIndex.");
for (size_t i = 0; i < latterItemList; i++)
{
latterItems[i] = itemRoulette->itemList.items[index + 1 + i];
}
itemRoulette->itemList.len = index;
for (size_t i = 0; i < latterItemList; i++)
K_PushToRouletteItemList(itemRoulette, latterItems[i]);
Z_Free(latterItems);
return 0;
}
static int lib_kCanChangeRules(lua_State *L)
{
lua_pushboolean(L, K_CanChangeRules(true));
return 1;
}
static int lib_getTimeMicros(lua_State *L) static int lib_getTimeMicros(lua_State *L)
{ {
lua_pushinteger(L, I_GetPreciseTime() / (I_GetPrecisePrecision() / 1000000)); lua_pushinteger(L, I_GetPreciseTime() / (I_GetPrecisePrecision() / 1000000));
@ -4326,9 +4811,15 @@ static luaL_Reg lib[] = {
{"K_GetKartAccel",lib_kGetKartAccel}, {"K_GetKartAccel",lib_kGetKartAccel},
{"K_GetKartFlashing",lib_kGetKartFlashing}, {"K_GetKartFlashing",lib_kGetKartFlashing},
{"K_GetItemPatch",lib_kGetItemPatch}, {"K_GetItemPatch",lib_kGetItemPatch},
{"K_GetCollideAngle",lib_kGetCollideAngle}, {"K_GetCollideAngle",lib_kGetCollideAngle},
{"K_AddHitLag",lib_kAddHitLag}, {"K_AddHitLag",lib_kAddHitLag},
{"K_GetShieldFromItem",lib_kGetShieldFromItem},
{"K_ItemResultToType",lib_kItemResultToType},
{"K_ItemResultToAmount",lib_kItemResultToAmount},
{"K_GetItemCooldown",lib_kGetItemCooldown},
{"K_SetItemCooldown",lib_kSetItemCooldown},
{"K_TimeAttackRules",lib_kTimeAttackRules},
{"K_CapsuleTimeAttackRules",lib_kCapsuleTimeAttackRules},
// k_powerup // k_powerup
{"K_PowerUpRemaining",lib_kPowerUpRemaining}, {"K_PowerUpRemaining",lib_kPowerUpRemaining},
@ -4339,9 +4830,39 @@ static luaL_Reg lib[] = {
{"K_InitBossHealthBar", lib_kInitBossHealthBar}, {"K_InitBossHealthBar", lib_kInitBossHealthBar},
{"K_UpdateBossHealthBar", lib_kUpdateBossHealthBar}, {"K_UpdateBossHealthBar", lib_kUpdateBossHealthBar},
{"K_DeclareWeakspot", lib_kDeclareWeakspot}, {"K_DeclareWeakspot", lib_kDeclareWeakspot},
{"K_CheckBossIntro", lib_kCheckBossIntro},
{"VS_GetArena", lib_vsGetArena}, {"VS_GetArena", lib_vsGetArena},
{"VS_PredictAroundArena", lib_vsPredictAroundArena}, {"VS_PredictAroundArena", lib_vsPredictAroundArena},
{"VS_RandomPointOnArena", lib_vsRandomPointOnArena}, {"VS_RandomPointOnArena", lib_vsRandomPointOnArena},
// k_roulette
{"K_ItemEnabled", lib_kItemEnabled},
{"K_ItemSingularity", lib_kItemSingularity},
{"K_GetBotItemPriority", lib_kGetBotItemPriority},
{"K_AddItemToReel", lib_kAddItemToReel},
{"K_PushToRouletteItemList", lib_kPushToRouletteItemList},
{"K_StartItemRoulette", lib_kStartItemRoulette},
{"K_StartEggmanRoulette", lib_kStartEggmanRoulette},
{"K_StopRoulette", lib_kStopRoulette},
{"K_KartGetItemResult", lib_kKartGetItemResult},
{"K_GetItemRouletteDistance", lib_kGetItemRouletteDistance},
{"K_FillItemRouletteData", lib_kFillItemRouletteData},
{"K_ForcedSPB", lib_kForcedSPB},
{"K_DenyShieldOdds", lib_kDenyShieldOdds},
{"K_GetRouletteOffset", lib_kGetRouletteOffset},
{"K_GetSlotOffset", lib_kGetSlotOffset},
{"K_CalculateRouletteSpeed", lib_kCalculateRouletteSpeed},
{"K_ScaleItemDistance", lib_kScaleItemDistance},
{"K_ItemOddsScale", lib_kItemOddsScale},
// These are not real functions in k_roulette, but they allow
// encapsulation on how the scripter interacts with the item reel.
{"K_WipeItemsInReel", lib_kWipeItemsInReel},
{"K_SetItemInReelByIndex", lib_kSetItemInReelByIndex},
{"K_AddItemToReelByIndex", lib_kAddItemToReelByIndex},
{"K_RemoveItemFromReelByIndex", lib_kRemoveItemFromReelByIndex},
// k_grandprix
{"K_CanChangeRules", lib_kCanChangeRules},
// hu_stuff technically? // hu_stuff technically?
{"HU_DoTitlecardCEcho", lib_startTitlecardCecho}, {"HU_DoTitlecardCEcho", lib_startTitlecardCecho},

View file

@ -79,6 +79,8 @@ automatically.
X (GameQuit),\ X (GameQuit),\
X (PlayerCmd),/* building the player's ticcmd struct */\ X (PlayerCmd),/* building the player's ticcmd struct */\
X (VoteThinker),/* Y_VoteTicker */\ X (VoteThinker),/* Y_VoteTicker */\
X (PreFillItemRoulette),/* K_FillItemRouletteData, before attempted reel build */\
X (FillItemRoulette),/* K_FillItemRouletteData, after built reel is in place */\
#define STRING_HOOK_LIST(X) \ #define STRING_HOOK_LIST(X) \
X (SpecialExecute),\ X (SpecialExecute),\
@ -146,6 +148,8 @@ void LUA_HookPlayerQuit(player_t *, kickreason_t);
//int LUA_HookTeamSwitch(player_t *, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); //int LUA_HookTeamSwitch(player_t *, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble);
int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced);
int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend); int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend);
int LUA_HookPreFillItemRoulette(player_t *player, itemroulette_t *const roulette, boolean ringbox);
int LUA_HookFillItemRoulette(player_t *player, itemroulette_t *const roulette, boolean ringbox);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View file

@ -24,6 +24,7 @@
#include "lua_hook.h" #include "lua_hook.h"
#include "lua_hud.h" // hud_running errors #include "lua_hud.h" // hud_running errors
#include "lua_profile.h" #include "lua_profile.h"
#include "lua_playerlib.h" // constplayer
#include "command.h" #include "command.h"
#include "m_perfstats.h" #include "m_perfstats.h"
@ -1009,4 +1010,39 @@ int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend)
return hook.status; return hook.status;
} }
static int roulette_hook(
player_t *player,
itemroulette_t *const roulette,
boolean ringbox,
int hook_type,
Hook_Callback results_handler)
{
Hook_State hook;
if (prepare_hook(&hook, false, hook_type))
{
if (player == NULL) {
lua_pushnil(gL);
} else {
LUA_PushUserdata(gL, player, META_PLAYER);
}
LUA_PushUserdata(gL, roulette, META_ITEMROULETTE);
lua_pushboolean(gL, ringbox);
constplayer = true; // Do not allow players to be modified.
call_hooks(&hook, 1, results_handler);
constplayer = false; // You're good.
}
return hook.status;
}
int LUA_HookPreFillItemRoulette(player_t *player, itemroulette_t *const roulette, boolean ringbox)
{
return roulette_hook(player, roulette, ringbox, HOOK(PreFillItemRoulette), res_true);
}
int LUA_HookFillItemRoulette(player_t *player, itemroulette_t *const roulette, boolean ringbox)
{
return roulette_hook(player, roulette, ringbox, HOOK(FillItemRoulette), res_true);
}
boolean hook_cmd_running = false; boolean hook_cmd_running = false;

276
src/lua_itemroulettelib.c Normal file
View file

@ -0,0 +1,276 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by Kart Krew.
// Copyright (C) 2020 by Sonic Team Junior.
// Copyright (C) 2016 by John "JTE" Muniz.
//
// 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 lua_itemroulettelib.c
/// \brief player item roulette structure library for Lua scripting
#include "doomdef.h"
#include "fastcmp.h"
#include "d_player.h"
#include "z_zone.h"
#include "k_roulette.h"
#include "lua_script.h"
#include "lua_libs.h"
#include "lua_hud.h" // hud_running errors
#include "lua_hook.h" // hook_cmd_running errors
enum itemroulette {
itemroulette_valid = 0,
itemroulette_active,
itemroulette_itemlist,
itemroulette_playing,
itemroulette_exiting,
itemroulette_dist,
itemroulette_basedist,
itemroulette_firstdist,
itemroulette_seconddist,
itemroulette_secondtofirst,
itemroulette_index,
itemroulette_sound,
itemroulette_speed,
itemroulette_tics,
itemroulette_elapsed,
itemroulette_eggman,
itemroulette_ringbox,
itemroulette_autoroulette,
itemroulette_reserved
};
static const char *const itemroulette_opt[] = {
"valid",
"active",
"itemlist",
"playing",
"exiting",
"dist",
"basedist",
"firstdist",
"seconddist",
"secondtofirst",
"index",
"sound",
"speed",
"tics",
"elapsed",
"eggman",
"ringbox",
"autoroulette",
"reserved",
NULL
};
static int itemroulette_get(lua_State *L)
{
itemroulette_t *itemroulette = *((itemroulette_t **)luaL_checkudata(L, 1, META_ITEMROULETTE));
enum itemroulette field = luaL_checkoption(L, 2, itemroulette_opt[0], itemroulette_opt);
// if this is null, welcome to parking garage rally circuit
I_Assert(itemroulette != NULL);
switch (field)
{
case itemroulette_valid:
lua_pushboolean(L, itemroulette != NULL);
break;
case itemroulette_active:
lua_pushboolean(L, itemroulette->active);
break;
case itemroulette_itemlist:
LUA_PushUserdata(L, &itemroulette->itemList, META_ITEMROULETTE_ITEMLIST);
break;
case itemroulette_playing:
lua_pushinteger(L, itemroulette->playing);
break;
case itemroulette_exiting:
lua_pushinteger(L, itemroulette->exiting);
break;
case itemroulette_dist:
lua_pushinteger(L, itemroulette->dist);
break;
case itemroulette_basedist:
lua_pushinteger(L, itemroulette->baseDist);
break;
case itemroulette_firstdist:
lua_pushinteger(L, itemroulette->firstDist);
break;
case itemroulette_seconddist:
lua_pushinteger(L, itemroulette->secondDist);
break;
case itemroulette_secondtofirst:
lua_pushinteger(L, itemroulette->secondToFirst);
break;
case itemroulette_index:
lua_pushinteger(L, itemroulette->index);
break;
case itemroulette_sound:
lua_pushinteger(L, itemroulette->sound);
break;
case itemroulette_speed:
lua_pushinteger(L, itemroulette->speed);
break;
case itemroulette_tics:
lua_pushinteger(L, itemroulette->tics);
break;
case itemroulette_elapsed:
lua_pushinteger(L, itemroulette->elapsed);
break;
case itemroulette_eggman:
lua_pushboolean(L, itemroulette->eggman);
break;
case itemroulette_ringbox:
lua_pushboolean(L, itemroulette->ringbox);
break;
case itemroulette_autoroulette:
lua_pushboolean(L, itemroulette->autoroulette);
break;
case itemroulette_reserved:
lua_pushinteger(L, itemroulette->reserved);
break;
}
return 1;
}
#define NOSET luaL_error(L, LUA_QL("itemroulette_t") " field " LUA_QS " should not be set directly.", itemroulette_opt[field])
#define UNIMPLEMENTED luaL_error(L, LUA_QL("itemroulette_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", itemroulette_opt[field])
#define NOSETITEMLIST luaL_error(L, LUA_QL("itemroulette_t") " field " LUA_QS " should not be set directly. Use " LUA_QL("K_AddItemToReel") ", " LUA_QL("K_PushToRouletteItemList") ", or " LUA_QL("K_StartItemRoulette") ", or " LUA_QL("K_StopRoulette") " instead.", itemroulette_opt[field])
static int itemroulette_set(lua_State *L)
{
itemroulette_t *itemroulette = *((itemroulette_t **)luaL_checkudata(L, 1, META_ITEMROULETTE));
enum itemroulette field = luaL_checkoption(L, 2, itemroulette_opt[0], itemroulette_opt);
// if this is null, welcome to parking garage rally circuit
I_Assert(itemroulette != NULL);
INLEVEL
if (hud_running)
return luaL_error(L, "Do not alter itemroulette_t in HUD rendering code!");
if (hook_cmd_running)
return luaL_error(L, "Do not alter itemroulette_t in CMD building code!");
switch(field)
{
case itemroulette_valid:
return NOSET;
case itemroulette_active:
itemroulette->active = luaL_checkboolean(L, 3);
break;
case itemroulette_itemlist:
return NOSETITEMLIST;
case itemroulette_playing:
itemroulette->playing = luaL_checkinteger(L, 3);
break;
case itemroulette_exiting:
itemroulette->exiting = luaL_checkinteger(L, 3);
break;
case itemroulette_dist:
itemroulette->dist = luaL_checkinteger(L, 3);
break;
case itemroulette_basedist:
itemroulette->baseDist = luaL_checkinteger(L, 3);
break;
case itemroulette_firstdist:
itemroulette->firstDist = luaL_checkinteger(L, 3);
break;
case itemroulette_seconddist:
itemroulette->secondDist = luaL_checkinteger(L, 3);
break;
case itemroulette_secondtofirst:
itemroulette->secondToFirst = luaL_checkinteger(L, 3);
break;
case itemroulette_index:
itemroulette->index = luaL_checkinteger(L, 3);
break;
case itemroulette_sound:
itemroulette->sound = luaL_checkinteger(L, 3);
break;
case itemroulette_speed:
itemroulette->speed = luaL_checkinteger(L, 3);
break;
case itemroulette_tics:
itemroulette->tics = luaL_checkinteger(L, 3);
break;
case itemroulette_elapsed:
itemroulette->elapsed = luaL_checkinteger(L, 3);
break;
case itemroulette_eggman:
itemroulette->eggman = luaL_checkboolean(L, 3);
break;
case itemroulette_ringbox:
itemroulette->ringbox = luaL_checkboolean(L, 3);
break;
case itemroulette_autoroulette:
itemroulette->autoroulette = luaL_checkboolean(L, 3);
break;
case itemroulette_reserved:
itemroulette->reserved = luaL_checkinteger(L, 3);
break;
}
return 0;
}
#undef NOSET
#undef NOSETITEMLIST
#undef UNIMPLEMENTED
// itemlist, i -> itemlist[i]
static int itemrouletteitemlist_get(lua_State *L)
{
itemlist_t *itemlist = *((itemlist_t **)luaL_checkudata(L, 1, META_ITEMROULETTE_ITEMLIST));
size_t index = luaL_checkint(L, 2);
if (!itemlist)
return LUA_ErrInvalid(L, "itemroulette_t.itemlist_t");
if (index == 0 || index > itemlist->len) {
return luaL_error(L, LUA_QL("itemroulette_t.itemlist_t") " index cannot be %d", index);
}
lua_pushinteger(L, itemlist->items[index-1]);
return 1;
}
static int itemrouletteitemlist_set(lua_State *L)
{
return luaL_error(L, LUA_QL("itemroulette_t.itemlist_t") " struct cannot be edited by Lua.");
}
// #itemlist -> itemList.len
static int itemrouletteitemlist_len(lua_State* L)
{
itemlist_t *itemlist = *((itemlist_t **)luaL_checkudata(L, 1, META_ITEMROULETTE_ITEMLIST));
lua_pushinteger(L, itemlist->len);
return 1;
}
int LUA_ItemRouletteLib(lua_State *L)
{
luaL_newmetatable(L, META_ITEMROULETTE);
lua_pushcfunction(L, itemroulette_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, itemroulette_set);
lua_setfield(L, -2, "__newindex");
lua_pop(L,1);
luaL_newmetatable(L, META_ITEMROULETTE_ITEMLIST);
lua_pushcfunction(L, itemrouletteitemlist_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, itemrouletteitemlist_set);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, itemrouletteitemlist_len);
lua_setfield(L, -2, "__len");
lua_pop(L,1);
return 0;
}

View file

@ -103,6 +103,8 @@ extern lua_State *gL;
#define META_ACTIVATOR "ACTIVATOR_T*" #define META_ACTIVATOR "ACTIVATOR_T*"
#define META_FOLLOWER "FOLLOWER_T*" #define META_FOLLOWER "FOLLOWER_T*"
#define META_ITEMROULETTE "ITEMROULETTE_T"
#define META_ITEMROULETTE_ITEMLIST "ITEMROULETTE_T.ITEMLIST"
boolean luaL_checkboolean(lua_State *L, int narg); boolean luaL_checkboolean(lua_State *L, int narg);
@ -123,6 +125,7 @@ int LUA_PolyObjLib(lua_State *L);
int LUA_BlockmapLib(lua_State *L); int LUA_BlockmapLib(lua_State *L);
int LUA_HudLib(lua_State *L); int LUA_HudLib(lua_State *L);
int LUA_FollowerLib(lua_State *L); int LUA_FollowerLib(lua_State *L);
int LUA_ItemRouletteLib(lua_State *L);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View file

@ -25,6 +25,8 @@
#include "lua_hook.h" // hook_cmd_running errors #include "lua_hook.h" // hook_cmd_running errors
#include "k_profiles.h" // GetPrettyRRID #include "k_profiles.h" // GetPrettyRRID
boolean constplayer = false;
static int lib_iteratePlayers(lua_State *L) static int lib_iteratePlayers(lua_State *L)
{ {
INT32 i = -1; INT32 i = -1;
@ -438,10 +440,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->tripwireUnstuck); lua_pushinteger(L, plr->tripwireUnstuck);
else if (fastcmp(field,"bumpunstuck")) else if (fastcmp(field,"bumpunstuck"))
lua_pushinteger(L, plr->bumpUnstuck); lua_pushinteger(L, plr->bumpUnstuck);
/*
else if (fastcmp(field,"itemroulette")) else if (fastcmp(field,"itemroulette"))
lua_pushinteger(L, plr->itemroulette); LUA_PushUserdata(L, &plr->itemRoulette, META_ITEMROULETTE);
*/
else if (fastcmp(field,"itemtype")) else if (fastcmp(field,"itemtype"))
lua_pushinteger(L, plr->itemtype); lua_pushinteger(L, plr->itemtype);
else if (fastcmp(field,"itemamount")) else if (fastcmp(field,"itemamount"))
@ -799,6 +799,8 @@ static int player_set(lua_State *L)
return luaL_error(L, "Do not alter player_t in HUD rendering code!"); return luaL_error(L, "Do not alter player_t in HUD rendering code!");
if (hook_cmd_running) if (hook_cmd_running)
return luaL_error(L, "Do not alter player_t in CMD building code!"); return luaL_error(L, "Do not alter player_t in CMD building code!");
if (constplayer)
return luaL_error(L, "Do not alter player_t while modifying the roulette!");
if (fastcmp(field,"mo")) { if (fastcmp(field,"mo")) {
mobj_t *newmo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)); mobj_t *newmo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
@ -1083,10 +1085,8 @@ static int player_set(lua_State *L)
plr->tripwireUnstuck = luaL_checkinteger(L, 3); plr->tripwireUnstuck = luaL_checkinteger(L, 3);
else if (fastcmp(field,"bumpunstuck")) else if (fastcmp(field,"bumpunstuck"))
plr->bumpUnstuck = luaL_checkinteger(L, 3); plr->bumpUnstuck = luaL_checkinteger(L, 3);
/*
else if (fastcmp(field,"itemroulette")) else if (fastcmp(field,"itemroulette"))
plr->itemroulette = luaL_checkinteger(L, 3); return NOSET;
*/
else if (fastcmp(field,"itemtype")) else if (fastcmp(field,"itemtype"))
plr->itemtype = luaL_checkinteger(L, 3); plr->itemtype = luaL_checkinteger(L, 3);
else if (fastcmp(field,"itemamount")) else if (fastcmp(field,"itemamount"))

27
src/lua_playerlib.h Normal file
View file

@ -0,0 +1,27 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by Kart Krew.
// Copyright (C) 2022 by Sonic Team Junior.
// Copyright (C) 2016 by John "JTE" Muniz.
//
// 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 lua_playerlib.h
/// \brief LUA Player library header.
#ifndef __LUA_PLAYER_H__
#define __LUA_PLAYER_H__
#ifdef __cplusplus
extern "C" {
#endif
extern boolean constplayer;
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __LUA_PLAYER_H__

View file

@ -61,6 +61,7 @@ static lua_CFunction liblist[] = {
LUA_BlockmapLib, // blockmap stuff LUA_BlockmapLib, // blockmap stuff
LUA_HudLib, // HUD stuff LUA_HudLib, // HUD stuff
LUA_FollowerLib, // follower_t, followers[] LUA_FollowerLib, // follower_t, followers[]
LUA_ItemRouletteLib, // itemroulette_t
NULL NULL
}; };
@ -894,6 +895,7 @@ void LUA_InvalidatePlayer(player_t *player)
LUA_InvalidateUserdata(player); LUA_InvalidateUserdata(player);
LUA_InvalidateUserdata(player->karthud); LUA_InvalidateUserdata(player->karthud);
LUA_InvalidateUserdata(&player->cmd); LUA_InvalidateUserdata(&player->cmd);
LUA_InvalidateUserdata(&player->itemRoulette.itemList);
} }
enum enum

View file

@ -138,6 +138,7 @@ static inline void P_ArchivePlayer(savebuffer_t *save)
WRITEUINT8(save->p, players[i].botvars.difficulty); WRITEUINT8(save->p, players[i].botvars.difficulty);
WRITEUINT8(save->p, (UINT8)players[i].botvars.rival); WRITEUINT8(save->p, (UINT8)players[i].botvars.rival);
WRITEUINT8(save->p, (UINT8)players[i].botvars.foe);
WRITEUINT32(save->p, players[i].score); WRITEUINT32(save->p, players[i].score);
} }
@ -195,6 +196,7 @@ static boolean P_UnArchivePlayer(savebuffer_t *save)
savedata.bots[pid].difficulty = READUINT8(save->p); savedata.bots[pid].difficulty = READUINT8(save->p);
savedata.bots[pid].rival = (boolean)READUINT8(save->p); savedata.bots[pid].rival = (boolean)READUINT8(save->p);
savedata.bots[pid].foe = (boolean)READUINT8(save->p);
savedata.bots[pid].score = READUINT32(save->p); savedata.bots[pid].score = READUINT32(save->p);
} }
@ -765,6 +767,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].botvars.difficulty); WRITEUINT8(save->p, players[i].botvars.difficulty);
WRITEUINT8(save->p, players[i].botvars.diffincrease); WRITEUINT8(save->p, players[i].botvars.diffincrease);
WRITEUINT8(save->p, players[i].botvars.rival); WRITEUINT8(save->p, players[i].botvars.rival);
WRITEUINT8(save->p, players[i].botvars.foe);
WRITEFIXED(save->p, players[i].botvars.rubberband); WRITEFIXED(save->p, players[i].botvars.rubberband);
WRITEUINT8(save->p, players[i].botvars.bumpslow); WRITEUINT8(save->p, players[i].botvars.bumpslow);
WRITEUINT32(save->p, players[i].botvars.itemdelay); WRITEUINT32(save->p, players[i].botvars.itemdelay);
@ -782,33 +785,33 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].itemRoulette.active); WRITEUINT8(save->p, players[i].itemRoulette.active);
#ifdef ITEM_LIST_SIZE #ifdef ITEM_LIST_SIZE
WRITEUINT32(save->p, players[i].itemRoulette.itemListLen); WRITEUINT32(save->p, players[i].itemRoulette.itemList.len);
for (q = 0; q < ITEM_LIST_SIZE; q++) for (q = 0; q < ITEM_LIST_SIZE; q++)
{ {
if (q >= players[i].itemRoulette.itemListLen) if (q >= players[i].itemRoulette.itemList.len)
{ {
WRITESINT8(save->p, KITEM_NONE); WRITESINT8(save->p, KITEM_NONE);
} }
else else
{ {
WRITESINT8(save->p, players[i].itemRoulette.itemList[q]); WRITESINT8(save->p, players[i].itemRoulette.itemList.items[q]);
} }
} }
#else #else
if (players[i].itemRoulette.itemList == NULL) if (players[i].itemRoulette.itemList.items == NULL)
{ {
WRITEUINT32(save->p, 0); WRITEUINT32(save->p, 0);
WRITEUINT32(save->p, 0); WRITEUINT32(save->p, 0);
} }
else else
{ {
WRITEUINT32(save->p, players[i].itemRoulette.itemListCap); WRITEUINT32(save->p, players[i].itemRoulette.itemList.cap);
WRITEUINT32(save->p, players[i].itemRoulette.itemListLen); WRITEUINT32(save->p, players[i].itemRoulette.itemList.len);
for (q = 0; q < players[i].itemRoulette.itemListLen; q++) for (q = 0; q < players[i].itemRoulette.itemList.len; q++)
{ {
WRITESINT8(save->p, players[i].itemRoulette.itemList[q]); WRITESINT8(save->p, players[i].itemRoulette.itemList.items[q]);
} }
} }
#endif #endif
@ -1430,6 +1433,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].botvars.difficulty = READUINT8(save->p); players[i].botvars.difficulty = READUINT8(save->p);
players[i].botvars.diffincrease = READUINT8(save->p); players[i].botvars.diffincrease = READUINT8(save->p);
players[i].botvars.rival = (boolean)READUINT8(save->p); players[i].botvars.rival = (boolean)READUINT8(save->p);
players[i].botvars.foe = (boolean)READUINT8(save->p);
players[i].botvars.rubberband = READFIXED(save->p); players[i].botvars.rubberband = READFIXED(save->p);
players[i].botvars.bumpslow = READUINT8(save->p); players[i].botvars.bumpslow = READUINT8(save->p);
players[i].botvars.itemdelay = READUINT32(save->p); players[i].botvars.itemdelay = READUINT32(save->p);
@ -1447,44 +1451,44 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].itemRoulette.active = (boolean)READUINT8(save->p); players[i].itemRoulette.active = (boolean)READUINT8(save->p);
#ifdef ITEM_LIST_SIZE #ifdef ITEM_LIST_SIZE
players[i].itemRoulette.itemListLen = (size_t)READUINT32(save->p); players[i].itemRoulette.itemList.len = (size_t)READUINT32(save->p);
for (q = 0; q < ITEM_LIST_SIZE; q++) for (q = 0; q < ITEM_LIST_SIZE; q++)
{ {
players[i].itemRoulette.itemList[q] = READSINT8(save->p); players[i].itemRoulette.itemList.items[q] = READSINT8(save->p);
} }
#else #else
players[i].itemRoulette.itemListCap = (size_t)READUINT32(save->p); players[i].itemRoulette.itemList.cap = (size_t)READUINT32(save->p);
players[i].itemRoulette.itemListLen = (size_t)READUINT32(save->p); players[i].itemRoulette.itemList.len = (size_t)READUINT32(save->p);
if (players[i].itemRoulette.itemListCap > 0) if (players[i].itemRoulette.itemList.cap > 0)
{ {
if (players[i].itemRoulette.itemList == NULL) if (players[i].itemRoulette.itemList.items == NULL)
{ {
players[i].itemRoulette.itemList = Z_Calloc( players[i].itemRoulette.itemList.items = (SINT8*)Z_Calloc(
sizeof(SINT8) * players[i].itemRoulette.itemListCap, sizeof(SINT8) * players[i].itemRoulette.itemList.cap,
PU_STATIC, PU_STATIC,
&players[i].itemRoulette.itemList NULL
); );
} }
else else
{ {
players[i].itemRoulette.itemList = Z_Realloc( players[i].itemRoulette.itemList.items = (SINT8*)Z_Realloc(
players[i].itemRoulette.itemList, players[i].itemRoulette.itemList.items,
sizeof(SINT8) * players[i].itemRoulette.itemListCap, sizeof(SINT8) * players[i].itemRoulette.itemList.cap,
PU_STATIC, PU_STATIC,
&players[i].itemRoulette.itemList NULL
); );
} }
if (players[i].itemRoulette.itemList == NULL) if (players[i].itemRoulette.itemList.items == NULL)
{ {
I_Error("Not enough memory for item roulette list\n"); I_Error("Not enough memory for item roulette list\n");
} }
for (q = 0; q < players[i].itemRoulette.itemListLen; q++) for (q = 0; q < players[i].itemRoulette.itemList.len; q++)
{ {
players[i].itemRoulette.itemList[q] = READSINT8(save->p); players[i].itemRoulette.itemList.items[q] = READSINT8(save->p);
} }
} }
#endif #endif

View file

@ -46,6 +46,7 @@ struct savedata_bot_s
UINT8 skin; UINT8 skin;
UINT8 difficulty; UINT8 difficulty;
boolean rival; boolean rival;
boolean foe;
UINT32 score; UINT32 score;
}; };