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_drawlist.c
lua_followerlib.c
lua_itemroulettelib.c
lua_profile.cpp
k_kart.c
k_respawn.c

View file

@ -426,6 +426,7 @@ struct botvars_t
UINT8 difficulty; // Bot's difficulty setting
INT16 diffincrease; // In GP: bot difficulty will increase this much next round
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
@ -516,22 +517,24 @@ struct skybox_t {
// player_t struct for item roulette variables
// Doing this the right way is causing problems.
// so FINE, it's a static length now.
#define ITEM_LIST_SIZE (NUMKARTRESULTS << 3)
// In case of dynamic alloc failure, break glass:
// #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
{
boolean active;
#ifdef ITEM_LIST_SIZE
size_t itemListLen;
SINT8 itemList[ITEM_LIST_SIZE];
#else
size_t itemListCap;
size_t itemListLen;
SINT8 *itemList;
#endif
itemlist_t itemList;
UINT8 playing, exiting;
UINT32 preexpdist, dist, baseDist;

View file

@ -5248,6 +5248,21 @@ struct int_const_s const INT_CONST[] = {
{"ENDOFPOWERUPS",ENDOFPOWERUPS},
{"LASTPOWERUP",LASTPOWERUP},
{"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
{"KSHIELD_NONE",KSHIELD_NONE},

View file

@ -321,6 +321,7 @@ void G_ReadDemoExtraData(void)
players[p].botvars.difficulty = READUINT8(demobuf.p);
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.foe = (boolean)READUINT8(demobuf.p);
}
}
if (extradata & DXD_PLAYSTATE)
@ -497,6 +498,7 @@ void G_WriteDemoExtraData(void)
WRITEUINT8(demobuf.p, players[i].botvars.difficulty);
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.foe);
}
}
if (demo_extradata[i] & DXD_PLAYSTATE)
@ -2132,6 +2134,7 @@ void G_BeginRecording(void)
WRITEUINT8(demobuf.p, player->botvars.difficulty);
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.foe);
}
// Name
@ -3315,6 +3318,7 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
players[p].botvars.difficulty = READUINT8(demobuf.p);
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.foe = (boolean)READUINT8(demobuf.p);
}
K_UpdateShrinkCheat(&players[p]);

View file

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

View file

