diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7dcb55484..52b2d24f0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/d_player.h b/src/d_player.h index e73ff2e12..f2edadeee 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -508,22 +508,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; diff --git a/src/deh_tables.c b/src/deh_tables.c index a3394b50b..8a381c834 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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}, diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index d042f0faa..360c80c31 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -1863,7 +1863,7 @@ static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player) else { botItemPriority_e currentPriority = K_GetBotItemPriority( - static_cast( player->itemRoulette.itemList[ player->itemRoulette.index ] ) + static_cast( 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( player->itemRoulette.itemList[i] ) ); + botItemPriority_e priority = K_GetBotItemPriority( static_cast( player->itemRoulette.itemList.items[i] ) ); if (priority < BOT_ITEM_PR__OVERRIDES) { diff --git a/src/k_hud.cpp b/src/k_hud.cpp index c3e444930..89a5e7fb6 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -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); } @@ -7202,7 +7202,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 } diff --git a/src/k_kart.c b/src/k_kart.c index ce6d07536..9aa3f45da 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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; diff --git a/src/k_roulette.c b/src/k_roulette.c index 5834519e2..158f463ba 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -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); diff --git a/src/k_roulette.h b/src/k_roulette.h index 6f3054c5e..e45dd7809 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -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 diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 873056643..c49e5e8ee 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -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}, diff --git a/src/lua_hook.h b/src/lua_hook.h index 12c85be94..d44423400 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -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" diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 676e40b77..45805a062 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -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; diff --git a/src/lua_itemroulettelib.c b/src/lua_itemroulettelib.c new file mode 100644 index 000000000..51bd651f8 --- /dev/null +++ b/src/lua_itemroulettelib.c @@ -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; +} \ No newline at end of file diff --git a/src/lua_libs.h b/src/lua_libs.h index 73d7b4f6e..7dc3f3380 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -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" diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index ea452a7ab..4a8230e82 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -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")) diff --git a/src/lua_playerlib.h b/src/lua_playerlib.h new file mode 100644 index 000000000..d2d09f5a5 --- /dev/null +++ b/src/lua_playerlib.h @@ -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__ diff --git a/src/lua_script.c b/src/lua_script.c index c7e9745f3..eae022f5b 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -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 diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 45da46e47..e2a7fd530 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -782,33 +782,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 @@ -1447,44 +1447,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