mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
2050 lines
47 KiB
C++
2050 lines
47 KiB
C++
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour
|
|
// Copyright (C) 2024 by Kart Krew
|
|
//
|
|
// 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 k_botitem.cpp
|
|
/// \brief Bot item usage logic
|
|
|
|
#include <algorithm>
|
|
|
|
#include <tracy/tracy/Tracy.hpp>
|
|
|
|
#include "doomdef.h"
|
|
#include "d_player.h"
|
|
#include "g_game.h"
|
|
#include "r_main.h"
|
|
#include "p_local.h"
|
|
#include "k_bot.h"
|
|
#include "lua_hook.h"
|
|
#include "byteptr.h"
|
|
#include "d_net.h" // nodetoplayer
|
|
#include "k_kart.h"
|
|
#include "z_zone.h"
|
|
#include "i_system.h"
|
|
#include "p_maputl.h"
|
|
#include "d_ticcmd.h"
|
|
#include "m_random.h"
|
|
#include "r_things.h" // numskins
|
|
#include "k_roulette.h"
|
|
|
|
/*--------------------------------------------------
|
|
static inline boolean K_ItemButtonWasDown(const player_t *player)
|
|
|
|
Looks for players around the bot, and presses the item button
|
|
if there is one in range.
|
|
|
|
Input Arguments:-
|
|
player - Bot to check.
|
|
|
|
Return:-
|
|
true if the item button was pressed last tic, otherwise false.
|
|
--------------------------------------------------*/
|
|
static inline boolean K_ItemButtonWasDown(const player_t *player)
|
|
{
|
|
return (player->oldcmd.buttons & BT_ATTACK);
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static boolean K_BotUseItemNearPlayer(const player_t *player, ticcmd_t *cmd, fixed_t radius)
|
|
|
|
Looks for players around the bot, and presses the item button
|
|
if there is one in range.
|
|
|
|
Input Arguments:-
|
|
player - Bot to compare against.
|
|
cmd - The bot's ticcmd.
|
|
radius - The radius to look for players in.
|
|
|
|
Return:-
|
|
true if a player was found & we can press the item button, otherwise false.
|
|
--------------------------------------------------*/
|
|
static boolean K_BotUseItemNearPlayer(const player_t *player, ticcmd_t *cmd, fixed_t radius)
|
|
{
|
|
ZoneScoped;
|
|
|
|
UINT8 i;
|
|
|
|
if (K_ItemButtonWasDown(player) == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
player_t *target = NULL;
|
|
fixed_t dist = INT32_MAX;
|
|
|
|
if (!playeringame[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
target = &players[i];
|
|
|
|
if (target->mo == NULL || P_MobjWasRemoved(target->mo)
|
|
|| player == target || target->spectator
|
|
|| target->flashing)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
dist = P_AproxDistance(P_AproxDistance(
|
|
player->mo->x - target->mo->x,
|
|
player->mo->y - target->mo->y),
|
|
(player->mo->z - target->mo->z) / 4
|
|
);
|
|
|
|
if (dist <= radius)
|
|
{
|
|
cmd->buttons |= BT_ATTACK;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static player_t *K_PlayerNearSpot(const player_t *player, fixed_t x, fixed_t y, fixed_t radius)
|
|
|
|
Looks for players around a specified x/y coordinate.
|
|
|
|
Input Arguments:-
|
|
player - Bot to compare against.
|
|
x - X coordinate to look around.
|
|
y - Y coordinate to look around.
|
|
radius - The radius to look for players in.
|
|
|
|
Return:-
|
|
The player we found, NULL if nothing was found.
|
|
--------------------------------------------------*/
|
|
static player_t *K_PlayerNearSpot(const player_t *player, fixed_t x, fixed_t y, fixed_t radius)
|
|
{
|
|
ZoneScoped;
|
|
|
|
UINT8 i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
player_t *target = NULL;
|
|
fixed_t dist = INT32_MAX;
|
|
|
|
if (!playeringame[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
target = &players[i];
|
|
|
|
if (target->mo == NULL || P_MobjWasRemoved(target->mo)
|
|
|| player == target || target->spectator
|
|
|| target->flashing)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
dist = P_AproxDistance(
|
|
x - target->mo->x,
|
|
y - target->mo->y
|
|
);
|
|
|
|
if (dist <= radius)
|
|
{
|
|
return target;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static player_t *K_PlayerPredictThrow(const player_t *player, UINT8 extra)
|
|
|
|
Looks for players around the predicted coordinates of their thrown item.
|
|
|
|
Input Arguments:-
|
|
player - Bot to compare against.
|
|
extra - Extra throwing distance, for aim forward on mines.
|
|
|
|
Return:-
|
|
The player we're trying to throw at, NULL if none was found.
|
|
--------------------------------------------------*/
|
|
static player_t *K_PlayerPredictThrow(const player_t *player, UINT8 extra)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t dist = (30 + (extra * 10)) * player->mo->scale;
|
|
const UINT32 airtime = FixedDiv(dist + player->mo->momz, gravity);
|
|
const fixed_t throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
const fixed_t estx = player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, (throwspeed + player->speed) * airtime);
|
|
const fixed_t esty = player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, (throwspeed + player->speed) * airtime);
|
|
|
|
return K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2);
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static player_t *K_PlayerInCone(const player_t *player, UINT16 cone, boolean flip)
|
|
|
|
Looks for players in the .
|
|
|
|
Input Arguments:-
|
|
player - Bot to compare against.
|
|
radius - How far away the targets can be.
|
|
cone - Size of cone, in degrees as an integer.
|
|
flip - If true, look behind. Otherwise, check in front of the player.
|
|
|
|
Return:-
|
|
true if a player was found in the cone, otherwise false.
|
|
--------------------------------------------------*/
|
|
static player_t *K_PlayerInCone(const player_t *player, fixed_t radius, UINT16 cone, boolean flip)
|
|
{
|
|
ZoneScoped;
|
|
|
|
UINT8 i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
player_t *target = NULL;
|
|
fixed_t dist = INT32_MAX;
|
|
|
|
if (!playeringame[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
target = &players[i];
|
|
|
|
if (target->mo == NULL || P_MobjWasRemoved(target->mo)
|
|
|| player == target || target->spectator
|
|
|| target->flashing
|
|
|| !P_CheckSight(player->mo, target->mo))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
dist = P_AproxDistance(P_AproxDistance(
|
|
player->mo->x - target->mo->x,
|
|
player->mo->y - target->mo->y),
|
|
(player->mo->z - target->mo->z) / 4
|
|
);
|
|
|
|
if (dist <= radius)
|
|
{
|
|
angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y);
|
|
INT16 ad = 0;
|
|
|
|
if (a < ANGLE_180)
|
|
{
|
|
ad = AngleFixed(a)>>FRACBITS;
|
|
}
|
|
else
|
|
{
|
|
ad = 360-(AngleFixed(a)>>FRACBITS);
|
|
}
|
|
|
|
ad = abs(ad);
|
|
|
|
if (flip)
|
|
{
|
|
if (ad >= 180-cone)
|
|
{
|
|
return target;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ad <= cone)
|
|
{
|
|
return target;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static boolean K_RivalBotAggression(const player_t *bot, const player_t *target)
|
|
|
|
Returns if a bot is a rival & wants to be aggressive to a player.
|
|
|
|
Input Arguments:-
|
|
bot - Bot to check.
|
|
target - Who the bot wants to attack.
|
|
|
|
Return:-
|
|
false if not the rival. false if the target is another bot. Otherwise, true.
|
|
--------------------------------------------------*/
|
|
static boolean K_RivalBotAggression(const player_t *bot, const player_t *target)
|
|
{
|
|
if (bot == NULL || target == NULL)
|
|
{
|
|
// Invalid.
|
|
return false;
|
|
}
|
|
|
|
if (bot->bot == false)
|
|
{
|
|
// lol
|
|
return false;
|
|
}
|
|
|
|
if (bot->botvars.rival == false)
|
|
{
|
|
// Not the rival, we aren't self-aware.
|
|
return false;
|
|
}
|
|
|
|
if (target->bot == false)
|
|
{
|
|
// This bot knows that the real threat is the player.
|
|
return true;
|
|
}
|
|
|
|
// Calling them your friends is misleading, but you'll at least spare them.
|
|
return false;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_ItemConfirmForTarget(const player_t *bot, ticcmd_t *cmd, const player_t *target, UINT16 amount)
|
|
|
|
Handles updating item confirm values for offense items.
|
|
|
|
Input Arguments:-
|
|
bot - Bot to check.
|
|
cmd - Bot's ticcmd to edit.
|
|
target - Who the bot wants to attack.
|
|
amount - Amount to increase item confirm time by.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_ItemConfirmForTarget(const player_t *bot, ticcmd_t *cmd, const player_t *target, UINT16 amount)
|
|
{
|
|
if (bot == NULL || target == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (K_RivalBotAggression(bot, target) == true)
|
|
{
|
|
// Double the rate when you're aggressive.
|
|
cmd->bot.itemconfirm += amount << 1;
|
|
}
|
|
else
|
|
{
|
|
// Do as normal.
|
|
cmd->bot.itemconfirm += amount;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static boolean K_BotGenericPressItem(const player_t *player, ticcmd_t *cmd, SINT8 dir)
|
|
|
|
Presses the item button & aim buttons for the bot.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
dir - Aiming direction: 1 for forwards, -1 for backwards, 0 for neutral.
|
|
|
|
Return:-
|
|
true if we could press, false if not.
|
|
--------------------------------------------------*/
|
|
static boolean K_BotGenericPressItem(const player_t *player, ticcmd_t *cmd, SINT8 dir)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (K_ItemButtonWasDown(player) == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
cmd->throwdir = KART_FULLTURN * dir;
|
|
cmd->buttons |= BT_ATTACK;
|
|
//player->botvars.itemconfirm = 0;
|
|
return true;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemGenericTap(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for generic items that you need to tap.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemGenericTap(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (K_ItemButtonWasDown(player) == false)
|
|
{
|
|
cmd->buttons |= BT_ATTACK;
|
|
//player->botvars.itemconfirm = 0;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static boolean K_BotRevealsGenericTrap(const player_t *player, INT16 turnamt, boolean mine)
|
|
|
|
Decides if a bot is ready to reveal their trap item or not.
|
|
|
|
Input Arguments:-
|
|
player - Bot that has the banana.
|
|
turnamt - How hard they currently are turning.
|
|
mine - Set to true to handle Mine-specific behaviors.
|
|
|
|
Return:-
|
|
true if we want the bot to reveal their banana, otherwise false.
|
|
--------------------------------------------------*/
|
|
static boolean K_BotRevealsGenericTrap(const player_t *player, INT16 turnamt, boolean mine)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t coneDist = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
|
|
if (abs(turnamt) >= KART_FULLTURN/2)
|
|
{
|
|
// DON'T reveal on turns, we can place bananas on turns whenever we have multiple to spare,
|
|
// or if you missed your intentioned throw/place on a player.
|
|
return false;
|
|
}
|
|
|
|
// Check the predicted throws.
|
|
if (K_PlayerPredictThrow(player, 0) != NULL)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (mine)
|
|
{
|
|
if (K_PlayerPredictThrow(player, 1) != NULL)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check your behind.
|
|
if (K_PlayerInCone(player, coneDist, 15, true) != NULL)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemGenericTrapShield(const player_t *player, ticcmd_t *cmd, INT16 turnamt, boolean mine)
|
|
|
|
Item usage for Eggman shields.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
turnamt - How hard they currently are turning.
|
|
mine - Set to true to handle Mine-specific behaviors.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemGenericTrapShield(const player_t *player, ticcmd_t *cmd, INT16 turnamt, boolean mine)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (player->itemflags & IF_ITEMOUT)
|
|
{
|
|
return;
|
|
}
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
if (K_BotRevealsGenericTrap(player, turnamt, mine) || (player->botvars.itemconfirm > 5*TICRATE))
|
|
{
|
|
K_BotGenericPressItem(player, cmd, 0);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemGenericOrbitShield(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for orbitting shields.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemGenericOrbitShield(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (player->itemflags & IF_ITEMOUT)
|
|
{
|
|
return;
|
|
}
|
|
|
|
K_BotGenericPressItem(player, cmd, 0);
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemSneaker(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for sneakers.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemSneaker(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (P_IsObjectOnGround(player->mo) == false)
|
|
{
|
|
// Don't use while mid-air.
|
|
return;
|
|
}
|
|
|
|
if ((player->offroad && K_ApplyOffroad(player)) // Stuck in offroad, use it NOW
|
|
|| K_GetWaypointIsShortcut(player->nextwaypoint) == true // Going toward a shortcut!
|
|
|| player->speed < K_GetKartSpeed(player, false, true) / 2 // Being slowed down too much
|
|
|| player->speedboost > (FRACUNIT/8) // Have another type of boost (tethering)
|
|
|| player->botvars.itemconfirm > 4*TICRATE) // Held onto it for too long
|
|
{
|
|
if (player->sneakertimer == 0 && K_ItemButtonWasDown(player) == false)
|
|
{
|
|
cmd->buttons |= BT_ATTACK;
|
|
//player->botvars.itemconfirm = 2*TICRATE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cmd->bot.itemconfirm++;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemRocketSneaker(player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for rocket sneakers.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemRocketSneaker(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (P_IsObjectOnGround(player->mo) == false)
|
|
{
|
|
// Don't use while mid-air.
|
|
return;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > TICRATE)
|
|
{
|
|
if (player->sneakertimer == 0 && K_ItemButtonWasDown(player) == false)
|
|
{
|
|
cmd->buttons |= BT_ATTACK;
|
|
//player->botvars.itemconfirm = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cmd->bot.itemconfirm++;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemBanana(player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
|
|
Item usage for trap item throwing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
turnamt - How hard they currently are turning.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemBanana(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t coneDist = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
SINT8 throwdir = -1;
|
|
boolean tryLookback = false;
|
|
player_t *target = NULL;
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
target = K_PlayerInCone(player, coneDist, 15, true);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty);
|
|
throwdir = -1;
|
|
tryLookback = true;
|
|
}
|
|
|
|
if (abs(turnamt) >= KART_FULLTURN/2)
|
|
{
|
|
cmd->bot.itemconfirm += player->botvars.difficulty / 2;
|
|
throwdir = -1;
|
|
}
|
|
else
|
|
{
|
|
target = K_PlayerPredictThrow(player, 0);
|
|
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * 2);
|
|
throwdir = 1;
|
|
}
|
|
}
|
|
|
|
if (tryLookback == true && throwdir == -1)
|
|
{
|
|
cmd->buttons |= BT_LOOKBACK;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > 10*TICRATE || player->bananadrag >= TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, throwdir);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemMine(player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
|
|
Item usage for trap item throwing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
turnamt - How hard they currently are turning.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemMine(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t coneDist = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
SINT8 throwdir = 0;
|
|
boolean tryLookback = false;
|
|
player_t *target = NULL;
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
target = K_PlayerInCone(player, coneDist, 15, true);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty);
|
|
throwdir = -1;
|
|
}
|
|
|
|
if (abs(turnamt) >= KART_FULLTURN/2)
|
|
{
|
|
cmd->bot.itemconfirm += player->botvars.difficulty / 2;
|
|
throwdir = -1;
|
|
tryLookback = true;
|
|
}
|
|
else
|
|
{
|
|
target = K_PlayerPredictThrow(player, 0);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * 2);
|
|
throwdir = 0;
|
|
}
|
|
|
|
target = K_PlayerPredictThrow(player, 1);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * 2);
|
|
throwdir = 1;
|
|
}
|
|
}
|
|
|
|
if (tryLookback == true && throwdir == -1)
|
|
{
|
|
cmd->buttons |= BT_LOOKBACK;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > 10*TICRATE || player->bananadrag >= TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, throwdir);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemLandmine(player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
|
|
Item usage for landmine tossing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
turnamt - How hard they currently are turning.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemLandmine(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t coneDist = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
player_t *target = NULL;
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
if (abs(turnamt) >= KART_FULLTURN/2)
|
|
{
|
|
cmd->bot.itemconfirm += player->botvars.difficulty / 2;
|
|
}
|
|
|
|
target = K_PlayerInCone(player, coneDist, 15, true);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty);
|
|
cmd->buttons |= BT_LOOKBACK;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > 10*TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, -1);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemEggman(player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Eggman item throwing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemEggman(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t coneDist = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
const UINT8 stealth = K_EggboxStealth(player->mo->x, player->mo->y);
|
|
SINT8 throwdir = -1;
|
|
boolean tryLookback = false;
|
|
player_t *target = NULL;
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
target = K_PlayerPredictThrow(player, 0);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty / 2);
|
|
throwdir = 1;
|
|
}
|
|
|
|
target = K_PlayerInCone(player, coneDist, 15, true);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty);
|
|
throwdir = -1;
|
|
tryLookback = true;
|
|
}
|
|
|
|
if (stealth > 1 || player->itemRoulette.active == true)
|
|
{
|
|
cmd->bot.itemconfirm += player->botvars.difficulty * 4;
|
|
throwdir = -1;
|
|
}
|
|
|
|
if (tryLookback == true && throwdir == -1)
|
|
{
|
|
cmd->buttons |= BT_LOOKBACK;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > 10*TICRATE || player->bananadrag >= TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, throwdir);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static boolean K_BotRevealsEggbox(const player_t *player)
|
|
|
|
Decides if a bot is ready to place their Eggman item or not.
|
|
|
|
Input Arguments:-
|
|
player - Bot that has the eggbox.
|
|
|
|
Return:-
|
|
true if we want the bot to reveal their eggbox, otherwise false.
|
|
--------------------------------------------------*/
|
|
static boolean K_BotRevealsEggbox(const player_t *player)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t coneDist = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
const UINT8 stealth = K_EggboxStealth(player->mo->x, player->mo->y);
|
|
player_t *target = NULL;
|
|
|
|
// This is a stealthy spot for an eggbox, lets reveal it!
|
|
if (stealth > 1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Check the predicted throws.
|
|
target = K_PlayerPredictThrow(player, 0);
|
|
if (target != NULL)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Check your behind.
|
|
target = K_PlayerInCone(player, coneDist, 15, true);
|
|
if (target != NULL)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemEggmanShield(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Eggman shields.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemEggmanShield(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (player->itemflags & IF_EGGMANOUT)
|
|
{
|
|
return;
|
|
}
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
if (K_BotRevealsEggbox(player) == true || (player->botvars.itemconfirm > 20*TICRATE))
|
|
{
|
|
K_BotGenericPressItem(player, cmd, 0);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemEggmanExplosion(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Eggman explosions.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemEggmanExplosion(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (player->position == 1)
|
|
{
|
|
// Hey, we aren't gonna find anyone up here...
|
|
// why don't we slow down a bit? :)
|
|
cmd->forwardmove /= 2;
|
|
}
|
|
|
|
K_BotUseItemNearPlayer(player, cmd, 128*player->mo->scale);
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemOrbinaut(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Orbinaut throwing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemOrbinaut(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t topspeed = K_GetKartSpeed(player, false, true);
|
|
fixed_t radius = FixedMul(2560 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
SINT8 throwdir = -1;
|
|
boolean tryLookback = false;
|
|
UINT8 snipeMul = 2;
|
|
player_t *target = NULL;
|
|
|
|
if (player->speed > topspeed)
|
|
{
|
|
radius = FixedMul(radius, FixedDiv(player->speed, topspeed));
|
|
snipeMul = 3; // Confirm faster when you'll throw it with a bunch of extra speed!!
|
|
}
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
target = K_PlayerInCone(player, radius, 15, false);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul);
|
|
throwdir = 1;
|
|
}
|
|
else
|
|
{
|
|
target = K_PlayerInCone(player, radius, 15, true);
|
|
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty);
|
|
throwdir = -1;
|
|
tryLookback = true;
|
|
}
|
|
}
|
|
|
|
if (tryLookback == true && throwdir == -1)
|
|
{
|
|
cmd->buttons |= BT_LOOKBACK;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > 25*TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, throwdir);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemBallhog(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Ballhog throwing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemBallhog(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t topspeed = K_GetKartSpeed(player, false, true);
|
|
fixed_t radius = FixedMul(2560 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
SINT8 throwdir = -1;
|
|
boolean tryLookback = false;
|
|
UINT8 snipeMul = 2;
|
|
player_t *target = NULL;
|
|
boolean hold = false;
|
|
|
|
if (player->speed > topspeed)
|
|
{
|
|
radius = FixedMul(radius, FixedDiv(player->speed, topspeed));
|
|
snipeMul = 3; // Confirm faster when you'll throw it with a bunch of extra speed!!
|
|
}
|
|
|
|
target = K_PlayerInCone(player, radius, 15, false);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul);
|
|
throwdir = 1;
|
|
}
|
|
else
|
|
{
|
|
target = K_PlayerInCone(player, radius, 15, true);
|
|
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty);
|
|
throwdir = -1;
|
|
tryLookback = true;
|
|
}
|
|
}
|
|
|
|
if (tryLookback == true && throwdir == -1)
|
|
{
|
|
cmd->buttons |= BT_LOOKBACK;
|
|
}
|
|
|
|
if (target != NULL)
|
|
{
|
|
// Charge up!
|
|
hold = true;
|
|
}
|
|
else
|
|
{
|
|
// If we lose sight of the target, then we'll just
|
|
// let go and it'll do a partial-blast.
|
|
|
|
// If we've been waiting for too long though, then
|
|
// we'll go for the full charge :)
|
|
cmd->bot.itemconfirm++;
|
|
hold = (player->botvars.itemconfirm > 10*TICRATE);
|
|
}
|
|
|
|
if (hold == true)
|
|
{
|
|
cmd->throwdir = KART_FULLTURN * throwdir;
|
|
cmd->buttons |= BT_ATTACK;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemDropTarget(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
|
|
Item usage for Drop Target throwing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
turnamt - How hard they currently are turning.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemDropTarget(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t topspeed = K_GetKartSpeed(player, false, true);
|
|
fixed_t radius = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
SINT8 throwdir = -1;
|
|
boolean tryLookback = false;
|
|
UINT8 snipeMul = 2;
|
|
player_t *target = NULL;
|
|
|
|
if (player->speed > topspeed)
|
|
{
|
|
radius = FixedMul(radius, FixedDiv(player->speed, topspeed));
|
|
snipeMul = 3; // Confirm faster when you'll throw it with a bunch of extra speed!!
|
|
}
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
if (abs(turnamt) >= KART_FULLTURN/2)
|
|
{
|
|
cmd->bot.itemconfirm += player->botvars.difficulty / 2;
|
|
throwdir = -1;
|
|
}
|
|
|
|
target = K_PlayerInCone(player, radius, 15, false);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul);
|
|
throwdir = 1;
|
|
}
|
|
else
|
|
{
|
|
target = K_PlayerInCone(player, radius, 15, true);
|
|
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty);
|
|
throwdir = -1;
|
|
tryLookback = true;
|
|
}
|
|
}
|
|
|
|
if (tryLookback == true && throwdir == -1)
|
|
{
|
|
cmd->buttons |= BT_LOOKBACK;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > 10*TICRATE || player->bananadrag >= TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, throwdir);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemJawz(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Jawz throwing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemJawz(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t topspeed = K_GetKartSpeed(player, false, true);
|
|
fixed_t radius = FixedMul(2560 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
SINT8 throwdir = 1;
|
|
boolean tryLookback = false;
|
|
UINT8 snipeMul = 2;
|
|
INT32 lastTarg = player->lastjawztarget;
|
|
player_t *target = NULL;
|
|
|
|
if (player->speed > topspeed)
|
|
{
|
|
radius = FixedMul(radius, FixedDiv(player->speed, topspeed));
|
|
snipeMul = 3; // Confirm faster when you'll throw it with a bunch of extra speed!!
|
|
}
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
target = K_PlayerInCone(player, radius, 15, true);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty);
|
|
throwdir = -1;
|
|
tryLookback = true;
|
|
}
|
|
|
|
if (lastTarg != -1
|
|
&& playeringame[lastTarg] == true
|
|
&& players[lastTarg].spectator == false
|
|
&& players[lastTarg].mo != NULL
|
|
&& P_MobjWasRemoved(players[lastTarg].mo) == false)
|
|
{
|
|
mobj_t *targMo = players[lastTarg].mo;
|
|
mobj_t *mobj = NULL, *next = NULL;
|
|
boolean targettedAlready = false;
|
|
|
|
target = &players[lastTarg];
|
|
|
|
// Make sure no other Jawz are targetting this player.
|
|
for (mobj = trackercap; mobj; mobj = next)
|
|
{
|
|
next = mobj->itnext;
|
|
|
|
if (mobj->type == MT_JAWZ && mobj->target == targMo)
|
|
{
|
|
targettedAlready = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (targettedAlready == false)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul);
|
|
throwdir = 1;
|
|
}
|
|
}
|
|
|
|
if (tryLookback == true && throwdir == -1)
|
|
{
|
|
cmd->buttons |= BT_LOOKBACK;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > 25*TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, throwdir);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemLightning(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Lightning Shield.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemLightning(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (K_BotUseItemNearPlayer(player, cmd, 192*player->mo->scale) == false)
|
|
{
|
|
if (player->botvars.itemconfirm > 10*TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, 0);
|
|
}
|
|
else
|
|
{
|
|
cmd->bot.itemconfirm++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemBubble(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Bubble Shield.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemBubble(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
boolean hold = false;
|
|
|
|
if (player->bubbleblowup <= 0)
|
|
{
|
|
UINT8 i;
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
if (player->bubblecool <= 0)
|
|
{
|
|
const fixed_t radius = 192 * player->mo->scale;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
player_t *target = NULL;
|
|
fixed_t dist = INT32_MAX;
|
|
|
|
if (!playeringame[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
target = &players[i];
|
|
|
|
if (target->mo == NULL || P_MobjWasRemoved(target->mo)
|
|
|| player == target || target->spectator
|
|
|| target->flashing)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
dist = P_AproxDistance(P_AproxDistance(
|
|
player->mo->x - target->mo->x,
|
|
player->mo->y - target->mo->y),
|
|
(player->mo->z - target->mo->z) / 4
|
|
);
|
|
|
|
if (dist <= radius)
|
|
{
|
|
hold = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (player->bubbleblowup >= bubbletime)
|
|
{
|
|
if (player->botvars.itemconfirm > 10*TICRATE)
|
|
{
|
|
hold = true;
|
|
}
|
|
}
|
|
else if (player->bubbleblowup < bubbletime)
|
|
{
|
|
hold = true;
|
|
}
|
|
|
|
if (hold && (player->itemflags & IF_HOLDREADY))
|
|
{
|
|
cmd->buttons |= BT_ATTACK;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemFlame(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for Flame Shield.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemFlame(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (player->botvars.itemconfirm > 0)
|
|
{
|
|
cmd->bot.itemconfirm--;
|
|
}
|
|
else if (player->itemflags & IF_HOLDREADY)
|
|
{
|
|
INT32 flamemax = player->flamelength;
|
|
|
|
if (player->flamemeter < flamemax || flamemax == 0)
|
|
{
|
|
cmd->buttons |= BT_ATTACK;
|
|
}
|
|
else
|
|
{
|
|
//player->botvars.itemconfirm = 3*flamemax/4;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemGardenTopDeploy(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for deploying the Garden Top.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemGardenTopDeploy(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
//if (player->curshield != KSHIELD_TOP)
|
|
if (player->botvars.itemconfirm > 2*TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, 0);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemGardenTop(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
|
|
Item usage for Garden Top movement.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
turnamt - How hard they currently are turning.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemGardenTop(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t topspeed = K_GetKartSpeed(player, false, true);
|
|
fixed_t radius = FixedMul(2560 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed));
|
|
SINT8 throwdir = -1;
|
|
UINT8 snipeMul = 1;
|
|
player_t *target = NULL;
|
|
|
|
if (player->speed > topspeed)
|
|
{
|
|
radius = FixedMul(radius, FixedDiv(player->speed, topspeed));
|
|
snipeMul = 2; // Confirm faster when you'll throw it with a bunch of extra speed!!
|
|
}
|
|
|
|
cmd->bot.itemconfirm++;
|
|
|
|
target = K_PlayerInCone(player, radius, 15, false);
|
|
if (target != NULL)
|
|
{
|
|
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul);
|
|
throwdir = 1;
|
|
}
|
|
|
|
if (player->topdriftheld > 0)
|
|
{
|
|
// Grinding in place.
|
|
// Wait until we're mostly done turning.
|
|
// Cancel early if we hit max thrust speed.
|
|
if ((abs(turnamt) >= KART_FULLTURN/8)
|
|
&& (player->topdriftheld <= GARDENTOP_MAXGRINDTIME))
|
|
{
|
|
cmd->buttons |= BT_DRIFT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const angle_t maxDelta = ANGLE_11hh;
|
|
angle_t delta = AngleDelta(player->mo->angle, K_MomentumAngle(player->mo));
|
|
|
|
if (delta > maxDelta)
|
|
{
|
|
// Do we need to turn? Start grinding!
|
|
cmd->buttons |= BT_DRIFT;
|
|
}
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > 25*TICRATE)
|
|
{
|
|
K_BotGenericPressItem(player, cmd, throwdir);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemRings(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for rings.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemRings(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
INT32 saferingsval = 16 - K_GetKartRingPower(player, false);
|
|
|
|
if (leveltime < starttime)
|
|
{
|
|
// Don't use rings during POSITION!!
|
|
return;
|
|
}
|
|
|
|
if ((cmd->buttons & BT_ACCELERATE) == 0)
|
|
{
|
|
// Don't use rings if you're not trying to accelerate.
|
|
return;
|
|
}
|
|
|
|
if (P_IsObjectOnGround(player->mo) == false)
|
|
{
|
|
// Don't use while mid-air.
|
|
return;
|
|
}
|
|
|
|
if (player->speed < (K_GetKartSpeed(player, false, true) * 9) / 10 // Being slowed down too much
|
|
|| player->speedboost > (FRACUNIT/5)) // Have another type of boost (tethering)
|
|
{
|
|
saferingsval -= 5;
|
|
}
|
|
|
|
if (player->rings > saferingsval)
|
|
{
|
|
cmd->buttons |= BT_ATTACK;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemInstashield(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for instashield.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemInstashield(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const fixed_t radius = FixedMul(mobjinfo[MT_INSTAWHIP].radius, player->mo->scale);
|
|
size_t i = SIZE_MAX;
|
|
|
|
boolean nearbyThreat = false; // Someone's near enough to worry about, start charging.
|
|
boolean attackOpportunity = false; // Someone's close enough to hit!
|
|
boolean coastIsClear = true; // Nobody is nearby, let any pending charge go.
|
|
|
|
UINT8 stupidRating = MAXBOTDIFFICULTY - player->botvars.difficulty;
|
|
// Weak bots take a second to react on offense.
|
|
UINT8 reactiontime = stupidRating;
|
|
// Weak bots misjudge their attack range. Purely accurate at Lv.MAX, 250% overestimate at Lv.1
|
|
fixed_t radiusWithError = radius + 3*(radius * stupidRating / MAXBOTDIFFICULTY)/2;
|
|
|
|
// Future work: Expand threat range versus fast pursuers.
|
|
|
|
if (leveltime < starttime || player->spindash || player->defenseLockout)
|
|
{
|
|
// Instashield is on cooldown.
|
|
return;
|
|
}
|
|
|
|
// Find players within the instashield's range.
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
player_t *target = NULL;
|
|
fixed_t dist = INT32_MAX;
|
|
|
|
if (!playeringame[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
target = &players[i];
|
|
if (P_MobjWasRemoved(target->mo) == true
|
|
|| player == target
|
|
|| target->spectator == true
|
|
|| target->flashing != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
dist = P_AproxDistance(P_AproxDistance(
|
|
player->mo->x - target->mo->x,
|
|
player->mo->y - target->mo->y),
|
|
(player->mo->z - target->mo->z) / 4
|
|
);
|
|
|
|
if (dist <= 8 * radius)
|
|
{
|
|
coastIsClear = false;
|
|
}
|
|
|
|
if (dist <= 5 * radius)
|
|
{
|
|
nearbyThreat = true;
|
|
}
|
|
|
|
if (dist <= (radiusWithError + target->mo->radius))
|
|
{
|
|
attackOpportunity = true;
|
|
K_ItemConfirmForTarget(player, cmd, target, 1);
|
|
}
|
|
}
|
|
|
|
if (player->instaWhipCharge) // Already charging, do we stay committed?
|
|
{
|
|
cmd->buttons |= BT_ATTACK; // Keep holding, unless...
|
|
|
|
// ...there are no attackers that are even distantly threatening...
|
|
if (coastIsClear)
|
|
cmd->buttons &= ~BT_ATTACK;
|
|
|
|
// ...or we're ready to rock.
|
|
if (attackOpportunity && player->instaWhipCharge >= (INSTAWHIP_CHARGETIME + reactiontime) && player->botvars.itemconfirm >= reactiontime)
|
|
cmd->buttons &= ~BT_ATTACK;
|
|
}
|
|
else // When should we get spooked and start a charge?
|
|
{
|
|
if (nearbyThreat)
|
|
cmd->buttons |= BT_ATTACK;
|
|
}
|
|
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemIceCube(player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for ice cubes.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemIceCube(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (leveltime % 7)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (player->sneakertimer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!P_IsObjectOnGround(player->mo))
|
|
{
|
|
return;
|
|
}
|
|
|
|
cmd->buttons |= BT_DRIFT;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static tic_t K_BotItemRouletteMashConfirm(const player_t *player)
|
|
|
|
How long this bot waits before selecting an item for
|
|
the item roulette.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
|
|
Return:-
|
|
Time to wait, in tics.
|
|
--------------------------------------------------*/
|
|
static tic_t K_BotItemRouletteMashConfirm(const player_t *player)
|
|
{
|
|
// 24 tics late for Lv.1, frame-perfect for Lv.MAX
|
|
return (MAXBOTDIFFICULTY - player->botvars.difficulty) * 2;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_BotItemRouletteMash(const player_t *player, ticcmd_t *cmd)
|
|
|
|
Item usage for item roulette mashing.
|
|
|
|
Input Arguments:-
|
|
player - Bot to do this for.
|
|
cmd - Bot's ticcmd to edit.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_BotItemRouletteMash(const player_t *player, ticcmd_t *cmd)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const tic_t confirmTime = K_BotItemRouletteMashConfirm(player);
|
|
|
|
if (K_ItemButtonWasDown(player) == true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (player->botvars.itemconfirm > confirmTime)
|
|
{
|
|
// We've waited out our reaction time -- press the button now!
|
|
cmd->buttons |= BT_ATTACK;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
void K_BotItemUsage(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
void K_BotItemUsage(const player_t *player, ticcmd_t *cmd, INT16 turnamt)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (player->icecube.frozen)
|
|
{
|
|
K_BotItemIceCube(player, cmd);
|
|
return;
|
|
}
|
|
|
|
if (player->itemflags & IF_USERINGS)
|
|
{
|
|
if (player->rings > 0)
|
|
{
|
|
// Use rings!
|
|
K_BotItemRings(player, cmd);
|
|
}
|
|
else
|
|
{
|
|
// Use the instashield!
|
|
K_BotItemInstashield(player, cmd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (player->botvars.itemdelay)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (player->itemRoulette.active == true)
|
|
{
|
|
// Mashing behaviors
|
|
K_BotItemRouletteMash(player, cmd);
|
|
return;
|
|
}
|
|
|
|
if (player->stealingtimer == 0)
|
|
{
|
|
if (player->eggmanexplode)
|
|
{
|
|
K_BotItemEggmanExplosion(player, cmd);
|
|
}
|
|
else if (player->itemflags & IF_EGGMANOUT)
|
|
{
|
|
K_BotItemEggman(player, cmd);
|
|
}
|
|
else if (player->rocketsneakertimer > 0)
|
|
{
|
|
K_BotItemRocketSneaker(player, cmd);
|
|
}
|
|
else
|
|
{
|
|
switch (player->itemtype)
|
|
{
|
|
default:
|
|
if (player->itemtype != KITEM_NONE)
|
|
{
|
|
K_BotItemGenericTap(player, cmd);
|
|
}
|
|
|
|
//player->botvars.itemconfirm = 0;
|
|
break;
|
|
case KITEM_INVINCIBILITY:
|
|
case KITEM_SPB:
|
|
case KITEM_GROW:
|
|
case KITEM_SHRINK:
|
|
case KITEM_SUPERRING:
|
|
K_BotItemGenericTap(player, cmd);
|
|
break;
|
|
case KITEM_ROCKETSNEAKER:
|
|
if (player->rocketsneakertimer <= 0)
|
|
{
|
|
K_BotItemGenericTap(player, cmd);
|
|
}
|
|
break;
|
|
case KITEM_SNEAKER:
|
|
K_BotItemSneaker(player, cmd);
|
|
break;
|
|
case KITEM_BANANA:
|
|
if (!(player->itemflags & IF_ITEMOUT))
|
|
{
|
|
K_BotItemGenericTrapShield(player, cmd, turnamt, false);
|
|
}
|
|
else
|
|
{
|
|
K_BotItemBanana(player, cmd, turnamt);
|
|
}
|
|
break;
|
|
case KITEM_EGGMAN:
|
|
K_BotItemEggmanShield(player, cmd);
|
|
break;
|
|
case KITEM_ORBINAUT:
|
|
if (!(player->itemflags & IF_ITEMOUT))
|
|
{
|
|
K_BotItemGenericOrbitShield(player, cmd);
|
|
}
|
|
else if (player->position != 1) // Hold onto orbiting items when in 1st :)
|
|
{
|
|
K_BotItemOrbinaut(player, cmd);
|
|
}
|
|
break;
|
|
case KITEM_JAWZ:
|
|
if (!(player->itemflags & IF_ITEMOUT))
|
|
{
|
|
K_BotItemGenericOrbitShield(player, cmd);
|
|
}
|
|
else if (player->position != 1) // Hold onto orbiting items when in 1st :)
|
|
{
|
|
K_BotItemJawz(player, cmd);
|
|
}
|
|
break;
|
|
case KITEM_MINE:
|
|
if (!(player->itemflags & IF_ITEMOUT))
|
|
{
|
|
K_BotItemGenericTrapShield(player, cmd, turnamt, true);
|
|
}
|
|
else
|
|
{
|
|
K_BotItemMine(player, cmd, turnamt);
|
|
}
|
|
break;
|
|
case KITEM_LANDMINE:
|
|
case KITEM_HYUDORO: // Function re-use, as they have about the same usage.
|
|
K_BotItemLandmine(player, cmd, turnamt);
|
|
break;
|
|
case KITEM_BALLHOG:
|
|
K_BotItemBallhog(player, cmd);
|
|
break;
|
|
case KITEM_DROPTARGET:
|
|
if (!(player->itemflags & IF_ITEMOUT))
|
|
{
|
|
K_BotItemGenericTrapShield(player, cmd, turnamt, false);
|
|
}
|
|
else
|
|
{
|
|
K_BotItemDropTarget(player, cmd, turnamt);
|
|
}
|
|
break;
|
|
case KITEM_GARDENTOP:
|
|
if (player->curshield != KSHIELD_TOP)
|
|
{
|
|
K_BotItemGardenTopDeploy(player, cmd);
|
|
}
|
|
else
|
|
{
|
|
K_BotItemGardenTop(player, cmd, turnamt);
|
|
}
|
|
break;
|
|
case KITEM_LIGHTNINGSHIELD:
|
|
K_BotItemLightning(player, cmd);
|
|
break;
|
|
case KITEM_BUBBLESHIELD:
|
|
K_BotItemBubble(player, cmd);
|
|
break;
|
|
case KITEM_FLAMESHIELD:
|
|
K_BotItemFlame(player, cmd);
|
|
break;
|
|
/*
|
|
case KITEM_GACHABOM:
|
|
K_BotItemGachabom(player, cmd);
|
|
break;
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player)
|
|
|
|
Thinker function used by K_UpdateBotGameplayVarsItemUsage for
|
|
deterimining item rolls.
|
|
--------------------------------------------------*/
|
|
static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player)
|
|
{
|
|
const tic_t confirmTime = K_BotItemRouletteMashConfirm(player);
|
|
|
|
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(
|
|
static_cast<kartitems_t>( player->itemRoulette.itemList[ player->itemRoulette.index ] )
|
|
);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
void K_UpdateBotGameplayVarsItemUsage(player_t *player)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
void K_UpdateBotGameplayVarsItemUsage(player_t *player)
|
|
{
|
|
if (player->itemflags & IF_USERINGS && !player->instaWhipCharge)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (player->botvars.itemdelay)
|
|
{
|
|
player->botvars.itemdelay--;
|
|
player->botvars.itemconfirm = 0;
|
|
return;
|
|
}
|
|
|
|
if (player->cmd.bot.itemconfirm < 0 && abs(player->cmd.bot.itemconfirm) > player->botvars.itemconfirm)
|
|
{
|
|
player->botvars.itemconfirm = 0;
|
|
}
|
|
else
|
|
{
|
|
player->botvars.itemconfirm += player->cmd.bot.itemconfirm;
|
|
}
|
|
|
|
if (player->itemflags & IF_USERINGS)
|
|
{
|
|
;
|
|
}
|
|
else
|
|
{
|
|
if (player->itemRoulette.active == true)
|
|
{
|
|
// Mashing behaviors
|
|
K_UpdateBotGameplayVarsItemUsageMash(player);
|
|
return;
|
|
}
|
|
|
|
if (player->stealingtimer == 0)
|
|
{
|
|
if (player->eggmanexplode)
|
|
{
|
|
;
|
|
}
|
|
else if (player->itemflags & IF_EGGMANOUT)
|
|
{
|
|
;
|
|
}
|
|
else if (player->rocketsneakertimer > 0)
|
|
{
|
|
;
|
|
}
|
|
else
|
|
{
|
|
switch (player->itemtype)
|
|
{
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
case KITEM_FLAMESHIELD:
|
|
{
|
|
if (player->botvars.itemconfirm == 0
|
|
&& (player->itemflags & IF_HOLDREADY) == IF_HOLDREADY)
|
|
{
|
|
INT32 flamemax = player->flamelength;
|
|
|
|
if (player->flamemeter < flamemax || flamemax == 0)
|
|
{
|
|
;
|
|
}
|
|
else
|
|
{
|
|
player->botvars.itemconfirm = (3 * flamemax / 4) + (TICRATE / 2);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
void K_BotPickItemPriority(player_t *player)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
void K_BotPickItemPriority(player_t *player)
|
|
{
|
|
ZoneScoped;
|
|
|
|
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( static_cast<kartitems_t>( player->itemRoulette.itemList[i] ) );
|
|
|
|
if (priority < BOT_ITEM_PR__OVERRIDES)
|
|
{
|
|
// Not high enough to override.
|
|
continue;
|
|
}
|
|
|
|
if (priority == BOT_ITEM_PR_RINGDEBT)
|
|
{
|
|
if (player->rings > 0)
|
|
{
|
|
// Only consider this priority when in ring debt.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
player->botvars.roulettePriority = std::max( static_cast<botItemPriority_e>( 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;
|
|
}
|
|
}
|