Bots: Decide what item they want without using RNG

Makes Lv.1 a lot less aggressive, and Lv.MAX slightly more aggressive. But the main advantage is simply that they are deterministic again and I can  :D
This commit is contained in:
Sally Coolatta 2023-05-15 03:05:28 -04:00
parent 77ab86ab34
commit d367bacc39
6 changed files with 263 additions and 6 deletions

View file

@ -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;

View file

@ -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

View file

@ -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;
}
}

View file

@ -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.

View file

@ -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);

View file

@ -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);