mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
532 lines
14 KiB
C++
532 lines
14 KiB
C++
// Bot.cpp
|
|
//
|
|
|
|
#include "precompiled.h"
|
|
#include "../Game_local.h"
|
|
|
|
idCVar bot_pathdebug( "bot_pathdebug", "0", CVAR_BOOL | CVAR_CHEAT, "force the bot to path to player" );
|
|
idCVar bot_goaldist( "bot_goaldist", "20", CVAR_INTEGER | CVAR_CHEAT, "" );
|
|
idCVar bot_debugnav( "bot_debugnav", "0", CVAR_BOOL | CVAR_CHEAT, "draws navmesh paths for the bot" );
|
|
idCVar bot_showstate( "bot_showstate", "0", CVAR_BOOL | CVAR_CHEAT, "draws the bot state above the bot" );
|
|
idCVar bot_debug( "bot_debug", "0", CVAR_BOOL, "shows debug info for the bot" );
|
|
idCVar bot_skill("bot_skill", "4", CVAR_INTEGER, "");
|
|
|
|
|
|
static int BotSafeAmmoCount( const idInventory& inv, const char* ammoName ) {
|
|
const int ammoNum = idWeapon::GetAmmoNumForName( ammoName );
|
|
if( ammoNum < 0 || ammoNum >= AMMO_NUMTYPES ) {
|
|
return 0;
|
|
}
|
|
return inv.ammo[ammoNum];
|
|
}
|
|
|
|
static bool BotValidGameEntityNum( int entityNum ) {
|
|
return entityNum >= 0 && entityNum < gameLocal.num_entities && gameLocal.entities[entityNum] != NULL;
|
|
}
|
|
|
|
static bool BotDirectPathIsClear( int passEntityNum, const idVec3& from, const idVec3& to ) {
|
|
trace_t trace;
|
|
gameLocal.Trace( trace, from, to, CONTENTS_SOLID | CONTENTS_PLAYERCLIP, passEntityNum );
|
|
return trace.fraction >= 0.95f;
|
|
}
|
|
|
|
static idVec3 bot_stuckOrigin[MAX_CLIENTS];
|
|
static float bot_stuckTime[MAX_CLIENTS];
|
|
static bool bot_stuckInit[MAX_CLIENTS];
|
|
|
|
CLASS_DECLARATION( idPlayer, rvmBot )
|
|
END_CLASS
|
|
|
|
/*
|
|
===================
|
|
rvmBot::rvmBot
|
|
===================
|
|
*/
|
|
rvmBot::rvmBot()
|
|
{
|
|
//bs.action = NULL;
|
|
hasSpawned = false;
|
|
//gameLocal.RegisterBot( this );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
rvmBot::~rvmBot
|
|
===================
|
|
*/
|
|
rvmBot::~rvmBot()
|
|
{
|
|
//gameLocal.UnRegisterBot( this );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::SetEnemy
|
|
==================
|
|
*/
|
|
void rvmBot::SetEnemy( idPlayer* player, idVec3 origin)
|
|
{
|
|
if( player == NULL || player == this || player->health <= 0 || player->spectating )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool noEnemy = bs.enemy < 0 || !BotValidGameEntityNum( bs.enemy ) || EntityIsDead( gameLocal.entities[bs.enemy] );
|
|
const bool sameEnemy = bs.enemy == player->entityNumber;
|
|
|
|
if( noEnemy || sameEnemy )
|
|
{
|
|
bs.enemy = player->entityNumber;
|
|
bs.aggressiveAttackTime = gameLocal.SysScriptTime() + 2.0f;
|
|
bs.lastenemyorigin = origin;
|
|
bs.last_enemy_visible_position = origin;
|
|
stateThread.SetState("state_Attacked");
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotUpdateInventory
|
|
==================
|
|
*/
|
|
void rvmBot::BotUpdateInventory( void )
|
|
{
|
|
bs.inventory[INVENTORY_ARMOR] = inventory.armor;
|
|
bs.inventory[INVENTORY_GAUNTLET] = 1;
|
|
bs.inventory[INVENTORY_SHOTGUN] = HasWeapon( weapon_shotgun );
|
|
bs.inventory[INVENTORY_MACHINEGUN] = HasWeapon( weapon_machinegun );
|
|
bs.inventory[INVENTORY_GRENADELAUNCHER] = 0;
|
|
bs.inventory[INVENTORY_ROCKETLAUNCHER] = HasWeapon( weapon_rocketlauncher );
|
|
bs.inventory[INVENTORY_LIGHTNING] = 0;
|
|
bs.inventory[INVENTORY_RAILGUN] = 0;
|
|
bs.inventory[INVENTORY_PLASMAGUN] = HasWeapon( weapon_plasmagun );
|
|
bs.inventory[INVENTORY_BFG10K] = 0;
|
|
bs.inventory[INVENTORY_GRAPPLINGHOOK] = 0;
|
|
bs.inventory[INVENTORY_SHELLS] = BotSafeAmmoCount( inventory, "ammo_shells" );
|
|
bs.inventory[INVENTORY_BULLETS] = BotSafeAmmoCount( inventory, "ammo_clip" );
|
|
bs.inventory[INVENTORY_GRENADES] = 0;
|
|
bs.inventory[INVENTORY_CELLS] = BotSafeAmmoCount( inventory, "ammo_cells" );
|
|
bs.inventory[INVENTORY_LIGHTNINGAMMO] = 0;
|
|
bs.inventory[INVENTORY_ROCKETS] = BotSafeAmmoCount( inventory, "ammo_rockets" );
|
|
bs.inventory[INVENTORY_SLUGS] = 0;
|
|
bs.inventory[INVENTORY_BFGAMMO] = 0;
|
|
bs.inventory[INVENTORY_HEALTH] = health;
|
|
bs.inventory[INVENTORY_TELEPORTER] = 0;
|
|
bs.inventory[INVENTORY_MEDKIT] = 0;
|
|
bs.inventory[INVENTORY_QUAD] = 0;
|
|
bs.inventory[INVENTORY_ENVIRONMENTSUIT] = 0;
|
|
bs.inventory[INVENTORY_HASTE] = 0;
|
|
bs.inventory[INVENTORY_INVISIBILITY] = 0;
|
|
bs.inventory[INVENTORY_REGEN] = 0;
|
|
bs.inventory[INVENTORY_FLIGHT] = 0;
|
|
bs.inventory[INVENTORY_REDFLAG] = 0;
|
|
bs.inventory[INVENTORY_BLUEFLAG] = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
rvmBot::Spawn
|
|
===================
|
|
*/
|
|
void rvmBot::Spawn( void )
|
|
{
|
|
idStr botName;
|
|
char filename[256];
|
|
int errnum;
|
|
|
|
stateThread.SetOwner(this);
|
|
|
|
idPlayer::Spawn();
|
|
|
|
if( gameLocal.isServer )
|
|
{
|
|
weapon_machinegun = SlotForWeapon( "weapon_machinegun" );
|
|
weapon_shotgun = SlotForWeapon( "weapon_shotgun" );
|
|
weapon_plasmagun = SlotForWeapon( "weapon_plasmagun" );
|
|
weapon_rocketlauncher = SlotForWeapon( "weapon_rocketlauncher" );
|
|
|
|
WP_MACHINEGUN = weapon_machinegun;
|
|
WP_SHOTGUN = weapon_shotgun;
|
|
WP_PLASMAGUN = weapon_plasmagun;
|
|
WP_ROCKET_LAUNCHER = weapon_rocketlauncher;
|
|
|
|
botName = "dark";// spawnArgs.GetString("botname");
|
|
|
|
aas = gameLocal.GetBotAAS();
|
|
if( aas == NULL )
|
|
{
|
|
gameLocal.Error( "Missing AAS\n" );
|
|
return;
|
|
}
|
|
|
|
// Load in the bot character.
|
|
const int skill = idMath::ClampInt( 1, 5, bot_skill.GetInteger() );
|
|
bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( va( "bots/%s_c.c", botName.c_str() ), skill );
|
|
if( bs.character == NULL && skill != 4 )
|
|
{
|
|
bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( va( "bots/%s_c.c", botName.c_str() ), 4 );
|
|
}
|
|
if( bs.character == NULL && skill != 1 )
|
|
{
|
|
bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( va( "bots/%s_c.c", botName.c_str() ), 1 );
|
|
}
|
|
if( !bs.character )
|
|
{
|
|
gameLocal.Error( "Failed to load character file for bot %s\n", botName.c_str() );
|
|
}
|
|
|
|
// Allocate the goal state.
|
|
bs.gs = botGoalManager.BotAllocGoalState( entityNumber );
|
|
|
|
// Get the bot items weights file name.
|
|
botCharacterStatsManager.Characteristic_String( bs.character, CHARACTERISTIC_ITEMWEIGHTS, filename, 256 );
|
|
errnum = botGoalManager.BotLoadItemWeights( bs.gs, filename );
|
|
if( errnum != BLERR_NOERROR )
|
|
{
|
|
gameLocal.Error( "Failed to load bot item weights!" );
|
|
botGoalManager.BotFreeGoalState( bs.gs );
|
|
return;
|
|
}
|
|
|
|
//allocate a weapon state
|
|
bs.ws = botWeaponInfoManager.BotAllocWeaponState();
|
|
|
|
//load the weapon weights
|
|
botCharacterStatsManager.Characteristic_String( bs.character, CHARACTERISTIC_WEAPONWEIGHTS, filename, 256 );
|
|
errnum = botWeaponInfoManager.BotLoadWeaponWeights( bs.ws, filename );
|
|
if( errnum != BLERR_NOERROR )
|
|
{
|
|
// trap_BotFreeGoalState(bs->gs);
|
|
botWeaponInfoManager.BotFreeWeaponState( bs.ws );
|
|
return;
|
|
}
|
|
|
|
bs.client = entityNumber;
|
|
bs.entitynum = entityNumber;
|
|
bs.setupcount = 4;
|
|
bs.entergame_time = Bot_Time();
|
|
bs.weaponnum = spawnArgs.GetInt( "current_weapon", "1" );
|
|
|
|
hasSpawned = true;
|
|
|
|
bs.botinput.respawn = true;
|
|
bs.botinput.weapon = bs.weaponnum;
|
|
bs.botinput.lastWeaponNum = -1;
|
|
|
|
stateThread.SetState("state_Respawn");
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
rvmBot::Think
|
|
===================
|
|
*/
|
|
void rvmBot::BotMoveToGoalOrigin(idVec3 goalOrigin)
|
|
{
|
|
idVec3 moveDir = goalOrigin - GetPhysics()->GetOrigin();
|
|
|
|
// Usercmd forward/right movement is horizontal. Feeding pitch or a view-origin
|
|
// z delta into the movement vector makes the bot under-steer, drift, or push
|
|
// into walls while looking up/down at a target.
|
|
moveDir[2] = 0.0f;
|
|
|
|
if( moveDir.LengthSqr() < Square( 4.0f ) )
|
|
{
|
|
bs.botinput.dir = idVec3( 0.0f, 0.0f, 0.0f );
|
|
bs.botinput.speed = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
bs.botinput.dir = moveDir;
|
|
bs.botinput.dir.Normalize();
|
|
bs.botinput.speed = pm_runspeed.GetInteger();
|
|
}
|
|
|
|
if( bs.enemy >= 0 )
|
|
{
|
|
// Combat states set bs.viewangles through BotAimAtEnemy. Do not replace that
|
|
// with a simple eye-to-eye angle here, or prediction/ground-rocket aiming is lost.
|
|
bs.botinput.viewangles = bs.viewangles;
|
|
}
|
|
else if( moveDir.LengthSqr() > Square( 1.0f ) )
|
|
{
|
|
bs.botinput.viewangles = idAngles( 0.0f, moveDir.ToYaw(), 0.0f );
|
|
bs.viewangles = bs.botinput.viewangles;
|
|
}
|
|
else
|
|
{
|
|
bs.botinput.viewangles = viewAngles;
|
|
bs.viewangles = viewAngles;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
rvmBot::SpawnToPoint
|
|
===================
|
|
*/
|
|
void rvmBot::SpawnToPoint( const idVec3& spawn_origin, const idAngles& spawn_angles )
|
|
{
|
|
idPlayer::SpawnToPoint( spawn_origin, spawn_angles );
|
|
|
|
if( gameLocal.isServer )
|
|
{
|
|
bs.ltg_time = 0;
|
|
stateThread.SetState("state_SeekLTG");
|
|
}
|
|
}
|
|
/*
|
|
===================
|
|
rvmBot::StateThreadChanged
|
|
===================
|
|
*/
|
|
void rvmBot::StateThreadChanged(void) {
|
|
// Ensure if we are switching states, pop the last goal.
|
|
bs.ltg_time = 0;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
rvmBot::ServerThink
|
|
===================
|
|
*/
|
|
void rvmBot::ServerThink( void )
|
|
{
|
|
bs.origin = GetPhysics()->GetOrigin();
|
|
bs.eye = GetEyePosition();
|
|
bs.thinktime = Bot_Time();
|
|
bs.botinput.actionflags = 0;
|
|
bs.botinput.dir = idVec3( 0.0f, 0.0f, 0.0f );
|
|
bs.botinput.speed = 0.0f;
|
|
bs.botinput.viewangles = viewAngles;
|
|
bs.viewangles = viewAngles;
|
|
|
|
BotUpdateInventory();
|
|
|
|
if( bot_pathdebug.IsModified() )
|
|
{
|
|
idPlayer* localPlayer = gameLocal.GetLocalPlayer();
|
|
if( bot_pathdebug.GetBool() && localPlayer != NULL )
|
|
{
|
|
bs.currentGoal.origin = localPlayer->GetPhysics()->GetOrigin();
|
|
bs.currentGoal.framenum = gameLocal.framenum;
|
|
}
|
|
|
|
bot_pathdebug.ClearModified();
|
|
bot_pathdebug.SetBool( false );
|
|
}
|
|
|
|
stateThread.Execute();
|
|
|
|
if( BotIsDead( &bs ) || spectating || aas == NULL )
|
|
{
|
|
bs.botinput.weapon = bs.weaponnum;
|
|
bs.attackerEntity = NULL;
|
|
return;
|
|
}
|
|
|
|
if( bs.enemy >= 0 && bs.botinput.speed > 0.0f )
|
|
{
|
|
bs.viewangles = bs.botinput.viewangles;
|
|
bs.useRandomPosition = false;
|
|
bs.attackerEntity = NULL;
|
|
bs.botinput.weapon = bs.weaponnum;
|
|
return;
|
|
}
|
|
|
|
idVec3 goalOrigin = bs.useRandomPosition ? bs.random_move_position : bs.currentGoal.origin;
|
|
idVec3 moveGoal = goalOrigin;
|
|
bool haveMoveGoal = false;
|
|
|
|
if( goalOrigin.LengthSqr() > Square( 1.0f ) )
|
|
{
|
|
aasPath_t path;
|
|
idVec3 org = bs.origin;
|
|
int curAreaNum = aas->AdjustPositionAndGetArea( org );
|
|
idVec3 adjustedGoal = goalOrigin;
|
|
int goalArea = aas->PointAreaNum( adjustedGoal );
|
|
|
|
if( goalArea <= 0 )
|
|
{
|
|
goalArea = aas->AdjustPositionAndGetArea( adjustedGoal );
|
|
}
|
|
|
|
if( bot_debug.GetBool() )
|
|
{
|
|
aas->ShowWalkPath( GetOrigin(), goalArea, adjustedGoal );
|
|
aas->ShowArea( GetOrigin() );
|
|
}
|
|
|
|
if( curAreaNum > 0 && goalArea > 0 && aas->WalkPathToGoal( path, curAreaNum, org, goalArea, adjustedGoal, TFL_WALK | TFL_AIR ) )
|
|
{
|
|
moveGoal = path.moveGoal;
|
|
haveMoveGoal = true;
|
|
}
|
|
else if( BotDirectPathIsClear( entityNumber, bs.eye, adjustedGoal + idVec3( 0.0f, 0.0f, 24.0f ) ) )
|
|
{
|
|
moveGoal = adjustedGoal;
|
|
haveMoveGoal = true;
|
|
}
|
|
}
|
|
|
|
// If the selected route is not producing movement, invalidate the current path
|
|
// and choose a small local AAS point. This prevents repeatedly pushing into the
|
|
// same wall or corner when the item/chase goal is stale.
|
|
if( haveMoveGoal )
|
|
{
|
|
const int stuckIndex = entityNumber >= 0 && entityNumber < MAX_CLIENTS ? entityNumber : 0;
|
|
if( !bot_stuckInit[stuckIndex] || ( bs.origin - bot_stuckOrigin[stuckIndex] ).LengthSqr() > Square( 8.0f ) )
|
|
{
|
|
bot_stuckInit[stuckIndex] = true;
|
|
bot_stuckOrigin[stuckIndex] = bs.origin;
|
|
bot_stuckTime[stuckIndex] = Bot_Time();
|
|
}
|
|
else if( Bot_Time() - bot_stuckTime[stuckIndex] > 1.25f )
|
|
{
|
|
bs.ltg_time = 0;
|
|
bs.nbg_time = 0;
|
|
BotGetRandomPointNearPosition( bs.origin, bs.random_move_position, 96.0f );
|
|
bs.useRandomPosition = true;
|
|
moveGoal = bs.random_move_position;
|
|
bot_stuckOrigin[stuckIndex] = bs.origin;
|
|
bot_stuckTime[stuckIndex] = Bot_Time();
|
|
}
|
|
|
|
BotMoveToGoalOrigin( moveGoal );
|
|
}
|
|
else
|
|
{
|
|
bs.botinput.dir = idVec3( 0.0f, 0.0f, 0.0f );
|
|
bs.botinput.speed = 0.0f;
|
|
bs.botinput.viewangles = bs.viewangles;
|
|
}
|
|
|
|
bs.viewangles = bs.botinput.viewangles;
|
|
bs.useRandomPosition = false;
|
|
bs.attackerEntity = NULL;
|
|
bs.botinput.weapon = bs.weaponnum;
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
rvmBot::Damage
|
|
=======================
|
|
*/
|
|
void rvmBot::Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location )
|
|
{
|
|
idPlayer::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
|
|
|
|
idPlayer* player = attacker != NULL ? attacker->Cast<idPlayer>() : NULL;
|
|
if( health <= 0 )
|
|
{
|
|
if( player != NULL )
|
|
{
|
|
BotSendChatMessage( DEATH, player->netname );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if( player == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
bs.attackerEntity = player;
|
|
SetEnemy( player, player->GetOrigin() );
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
rvmBot::InflictedDamageEvent
|
|
=======================
|
|
*/
|
|
void rvmBot::InflictedDamageEvent(idEntity* target) {
|
|
idPlayer* player = target != NULL ? target->Cast<idPlayer>() : NULL;
|
|
|
|
// Don't flood the chat with death and insults.
|
|
if( player != NULL && !player->IsBot() && player->health <= 0 )
|
|
{
|
|
BotSendChatMessage(KILL, player->netname);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
rvmBot::PresenceTypeBoundingBox
|
|
=======================
|
|
*/
|
|
void rvmBot::PresenceTypeBoundingBox( int presencetype, idVec3& mins, idVec3& maxs )
|
|
{
|
|
int index;
|
|
//bounding box size for each presence type
|
|
//idVec3 boxmins[3] = { {0, 0, 0}, {-15, -15, -24}, {-15, -15, -24} };
|
|
//idVec3 boxmaxs[3] = { {0, 0, 0}, { 15, 15, 32}, { 15, 15, 8} };
|
|
|
|
idVec3 boxmins[3];
|
|
idVec3 boxmaxs[3];
|
|
|
|
boxmins[0] = idVec3( 0, 0, 0 );
|
|
boxmins[1] = idVec3( -15, -15, -24 );
|
|
boxmins[2] = idVec3( -15, -15, -24 );
|
|
|
|
boxmaxs[0] = idVec3( 0, 0, 0 );
|
|
boxmaxs[1] = idVec3( 15, 15, 32 );
|
|
boxmaxs[2] = idVec3( 15, 15, 8 );
|
|
|
|
|
|
if( presencetype == PRESENCE_NORMAL )
|
|
{
|
|
index = 1;
|
|
}
|
|
else if( presencetype == PRESENCE_CROUCH )
|
|
{
|
|
index = 2;
|
|
}
|
|
else
|
|
{
|
|
//botimport.Print(PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n");
|
|
index = 2;
|
|
}
|
|
mins = boxmins[index];
|
|
maxs = boxmaxs[index];
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
rvmBot::Think
|
|
===================
|
|
*/
|
|
void rvmBot::Think( void )
|
|
{
|
|
if( !hasSpawned )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( gameLocal.isServer )
|
|
{
|
|
ServerThink();
|
|
|
|
if( bot_debug.GetBool() )
|
|
{
|
|
idVec4 color;
|
|
color = idVec4(1, 1, 1, 1);
|
|
if (bs.enemy >= 0)
|
|
color = idVec4(1, 0, 0, 1);
|
|
|
|
idBounds bounds = idBounds( idVec3( -10, -10, -10 ), idVec3( 10, 10, 10 ) );
|
|
gameRenderWorld->DebugBounds( color, bounds, GetOrigin() );
|
|
|
|
idMat3 axis = viewAngles.ToMat3();
|
|
gameRenderWorld->DrawTextA(stateThread.GetState()->state.c_str(), GetOrigin(), 1.0f, color, axis);
|
|
}
|
|
}
|
|
|
|
deltaViewAngles.Zero();
|
|
|
|
idPlayer::Think();
|
|
}
|