@ -408,6 +408,8 @@ void K_UpdateMatchRaceBots(void)
clear_bots(wantedbots);
}
K_AssignFoes();
// We should have enough bots now :)
#ifdef HAVE_DISCORDRPC
@ -615,7 +617,7 @@ fixed_t K_BotMapModifier(void)
--------------------------------------------------*/
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;
UINT8 pos = 1;
UINT8 i;
@ -626,6 +628,11 @@ static UINT32 K_BotRubberbandDistance(const player_t *player)
return 0;
}
/*
if (player->botvars.foe)
spacing /= 2;
*/
for (i = 0; i < MAXPLAYERS; i++)
{
if (i == portpriority)
@ -649,6 +656,11 @@ static UINT32 K_BotRubberbandDistance(const player_t *player)
continue;
}
if (player->botvars.foe && !players[i].botvars.foe)
{
continue;
}
// First check difficulty levels, then score, then settle it with port priority!
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
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);
@ -805,7 +819,13 @@ fixed_t K_BotRubberband(const player_t *player)
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)
{
// 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 dest = K_BotRubberband(player);
fixed_t deflect = player->botvars.recentDeflection;
if (deflect > BOTMAXDEFLECTION)
deflect = BOTMAXDEFLECTION;
@ -2146,7 +2166,7 @@ void K_UpdateBotGameplayVars(player_t *player)
UINT32 smo = BOTANGLESAMPLES - 1;
player->botvars.recentDeflection = (smo * player->botvars.recentDeflection / BOTANGLESAMPLES) + (dangle / BOTANGLESAMPLES);
player->botvars.lastAngle = mangle;
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;
}
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.
return false;
@ -1863,7 +1863,7 @@ static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player)
else
{
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)
@ -1877,7 +1877,7 @@ static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player)
// reduce priority until we get to a valid one.
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.rouletteTimeout = 0;
@ -1995,9 +1995,9 @@ void K_BotPickItemPriority(player_t *player)
player->botvars.rouletteTimeout = 0;
// 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)
{

View file

@ -98,6 +98,113 @@ static UINT8 K_GetOffsetStartingDifficulty(const UINT8 startingdifficulty, UINT8
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)
@ -254,6 +361,8 @@ void K_InitGrandPrixBots(void)
{
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);
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].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)
@ -379,6 +437,8 @@ void K_UpdateGrandPrixBots(void)
return;
}
K_AssignFoes();
// Find the rival.
for (i = 0; i < MAXPLAYERS; i++)
{
@ -639,7 +699,7 @@ void K_IncreaseBotDifficulty(player_t *bot)
// RELAXED MODE:
// Continues don't drop bot difficulty, because we always advance.
// 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)
{
switch(averageRank)
@ -689,8 +749,8 @@ static boolean CompareJoiners(player_t *a, player_t *b)
return (a->spectatewait < b->spectatewait);
}
// They are equals, so just randomize
return (P_Random(PR_BOTS) & 1);
// Fuck it
return a > 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);
}
// They are equals, so just randomize
return (P_Random(PR_BOTS) & 1);
// Fuck it
return a > b;
}
/*--------------------------------------------------

View file

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

View file

@ -1630,15 +1630,15 @@ static void K_drawKartItem(void)
boolean flashOnOne = false;
boolean flashOnTwo = false;
if (stplyr->itemRoulette.itemListLen > 0)
if (stplyr->itemRoulette.itemList.len > 0)
{
// Init with item roulette stuff.
for (i = 0; i < 3; i++)
{
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 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);
@ -1825,7 +1825,7 @@ static void K_drawKartItem(void)
}
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);
}
@ -2144,15 +2144,15 @@ static void K_drawKartSlotMachine(void)
vector2_t rouletteCrop = {10, 10};
INT32 i;
if (stplyr->itemRoulette.itemListLen > 0)
if (stplyr->itemRoulette.itemList.len > 0)
{
// Init with item roulette stuff.
for (i = 0; i < 3; i++)
{
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);
}
@ -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);
}
UINT8 *foecol = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(SKINCOLOR_RED), GTC_CACHE);
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)
@ -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));
#ifndef ITEM_LIST_SIZE
Z_Free(rouletteData.itemList);
Z_Free(rouletteData.itemList.items);
#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, 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, 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)
{
if (player == NULL)
return FRACUNIT; // K_FillItemRouletteData can OSTENSIBLY call this with null player for "generic" use.
fixed_t min = (franticitems) ? MINFRANTICFACTOR : MINGRADINGFACTOR;
if (grandprixinfo.gp && grandprixinfo.masterbots && !K_PlayerUsesBotMovement(player))
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!
if (player->botvars.rival || cv_levelskull.value)
player->draftpower += add;
else if (player->botvars.foe)
player->draftpower += add/2;
else if (dest->player->bot) // Reduce bot gluts.
player->draftpower -= 3*add/4;
}
@ -3558,6 +3563,10 @@ static fixed_t K_RingDurationBoost(const player_t *player)
// x2.0 for Rival
ret *= 2;
}
else if (player->botvars.foe)
{
ret = 3 * ret / 2;
}
}
return ret;
@ -3980,6 +3989,11 @@ fixed_t K_GetKartSpeed(const player_t *player, boolean doboostpower, boolean dor
// +10% top speed for the rival
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)
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.
See header file for description.
--------------------------------------------------*/
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.
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
as well as Frantic Items.
@ -480,10 +472,8 @@ static UINT32 K_UndoMapScaling(UINT32 distance)
Return:-
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 (franticitems == true)
{
@ -498,24 +488,13 @@ static UINT32 K_ScaleItemDistance(const player_t *player, UINT32 distance, UINT8
FRACUNIT + (K_ItemOddsScale(numPlayers) / 2)
);
// Distance is reduced based on the player's gradingfactor
// distance = FixedMul(distance, player->gradingfactor);
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.
See header file for description.
--------------------------------------------------*/
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_ScaleItemDistance(player, pdis, numPlayers);
pdis = K_ScaleItemDistance(pdis, numPlayers);
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
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.
See header file for description.
--------------------------------------------------*/
static boolean K_DenyShieldOdds(kartitems_t item)
boolean K_DenyShieldOdds(kartitems_t item)
{
const INT32 shieldType = K_GetShieldFromItem(item);
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)
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.
See header file for description.
--------------------------------------------------*/
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)
{
@ -778,23 +740,23 @@ static void K_InitRoulette(itemroulette_t *const roulette)
size_t i;
#ifndef ITEM_LIST_SIZE
if (roulette->itemList == NULL)
if (roulette->itemList.items == NULL)
{
roulette->itemListCap = 8;
roulette->itemList = Z_Calloc(
sizeof(SINT8) * roulette->itemListCap,
roulette->itemList.cap = 32;
roulette->itemList.items = Z_Calloc(
sizeof(SINT8) * roulette->itemList.cap,
PU_STATIC,
&roulette->itemList
NULL
);
if (roulette->itemList == NULL)
if (roulette->itemList.items == NULL)
{
I_Error("Not enough memory for item roulette list\n");
}
}
#endif
roulette->itemListLen = 0;
roulette->itemList.len = 0;
roulette->index = 0;
roulette->baseDist = roulette->dist = 0;
@ -860,74 +822,65 @@ static void K_InitRoulette(itemroulette_t *const roulette)
&& 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
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
See header file for description.
--------------------------------------------------*/
static void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item)
void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item)
{
#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");
return;
}
#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;
roulette->itemList = Z_Realloc(
roulette->itemList,
sizeof(SINT8) * roulette->itemListCap,
CONS_Alert(CONS_WARNING, M_GetText("Item Roulette rejected an out-of-range item.\n"));
return;
}
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,
&roulette->itemList
NULL
);
if (roulette->itemList == NULL)
if (roulette->itemList.items == NULL)
{
I_Error("Not enough memory for item roulette list\n");
}
}
#endif
roulette->itemList[ roulette->itemListLen ] = item;
roulette->itemListLen++;
roulette->itemList.items[ roulette->itemList.len ] = item;
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
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
See header file for description.
--------------------------------------------------*/
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))
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,
adjusted for progress in the race and front
running.
Input Arguments:-
roulette - The item roulette data to modify.
Return:-
N/A
See header file for description.
--------------------------------------------------*/
static void K_CalculateRouletteSpeed(itemroulette_t *const roulette)
void K_CalculateRouletteSpeed(itemroulette_t *const roulette)
{
fixed_t frontRun = 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)
{
if (roulette->itemListLen > 0)
if (roulette->itemList.len > 0)
return;
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.
--------------------------------------------------*/
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);
if (player != NULL)
@ -1281,6 +1217,49 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
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 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]);
}
return;
}
}
@ -1792,10 +1772,10 @@ void K_StartItemRoulette(player_t *const player, boolean ringbox)
itemroulette_t *const roulette = &player->itemRoulette;
size_t i;
K_FillItemRouletteData(player, roulette, ringbox, false);
K_FillItemRoulette(player, roulette, ringbox);
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)
{
@ -1804,9 +1784,9 @@ void K_StartItemRoulette(player_t *const player, boolean ringbox)
// Prevent further duplicates of items that
// 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)
{
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
received from the roulette.
Input Arguments:-
player - The player receiving the item.
getitem - The item to give to the player.
Return:-
N/A
See header file for description.
--------------------------------------------------*/
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)
{
@ -1935,9 +1907,9 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
return;
}
if (roulette->itemListLen == 0
if (roulette->itemList.len == 0
#ifndef ITEM_LIST_SIZE
|| roulette->itemList == NULL
|| roulette->itemList.items == NULL
#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?
{
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...
}
else
@ -2012,7 +1984,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
// And one more nudge for the remaining delay.
roulette->tics = (roulette->tics + fudgedDelay) % roulette->speed;
INT32 finalItem = roulette->itemList[ roulette->index ];
INT32 finalItem = roulette->itemList.items[ roulette->index ];
if (roulette->ringbox == true)
{
@ -2057,7 +2029,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
if (roulette->tics == 0)
{
roulette->index = (roulette->index + 1) % roulette->itemListLen;
roulette->index = (roulette->index + 1) % roulette->itemList.len;
roulette->tics = roulette->speed;
// This makes the roulette produce the random noises.
@ -2070,7 +2042,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd)
else
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);

View file

@ -76,6 +76,90 @@ boolean K_ItemSingularity(kartitems_t item);
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);
@ -96,9 +180,27 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result);
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
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
--------------------------------------------------*/
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_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);
/*--------------------------------------------------
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
} // extern "C"
#endif

View file

@ -32,6 +32,7 @@
#include "k_collide.h"
#include "k_color.h"
#include "k_hud.h"
#include "k_grandprix.h"
#include "d_netcmd.h" // IsPlayerAdmin
#include "k_menu.h" // Player Setup menu color stuff
#include "p_spec.h" // P_StartQuake
@ -46,6 +47,7 @@
#include "lua_hud.h" // hud_running errors
#include "taglist.h" // P_FindSpecialLineFromTag
#include "lua_hook.h" // hook_cmd_running errors
#include "k_roulette.h"
#define NOHUD if (hud_running)\
return luaL_error(L, "HUD rendering code should not call this function!");\
@ -232,6 +234,8 @@ static const struct {
{META_ACTIVATOR, "activator_t"},
{META_FOLLOWER, "follower_t"},
{META_ITEMROULETTE, "itemroulette_t"},
{META_ITEMROULETTE_ITEMLIST, "itemroulette_t.itemlist_t"},
{NULL, NULL}
};
@ -3920,6 +3924,68 @@ static int lib_kGetItemPatch(lua_State *L)
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)
{
mobj_t *t1 = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@ -4014,6 +4080,12 @@ static int lib_kDeclareWeakspot(lua_State *L)
return 0;
}
static int lib_kCheckBossIntro(lua_State *L)
{
lua_pushboolean(L, K_CheckBossIntro());
return 1;
}
static int lib_vsGetArena(lua_State *L)
{
INT32 bossindex = luaL_checkinteger(L, 1);
@ -4057,6 +4129,419 @@ static int lib_vsRandomPointOnArena(lua_State *L)
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)
{
lua_pushinteger(L, I_GetPreciseTime() / (I_GetPrecisePrecision() / 1000000));
@ -4326,9 +4811,15 @@ static luaL_Reg lib[] = {
{"K_GetKartAccel",lib_kGetKartAccel},
{"K_GetKartFlashing",lib_kGetKartFlashing},
{"K_GetItemPatch",lib_kGetItemPatch},
{"K_GetCollideAngle",lib_kGetCollideAngle},
{"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_PowerUpRemaining",lib_kPowerUpRemaining},
@ -4339,9 +4830,39 @@ static luaL_Reg lib[] = {
{"K_InitBossHealthBar", lib_kInitBossHealthBar},
{"K_UpdateBossHealthBar", lib_kUpdateBossHealthBar},
{"K_DeclareWeakspot", lib_kDeclareWeakspot},
{"K_CheckBossIntro", lib_kCheckBossIntro},
{"VS_GetArena", lib_vsGetArena},
{"VS_PredictAroundArena", lib_vsPredictAroundArena},
{"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_DoTitlecardCEcho", lib_startTitlecardCecho},

View file

@ -79,6 +79,8 @@ automatically.
X (GameQuit),\
X (PlayerCmd),/* building the player's ticcmd struct */\
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) \
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_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced);
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
} // extern "C"

