diff --git a/src/d_player.h b/src/d_player.h index 1cb70aeaa..a6aa69675 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -333,6 +333,9 @@ struct botvars_t tic_t spindashconfirm; // When high enough, they will try spindashing UINT32 respawnconfirm; // When high enough, they will use Ring Shooter + + UINT8 roulettePriority; // What items to go for on the roulette + tic_t rouletteTimeout; // If it takes too long to decide, try lowering priority until we find something valid. }; // player_t struct for round-specific condition tracking @@ -399,6 +402,23 @@ struct itemroulette_t boolean eggman; }; +// enum for bot item priorities +typedef enum +{ + BOT_ITEM_PR__FALLBACK, // Priority decrement fallback -- end the bot's roulette asap + BOT_ITEM_PR_NEUTRAL, // Default priority + BOT_ITEM_PR_FRONTRUNNER, + BOT_ITEM_PR_SPEED, + // Priorities beyond this point are explicitly + // used when any item from their priority group + // exists in the roulette at all. + BOT_ITEM_PR__OVERRIDES, + BOT_ITEM_PR_RINGDEBT = BOT_ITEM_PR__OVERRIDES, + BOT_ITEM_PR_POWER, + BOT_ITEM_PR_SPB, + BOT_ITEM_PR__MAX +} botItemPriority_e; + // player_t struct for loop state typedef struct { fixed_t radius; diff --git a/src/k_bot.h b/src/k_bot.h index c06c90a93..595938af6 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -37,6 +37,9 @@ extern "C" { // How many tics without being able to make progress before we'll let you respawn. #define BOTRESPAWNCONFIRM (5*TICRATE) +// How long it takes for a Lv.1 bot to decide to pick an item. +#define BOT_ITEM_DECISION_TIME (3*TICRATE) + // Point for bots to aim for struct botprediction_t { fixed_t x, y; @@ -277,6 +280,25 @@ void K_UpdateBotGameplayVars(player_t *player); void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); + +/*-------------------------------------------------- + void K_BotPickItemPriority(player_t *player) + + Sets a bot's desired item classification + based on what they're , + and delays based on their difficulty + intended to be run when starting a roulette. + + Input Arguments:- + player - Bot to set the item classification for. + + Return:- + N/A +--------------------------------------------------*/ + +void K_BotPickItemPriority(player_t *player); + + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_botitem.c b/src/k_botitem.c index 58e448d1f..c4ecc4af3 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1394,15 +1394,54 @@ static void K_BotItemRings(player_t *player, ticcmd_t *cmd) --------------------------------------------------*/ static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) { + // 12 tics late for Lv.1, frame-perfect for Lv.MAX + const tic_t confirmTime = (MAXBOTDIFFICULTY - player->botvars.difficulty); + if (K_ItemButtonWasDown(player) == true) { return; } - // TODO: Would be nice to implement smarter behavior - // for selecting items. + if (player->botvars.roulettePriority == BOT_ITEM_PR__FALLBACK) + { + // No items were part of our list, so set immediately. + player->botvars.itemconfirm = confirmTime + 1; + } + else if (player->botvars.itemconfirm > 0) + { + // Delaying our reaction time a bit... + player->botvars.itemconfirm++; + } + else + { + botItemPriority_e currentPriority = K_GetBotItemPriority( + player->itemRoulette.itemList[ player->itemRoulette.index ] + ); - cmd->buttons |= BT_ATTACK; + if (player->botvars.roulettePriority == currentPriority) + { + // This is the item we want! Start timing! + player->botvars.itemconfirm++; + } + else + { + // Not the time we want... if we take too long, + // reduce priority until we get to a valid one. + player->botvars.rouletteTimeout++; + + if (player->botvars.rouletteTimeout > player->itemRoulette.itemListLen * player->itemRoulette.speed) + { + player->botvars.roulettePriority--; + player->botvars.rouletteTimeout = 0; + } + } + } + + if (player->botvars.itemconfirm > confirmTime) + { + // We've waited out our reaction time -- press the button now! + cmd->buttons |= BT_ATTACK; + } } /*-------------------------------------------------- @@ -1568,3 +1607,92 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) } } } + +/*-------------------------------------------------- + void K_BotPickItemPriority(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void K_BotPickItemPriority(player_t *player) +{ + const fixed_t closeDistance = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); + size_t i; + + // Roulette reaction time. This is how long to wait before considering items. + // Takes 3 seconds for Lv.1, is instant for Lv.MAX + player->botvars.itemdelay = ((MAXBOTDIFFICULTY - player->botvars.difficulty) * BOT_ITEM_DECISION_TIME) / (MAXBOTDIFFICULTY - 1); + player->botvars.itemconfirm = 0; + + // Set neutral items by default. + player->botvars.roulettePriority = BOT_ITEM_PR_NEUTRAL; + player->botvars.rouletteTimeout = 0; + + // Check for items that are extremely high priority. + for (i = 0; i < player->itemRoulette.itemListLen; i++) + { + botItemPriority_e priority = K_GetBotItemPriority( player->itemRoulette.itemList[i] ); + + if (priority < BOT_ITEM_PR__OVERRIDES) + { + // Not high enough to override. + continue; + } + + player->botvars.roulettePriority = max( player->botvars.roulettePriority, priority ); + } + + if (player->botvars.roulettePriority >= BOT_ITEM_PR__OVERRIDES) + { + // Selected a priority in the loop above. + return; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *other = NULL; + fixed_t distance = INT32_MAX; + + if (playeringame[i] == false) + { + continue; + } + + other = &players[i]; + if (other->spectator == true || P_MobjWasRemoved(other->mo) == true) + { + continue; + } + + distance = P_AproxDistance( + P_AproxDistance( + other->mo->x - player->mo->x, + other->mo->y - player->mo->y + ), + other->mo->z - player->mo->z + ); + + if (distance < closeDistance) + { + // A player is relatively close. + break; + } + } + + if (i == MAXPLAYERS) + { + // Players are nearby, stay as neutral priority. + return; + } + + // Players are far away enough to give you breathing room. + if (player->position == 1) + { + // Frontrunning, so pick frontrunner items! + player->botvars.roulettePriority = BOT_ITEM_PR_FRONTRUNNER; + } + else + { + // Behind, so pick speed items! + player->botvars.roulettePriority = BOT_ITEM_PR_SPEED; + } +} diff --git a/src/k_roulette.c b/src/k_roulette.c index 4efa164a4..db97e1695 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -262,6 +262,71 @@ boolean K_ItemSingularity(kartitems_t item) } } +/*-------------------------------------------------- + botItemPriority_e K_GetBotItemPriority(kartitems_t type) + + See header file for description. +--------------------------------------------------*/ +botItemPriority_e K_GetBotItemPriority(kartitems_t type) +{ + switch (type) + { + case KITEM_SPB: + { + // Items that are intended to improve the game balance for everyone. + return BOT_ITEM_PR_SPB; + } + case KITEM_INVINCIBILITY: + case KITEM_GROW: + case KITEM_SHRINK: + case KITEM_LIGHTNINGSHIELD: + case KITEM_BUBBLESHIELD: + case KITEM_FLAMESHIELD: + { + // Items that drastically improve your own defense and/or speed. + return BOT_ITEM_PR_POWER; + } + case KITEM_SUPERRING: + { + // Items that get you out of ring debt. + return BOT_ITEM_PR_RINGDEBT; + } + case KITEM_SNEAKER: + case KITEM_ROCKETSNEAKER: + case KITEM_GARDENTOP: + case KITEM_POGOSPRING: + { + // Used when not in 1st place and relatively far from players. + // Items that give you speed with no protection. + return BOT_ITEM_PR_SPEED; + } + case KITEM_HYUDORO: + case KITEM_LANDMINE: + case KITEM_DROPTARGET: + case KITEM_EGGMAN: + case KITEM_GACHABOM: + { + // Used when in 1st place and relatively far from players. + // Typically attack items that don't give you protection. + return BOT_ITEM_PR_FRONTRUNNER; + } + case KITEM_ORBINAUT: + case KITEM_BALLHOG: + case KITEM_JAWZ: + case KITEM_BANANA: + case KITEM_MINE: + { + // Used in all other instances (close to other players, no priority override) + // Typically attack items that give you protection. + return BOT_ITEM_PR_NEUTRAL; + } + default: + { + return BOT_ITEM_PR__FALLBACK; + } + } +} + /*-------------------------------------------------- static fixed_t K_ItemOddsScale(UINT8 playerCount) @@ -1351,9 +1416,10 @@ void K_StartItemRoulette(player_t *const player) K_FillItemRouletteData(player, roulette); - // Make the bots select their item after a little while. - // One of the few instances of bot RNG, would be nice to remove it. - player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); + if (K_PlayerUsesBotMovement(player) == true) + { + K_BotPickItemPriority(player); + } // Prevent further duplicates of items that // are intended to only have one out at a time. diff --git a/src/k_roulette.h b/src/k_roulette.h index f4afb4988..f31345e8f 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -57,6 +57,23 @@ boolean K_ItemEnabled(kartitems_t item); boolean K_ItemSingularity(kartitems_t item); +/*-------------------------------------------------- + botItemPriority_e K_GetBotItemPriority(kartitems_t type) + + Returns an item's priority value, which + bots use to determine what kind of item they + want when the roulette is started. + + Input Arguments:- + item - The item to check. + + Return:- + The item's priority type. +--------------------------------------------------*/ + +botItemPriority_e K_GetBotItemPriority(kartitems_t type); + + /*-------------------------------------------------- INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); diff --git a/src/p_saveg.c b/src/p_saveg.c index 9acb3d980..88c031329 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -444,6 +444,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITESINT8(save->p, players[i].botvars.turnconfirm); WRITEUINT32(save->p, players[i].botvars.spindashconfirm); WRITEUINT32(save->p, players[i].botvars.respawnconfirm); + WRITEUINT8(save->p, players[i].botvars.roulettePriority); + WRITEUINT32(save->p, players[i].botvars.rouletteTimeout); // itemroulette_t WRITEUINT8(save->p, players[i].itemRoulette.active); @@ -827,6 +829,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].botvars.turnconfirm = READSINT8(save->p); players[i].botvars.spindashconfirm = READUINT32(save->p); players[i].botvars.respawnconfirm = READUINT32(save->p); + players[i].botvars.roulettePriority = READUINT8(save->p); + players[i].botvars.rouletteTimeout = READUINT32(save->p); // itemroulette_t players[i].itemRoulette.active = (boolean)READUINT8(save->p);