View file

@ -24,6 +24,7 @@
#include "lua_hook.h"
#include "lua_hud.h" // hud_running errors
#include "lua_profile.h"
#include "lua_playerlib.h" // constplayer
#include "command.h"
#include "m_perfstats.h"
@ -1009,4 +1010,39 @@ int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend)
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;

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_FOLLOWER "FOLLOWER_T*"
#define META_ITEMROULETTE "ITEMROULETTE_T"
#define META_ITEMROULETTE_ITEMLIST "ITEMROULETTE_T.ITEMLIST"
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_HudLib(lua_State *L);
int LUA_FollowerLib(lua_State *L);
int LUA_ItemRouletteLib(lua_State *L);
#ifdef __cplusplus
} // extern "C"

View file

@ -25,6 +25,8 @@
#include "lua_hook.h" // hook_cmd_running errors
#include "k_profiles.h" // GetPrettyRRID
boolean constplayer = false;
static int lib_iteratePlayers(lua_State *L)
{
INT32 i = -1;
@ -438,10 +440,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->tripwireUnstuck);
else if (fastcmp(field,"bumpunstuck"))
lua_pushinteger(L, plr->bumpUnstuck);
/*
else if (fastcmp(field,"itemroulette"))
lua_pushinteger(L, plr->itemroulette);
*/
LUA_PushUserdata(L, &plr->itemRoulette, META_ITEMROULETTE);
else if (fastcmp(field,"itemtype"))
lua_pushinteger(L, plr->itemtype);
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!");
if (hook_cmd_running)
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")) {
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);
else if (fastcmp(field,"bumpunstuck"))
plr->bumpUnstuck = luaL_checkinteger(L, 3);
/*
else if (fastcmp(field,"itemroulette"))
plr->itemroulette = luaL_checkinteger(L, 3);
*/
return NOSET;
else if (fastcmp(field,"itemtype"))
plr->itemtype = luaL_checkinteger(L, 3);
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_HudLib, // HUD stuff
LUA_FollowerLib, // follower_t, followers[]
LUA_ItemRouletteLib, // itemroulette_t
NULL
};
@ -894,6 +895,7 @@ void LUA_InvalidatePlayer(player_t *player)
LUA_InvalidateUserdata(player);
LUA_InvalidateUserdata(player->karthud);
LUA_InvalidateUserdata(&player->cmd);
LUA_InvalidateUserdata(&player->itemRoulette.itemList);
}
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, (UINT8)players[i].botvars.rival);
WRITEUINT8(save->p, (UINT8)players[i].botvars.foe);
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].rival = (boolean)READUINT8(save->p);
savedata.bots[pid].foe = (boolean)READUINT8(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.diffincrease);
WRITEUINT8(save->p, players[i].botvars.rival);
WRITEUINT8(save->p, players[i].botvars.foe);
WRITEFIXED(save->p, players[i].botvars.rubberband);
WRITEUINT8(save->p, players[i].botvars.bumpslow);
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);
#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++)
{
if (q >= players[i].itemRoulette.itemListLen)
if (q >= players[i].itemRoulette.itemList.len)
{
WRITESINT8(save->p, KITEM_NONE);
}
else
{
WRITESINT8(save->p, players[i].itemRoulette.itemList[q]);
WRITESINT8(save->p, players[i].itemRoulette.itemList.items[q]);
}
}
#else
if (players[i].itemRoulette.itemList == NULL)
if (players[i].itemRoulette.itemList.items == NULL)
{
WRITEUINT32(save->p, 0);
WRITEUINT32(save->p, 0);
}
else
{
WRITEUINT32(save->p, players[i].itemRoulette.itemListCap);
WRITEUINT32(save->p, players[i].itemRoulette.itemListLen);
WRITEUINT32(save->p, players[i].itemRoulette.itemList.cap);
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
@ -1430,6 +1433,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].botvars.difficulty = READUINT8(save->p);
players[i].botvars.diffincrease = 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.bumpslow = READUINT8(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);
#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++)
{
players[i].itemRoulette.itemList[q] = READSINT8(save->p);
players[i].itemRoulette.itemList.items[q] = READSINT8(save->p);
}
#else
players[i].itemRoulette.itemListCap = (size_t)READUINT32(save->p);
players[i].itemRoulette.itemListLen = (size_t)READUINT32(save->p);
players[i].itemRoulette.itemList.cap = (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(
sizeof(SINT8) * players[i].itemRoulette.itemListCap,
players[i].itemRoulette.itemList.items = (SINT8*)Z_Calloc(
sizeof(SINT8) * players[i].itemRoulette.itemList.cap,
PU_STATIC,
&players[i].itemRoulette.itemList
NULL
);
}
else
{
players[i].itemRoulette.itemList = Z_Realloc(
players[i].itemRoulette.itemList,
sizeof(SINT8) * players[i].itemRoulette.itemListCap,
players[i].itemRoulette.itemList.items = (SINT8*)Z_Realloc(
players[i].itemRoulette.itemList.items,
sizeof(SINT8) * players[i].itemRoulette.itemList.cap,
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");
}
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

View file

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