You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2437 lines
72 KiB
2437 lines
72 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// The copyright to the contents herein is the property of Valve, L.L.C. |
|
// The contents may be used and/or copied only with the written permission of |
|
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in |
|
// the agreement/contract under which the contents have been supplied. |
|
// |
|
// Purpose: Basic BOT handling. |
|
// |
|
// $Workfile: $ |
|
// $Date: $ |
|
// |
|
//----------------------------------------------------------------------------- |
|
// $Log: $ |
|
// |
|
// $NoKeywords: $ |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
#include "player.h" |
|
#include "tf_player.h" |
|
#include "tf_gamerules.h" |
|
#include "in_buttons.h" |
|
#include "movehelper_server.h" |
|
#include "tf_playerclass_shared.h" |
|
#include "datacache/imdlcache.h" |
|
#include "serverbenchmark_base.h" |
|
#include "econ_entity_creation.h" |
|
#include "nav_mesh.h" |
|
#include "nav_pathfind.h" |
|
#include "tf_team.h" |
|
#include "tf_obj.h" |
|
#include "player.h" |
|
|
|
#ifdef STAGING_ONLY |
|
#include "econ_item_system.h" |
|
#endif // STAGING_ONLY |
|
|
|
void InitBotTrig( void ); |
|
void ClientPutInServer( edict_t *pEdict, const char *playername ); |
|
void Bot_Think( CTFPlayer *pBot ); |
|
|
|
ConVar bot_debug( "bot_debug", "0", FCVAR_CHEAT, "Bot debugging." ); |
|
#define DbgBotMsg( pszMsg ) \ |
|
do \ |
|
{ \ |
|
if ( bot_debug.GetBool() ) \ |
|
DevMsg( "[Bot] %s", static_cast<const char *>(pszMsg) ); \ |
|
} while (0) |
|
|
|
|
|
ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." ); |
|
ConVar bot_forceattack( "bot_forceattack", "0", 0, "When on, all bots fire their guns." ); |
|
ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." ); |
|
ConVar bot_forceattack_down( "bot_forceattack_down", "1", 0, "When firing, don't tap fire, hold it down." ); |
|
ConVar bot_changeclass( "bot_changeclass", "0", 0, "Force all bots to change to the specified class." ); |
|
ConVar bot_dontmove( "bot_dontmove", "0", FCVAR_CHEAT ); |
|
ConVar bot_saveme( "bot_saveme", "0", FCVAR_CHEAT ); |
|
static ConVar bot_mimic_inverse( "bot_mimic_inverse", "0", 0, "Bot uses usercmd of player by index." ); |
|
static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "180", 0, "Offsets the bot yaw." ); |
|
ConVar bot_selectweaponslot( "bot_selectweaponslot", "-1", FCVAR_CHEAT, "set to weapon slot that bot should switch to." ); |
|
ConVar bot_randomnames( "bot_randomnames", "0", FCVAR_CHEAT ); |
|
ConVar bot_jump( "bot_jump", "0", FCVAR_CHEAT, "Force all bots to repeatedly jump." ); |
|
ConVar bot_crouch( "bot_crouch", "0", FCVAR_CHEAT, "Force all bots to crouch." ); |
|
|
|
ConVar bot_nav_turnspeed( "bot_nav_turnspeed", "5", FCVAR_CHEAT, "Rate at which bots turn to face their targets." ); |
|
ConVar bot_nav_wpdistance( "bot_nav_wpdistance", "16", FCVAR_CHEAT, "Distance to a waypoint within which a bot considers as having reached it." ); |
|
ConVar bot_nav_wpdeceldistance( "bot_nav_wpdeceldistance", "128", FCVAR_CHEAT, "Distance to a waypoint to which a bot starts to decelerate to reach it." ); |
|
ConVar bot_nav_simplifypaths( "bot_nav_simplifypaths", "1", FCVAR_CHEAT, "If set, bots will skip waypoints if they already see the waypoint post." ); |
|
ConVar bot_nav_useoffsetpaths( "bot_nav_useoffsetpaths", "1", FCVAR_CHEAT, "If set, bots will generate waypoints on both sides of portals between waypoints when building paths." ); |
|
ConVar bot_nav_offsetpathinset( "bot_nav_offsetpathinset", "20", FCVAR_CHEAT, "Distance into an area that waypoints should be generated when pathfinding through portals." ); |
|
ConVar bot_nav_usefeelers( "bot_nav_usefeelers", "1", FCVAR_CHEAT, "If set, bots will extend feelers to their sides to find & avoid upcoming collisions." ); |
|
ConVar bot_nav_recomputetime( "bot_nav_recomputetime", "0.5", FCVAR_CHEAT, "Delay before bots recompute their path to targets that have moved when moving to them." ); |
|
|
|
ConVar bot_com_meleerange( "bot_com_meleerange", "80", FCVAR_CHEAT, "Distance to a target that a melee bot wants to be within to attack." ); |
|
ConVar bot_com_wpnrange( "bot_com_wpnrange", "400", FCVAR_CHEAT, "Distance to a target that a ranged bot wants to be within to attack." ); |
|
ConVar bot_com_viewrange( "bot_com_viewrange", "2000", FCVAR_CHEAT, "Distance within which bots looking for any enemies will find them." ); |
|
|
|
extern ConVar bot_mimic; |
|
extern ConVar sv_usercmd_custom_random_seed; |
|
|
|
// Utility function to get the specified bot from the command arguments |
|
void GetBotsFromCommand( const CCommand &args, int iArgsRequired, const char *pszUsage, CUtlVector< CTFPlayer* > *botVector ) |
|
{ |
|
if ( !botVector) |
|
return; |
|
|
|
if ( args.ArgC() < iArgsRequired ) |
|
{ |
|
Msg( "Too few parameters. %s\n", pszUsage ); |
|
return; |
|
} |
|
|
|
const char *pszBotName = args[1]; |
|
|
|
if ( FStrEq( pszBotName, "all" ) ) |
|
{ |
|
CUtlVector< CTFPlayer* > playerVector; |
|
CollectPlayers( &playerVector ); |
|
for ( int i=0; i<playerVector.Count(); ++i ) |
|
{ |
|
if ( playerVector[i]->IsFakeClient() ) |
|
{ |
|
botVector->AddToTail( playerVector[i] ); |
|
} |
|
} |
|
return; |
|
} |
|
|
|
// get the bot's player object |
|
CTFPlayer *pBot = ToTFPlayer( UTIL_PlayerByName( pszBotName ) ); |
|
if ( !pBot || !pBot->IsFakeClient() ) |
|
{ |
|
Msg( "No bot with name %s\n", args[1] ); |
|
return; |
|
} |
|
|
|
botVector->AddToTail( pBot ); |
|
} |
|
|
|
static int BotNumber = 1; |
|
static int g_iNextBotTeam = -1; |
|
static int g_iNextBotClass = -1; |
|
|
|
//=================================================================================================================== |
|
// Purpose: Mapmaker bot control entity. Used by mapmakers to add & script bot behaviors. |
|
class CTFBotController : public CPointEntity |
|
{ |
|
DECLARE_CLASS( CTFBotController, CPointEntity ); |
|
public: |
|
DECLARE_DATADESC(); |
|
|
|
// Input. |
|
void InputCreateBot( inputdata_t &inputdata ); |
|
void InputRespawnBot( inputdata_t &inputdata ); |
|
void InputBotAddCommandMoveToEntity( inputdata_t &inputdata ); |
|
void InputBotAddCommandAttackEntity( inputdata_t &inputdata ); |
|
void InputBotAddCommandSwitchWeapon( inputdata_t &inputdata ); |
|
void InputBotAddCommandDefend( inputdata_t &inputdata ); |
|
void InputBotSetIgnoreHumans( inputdata_t &inputdata ); |
|
void InputBotPreventMovement( inputdata_t &inputdata ); |
|
void InputBotClearQueue( inputdata_t &inputdata ); |
|
|
|
public: |
|
COutputEvent m_outputOnCommandFinished; // Fired when the entity is done respawning the players. |
|
CHandle<CTFPlayer> m_hBot; |
|
string_t m_iszBotName; |
|
int m_iBotClass; |
|
}; |
|
|
|
enum botcommands_t |
|
{ |
|
BC_NONE, |
|
BC_MOVETO_ENTITY, |
|
BC_MOVETO_POINT, |
|
BC_ATTACK, |
|
BC_SWITCH_WEAPON, |
|
BC_DEFEND, |
|
}; |
|
|
|
typedef struct botdata_t |
|
{ |
|
CHandle<CTFPlayer> m_hBot; |
|
|
|
bool backwards; |
|
|
|
float nextturntime; |
|
bool lastturntoright; |
|
|
|
float nextstrafetime; |
|
float sidemove; |
|
|
|
QAngle forwardAngle; |
|
QAngle lastAngles; |
|
|
|
float m_flJoinTeamTime; |
|
int m_WantedTeam; |
|
int m_WantedClass; |
|
|
|
bool m_bChoseWeapon; |
|
|
|
bool m_bWasDead; |
|
float m_flDeadTime; |
|
|
|
//------------------------------------------------------ |
|
// Input into the next command |
|
unsigned short buttons; |
|
|
|
//------------------------------------------------------ |
|
// Base thinking |
|
void Bot_AliveThink( QAngle *vecAngles, Vector *vecMove ); |
|
void Bot_AliveWeaponThink( QAngle *vecAngles, Vector *vecMove ); |
|
void Bot_AliveMovementThink( QAngle *vecAngles, Vector *vecMove ); |
|
void Bot_AliveMovementThink_ExtendFeelers( QAngle *vecAngles, Vector *vecMove, Vector *vecCurVelocity ); |
|
void Bot_DeadThink( QAngle *vecAngles, Vector *vecMove ); |
|
|
|
void Bot_ItemTestingThink( QAngle *vecAngles, Vector *vecMove ); |
|
|
|
//------------------------------------------------------ |
|
// Navigation |
|
bool FindPathTo( Vector vecTarget ); |
|
bool ComputePathPositions( void ); |
|
bool UpdatePath( void ); |
|
void AdvancePath( void ) |
|
{ |
|
m_iPathIndex++; |
|
|
|
if ( m_iPathIndex >= m_iPathLength ) |
|
{ |
|
if ( RunningMovementCommand() ) |
|
{ |
|
// We're not done if we're moving to an entity |
|
if ( !CommandHasATarget() && ShouldFinishCommandOnArrival() ) |
|
{ |
|
ResetPath(); |
|
FinishCommand(); |
|
} |
|
else |
|
{ |
|
// We finished our path, so find another one. |
|
m_flRecomputePathAt = 0; |
|
if ( !UpdatePath() ) |
|
{ |
|
FinishCommand(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
bool HasPath( void ) |
|
{ |
|
return (m_iPathLength > 0); |
|
} |
|
|
|
void ResetPath( void ) |
|
{ |
|
m_iPathIndex = 0; |
|
m_iPathLength = 0; |
|
} |
|
|
|
Vector m_vecFaceToTarget; |
|
enum { MAX_PATH_LENGTH = 256 }; |
|
struct ConnectInfo |
|
{ |
|
CNavArea *area; ///< the area along the path |
|
NavTraverseType how; ///< how to enter this area from the previous one |
|
Vector pos; ///< our movement goal position at this point in the path |
|
float flOffset; |
|
}; |
|
ConnectInfo m_path[MAX_PATH_LENGTH]; |
|
int m_iPathLength; |
|
int m_iPathIndex; |
|
CNavArea *m_lastKnownArea; |
|
float m_flRecomputePathAt; |
|
Vector m_vecLastPathTarget; |
|
|
|
//------------------------------------------------------ |
|
// Sensing |
|
void SetIgnoreHumans( bool bIgnore ) { m_bIgnoreHumans = bIgnore; } |
|
void SetFrozen( bool bFrozen ) { if ( bFrozen ) m_hBot->AddEFlags( EFL_BOT_FROZEN ); else m_hBot->RemoveEFlags( EFL_BOT_FROZEN ); } |
|
bool FindEnemyTarget( void ); |
|
bool ValidTargetPlayer( CTFPlayer *pPlayer, const Vector &vecStart, const Vector &vecEnd ); |
|
bool ValidTargetObject( CBaseObject *pObject, const Vector &vecStart, const Vector &vecEnd ); |
|
|
|
EHANDLE m_hEnemy; |
|
bool m_bIgnoreHumans; |
|
|
|
//------------------------------------------------------ |
|
// Command Queue |
|
void UpdatePlanForCommand( void ); |
|
void StartNewCommand( void ); |
|
void FinishCommand( void ); |
|
void RunAttackPlan( void ); |
|
void RunDefendPlan( void ); |
|
|
|
void AddAttackCommand( CBaseEntity *pTarget ); |
|
void AddMoveToCommand( CBaseEntity *pTarget, Vector vecTarget ); |
|
void AddSwitchWeaponCommand( int iSlot ); |
|
void AddDefendCommand( float flRange ); |
|
void ClearQueue( void ); |
|
|
|
bool RunningMovementCommand( void ); |
|
bool CommandHasATarget( void ); |
|
bool ShouldFinishCommandOnArrival( void ); |
|
|
|
CBaseEntity *GetCommandTarget( void ) { return m_Commands.Count() ? m_Commands[0].pTarget : NULL; } |
|
Vector GetCommandPosition( void ) { return m_Commands.Count() ? m_Commands[0].vecTarget : vec3_origin; } |
|
|
|
struct CommandInfo |
|
{ |
|
botcommands_t iCommand; |
|
EHANDLE pTarget; |
|
Vector vecTarget; |
|
float flData; |
|
}; |
|
CUtlVector<CommandInfo> m_Commands; |
|
bool m_bStartedCommand; |
|
CHandle<CTFBotController> m_hBotController; |
|
} botdata_t; |
|
|
|
static botdata_t g_BotData[ MAX_PLAYERS ]; |
|
|
|
inline botdata_t *BotData( CBasePlayer *pBot ) |
|
{ |
|
return &g_BotData[ pBot->entindex() - 1 ]; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Create a new Bot and put it in the game. |
|
// Output : Pointer to the new Bot, or NULL if there's no free clients. |
|
//----------------------------------------------------------------------------- |
|
CBasePlayer *BotPutInServer( bool bTargetDummy, bool bFrozen, int iTeam, int iClass, const char *pszCustomName ) |
|
{ |
|
InitBotTrig(); |
|
|
|
g_iNextBotTeam = iTeam; |
|
g_iNextBotClass = iClass; |
|
|
|
char botname[ 64 ]; |
|
if ( pszCustomName && pszCustomName[0] ) |
|
{ |
|
Q_strncpy( botname, pszCustomName, sizeof( botname ) ); |
|
} |
|
else if ( bot_randomnames.GetBool() ) |
|
{ |
|
switch( g_pServerBenchmark->RandomInt(0,5) ) |
|
{ |
|
case 0: Q_snprintf( botname, sizeof( botname ), "Bot" ); break; |
|
case 1: Q_snprintf( botname, sizeof( botname ), "This is a medium Bot" ); break; |
|
case 2: Q_snprintf( botname, sizeof( botname ), "This is a super long bot name that is too long for the game to allow" ); break; |
|
case 3: Q_snprintf( botname, sizeof( botname ), "Another bot" ); break; |
|
case 4: Q_snprintf( botname, sizeof( botname ), "Yet more Bot names, medium sized" ); break; |
|
default: Q_snprintf( botname, sizeof( botname ), "B" ); break; |
|
} |
|
} |
|
else |
|
{ |
|
Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber ); |
|
} |
|
|
|
edict_t *pEdict = engine->CreateFakeClient( botname ); |
|
if (!pEdict) |
|
{ |
|
Msg( "Failed to create Bot.\n"); |
|
return NULL; |
|
} |
|
|
|
// Allocate a CBasePlayer for the bot, and call spawn |
|
//ClientPutInServer( pEdict, botname ); |
|
CTFPlayer *pPlayer = ((CTFPlayer *)CBaseEntity::Instance( pEdict )); |
|
pPlayer->ClearFlags(); |
|
pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT ); |
|
pPlayer->SetPlayerType( CTFPlayer::TEMP_BOT ); |
|
|
|
if ( bFrozen ) |
|
{ |
|
pPlayer->AddEFlags( EFL_BOT_FROZEN ); |
|
} |
|
|
|
if ( bTargetDummy ) |
|
{ |
|
pPlayer->SetTargetDummy(); |
|
} |
|
|
|
BotNumber++; |
|
|
|
botdata_t *pBot = BotData(pPlayer); |
|
pBot->m_hBot = pPlayer; |
|
pBot->m_bWasDead = false; |
|
pBot->m_WantedTeam = iTeam; |
|
pBot->m_WantedClass = iClass; |
|
pBot->m_bChoseWeapon = false; |
|
pBot->m_flJoinTeamTime = gpGlobals->curtime + 0.3; |
|
pBot->m_vecFaceToTarget.Zero(); |
|
pBot->m_lastKnownArea = NULL; |
|
pBot->m_flRecomputePathAt = 0; |
|
pBot->ResetPath(); |
|
pBot->m_bIgnoreHumans = false; |
|
pBot->m_bStartedCommand = false; |
|
|
|
return pPlayer; |
|
} |
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
CON_COMMAND_F( bot_kick, "Remove a bot by name, or an entire team (\"red\" or \"blue\"), or all bots (\"all\").", FCVAR_CHEAT ) |
|
{ |
|
// Listenserver host or rcon access only! |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
if ( args.ArgC() != 2 ) |
|
{ |
|
DevMsg( "%s <bot name>, \"red\", \"blue\", or \"all\"\n", args.Arg(0) ); |
|
return; |
|
} |
|
|
|
for( int i = 1; i <= gpGlobals->maxClients; ++i ) |
|
{ |
|
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); |
|
|
|
if ( !player ) |
|
continue; |
|
|
|
if ( FNullEnt( player->edict() ) ) |
|
continue; |
|
|
|
if ( player->GetFlags() & FL_FAKECLIENT ) |
|
{ |
|
if ( FStrEq( args.Arg( 1 ), "all" ) || |
|
FStrEq( args.Arg( 1 ), player->GetPlayerName() ) || |
|
( FStrEq( args.Arg( 1 ), "red" ) && player->GetTeamNumber() == TF_TEAM_RED ) || |
|
( FStrEq( args.Arg( 1 ), "blue" ) && player->GetTeamNumber() == TF_TEAM_BLUE ) ) |
|
{ |
|
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Handler for the "bot" command. |
|
CON_COMMAND_F( bot, "Add a bot.", FCVAR_NONE ) |
|
{ |
|
// Listenserver host or rcon access only! |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
// Allow bots in item testing mode |
|
if ( !sv_cheats->GetBool() && !TFGameRules()->IsInItemTestingMode() ) |
|
return; |
|
|
|
//CDODPlayer *pPlayer = CDODPlayer::Instance( UTIL_GetCommandClientIndex() ); |
|
|
|
// The bot command uses switches like command-line switches. |
|
// -count <count> tells how many bots to spawn. |
|
// -team <index> selects the bot's team. Default is -1 which chooses randomly. |
|
// Note: if you do -team !, then it |
|
// -class <index> selects the bot's class. Default is -1 which chooses randomly. |
|
// -frozen prevents the bots from running around when they spawn in. |
|
// -name sets the bot's name |
|
// -targetdummy sets their health to 1,000,000. Useful for testing damage via hud_damagemeter. |
|
// -teleport teleports the bot to the location the player is pointing at |
|
|
|
// Look at -count. |
|
int count = args.FindArgInt( "-count", 1 ); |
|
count = clamp( count, 1, 16 ); |
|
|
|
if ( args.FindArg( "-all" ) ) |
|
{ |
|
count = 9; |
|
} |
|
|
|
// Look at -frozen. |
|
bool bFrozen = !!args.FindArg( "-frozen" ); |
|
bool bTargetDummy = !!args.FindArg( "-targetdummy" ); |
|
bool bTeleport = !!args.FindArg( "-teleport" ); |
|
|
|
// Ok, spawn all the bots. |
|
while ( --count >= 0 ) |
|
{ |
|
// What class do they want? |
|
int iClass = g_pServerBenchmark->RandomInt( 1, TF_CLASS_COUNT-2 ); // -2 so we skip the Civilian class |
|
char const *pVal = args.FindArg( "-class" ); |
|
if ( pVal ) |
|
{ |
|
for ( int i=1; i < TF_CLASS_COUNT_ALL; i++ ) |
|
{ |
|
if ( !Q_stricmp(pVal, g_aPlayerClassNames_NonLocalized[i] ) ) |
|
{ |
|
iClass = i; |
|
break; |
|
} |
|
} |
|
} |
|
if ( args.FindArg( "-all" ) ) |
|
{ |
|
iClass = 9 - count ; |
|
} |
|
|
|
int iTeam = TEAM_UNASSIGNED; |
|
pVal = args.FindArg( "-team" ); |
|
if ( pVal ) |
|
{ |
|
if ( stricmp( pVal, "red" ) == 0 ) |
|
{ |
|
iTeam = TF_TEAM_RED; |
|
} |
|
else if ( stricmp( pVal, "spectator" ) == 0 ) |
|
{ |
|
iTeam = TEAM_SPECTATOR; |
|
} |
|
else if ( stricmp( pVal, "random" ) == 0 ) |
|
{ |
|
iTeam = g_pServerBenchmark->RandomInt( 0, 100 ) < 50 ? TF_TEAM_BLUE : TF_TEAM_RED; |
|
} |
|
else |
|
{ |
|
iTeam = TF_TEAM_BLUE; |
|
} |
|
} |
|
|
|
char const *pName = args.FindArg( "-name" ); |
|
|
|
CBasePlayer *pTemp = BotPutInServer( bTargetDummy, bFrozen, iTeam, iClass, pName ); |
|
|
|
if ( bTeleport && pTemp ) |
|
{ |
|
CTFPlayer *pBot = ToTFPlayer( pTemp ); |
|
CBasePlayer *pPlayer = UTIL_GetCommandClient(); |
|
if ( pPlayer && pBot ) |
|
{ |
|
trace_t tr; |
|
Vector forward; |
|
|
|
pPlayer->EyeVectors( &forward ); |
|
UTIL_TraceLine( pPlayer->EyePosition(), |
|
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_NPCSOLID, |
|
pPlayer, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction != 1.0 ) |
|
{ |
|
botdata_t *pBotData = BotData( pBot ); |
|
if ( pBotData ) |
|
{ |
|
pBotData->m_flJoinTeamTime = gpGlobals->curtime - 0.1; |
|
} |
|
|
|
const char *pszTeam = "auto"; |
|
switch ( pBotData->m_WantedTeam ) |
|
{ |
|
case TF_TEAM_RED: |
|
pszTeam = "red"; |
|
break; |
|
case TF_TEAM_BLUE: |
|
pszTeam = "blue"; |
|
break; |
|
case TEAM_SPECTATOR: |
|
pszTeam = "spectator"; |
|
break; |
|
default: |
|
pszTeam = "auto"; |
|
break; |
|
} |
|
pBot->HandleCommand_JoinTeam( pszTeam ); |
|
|
|
const char *pszClassname = ( pBotData->m_WantedClass == TF_CLASS_RANDOM ) ? "random" : GetPlayerClassData( pBotData->m_WantedClass )->m_szClassName; |
|
pBot->HandleCommand_JoinClass( pszClassname ); |
|
|
|
pBot->ForceRespawn(); |
|
pBot->Teleport( &tr.endpos, NULL, NULL ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
CON_COMMAND_F( bot_hurt, "Hurt a bot by team, or all bots (\"all\").", FCVAR_GAMEDLL ) |
|
{ |
|
// Listenserver host or rcon access only! |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
if ( args.ArgC() < 2 ) |
|
{ |
|
DevMsg( "%s (-team <red/blue/all>>) (-name <name>) (-damage <int>) (-burn)\n", args.Arg(0) ); |
|
return; |
|
} |
|
|
|
int nDamage = 50; |
|
const char* pszDamage = args.FindArg( "-damage" ); |
|
if( pszDamage ) |
|
{ |
|
nDamage = atoi(pszDamage); |
|
} |
|
|
|
bool bBurn = args.FindArg( "-burn" ) != nullptr; |
|
|
|
// Figure out which team if specified |
|
int iTeam = TEAM_UNASSIGNED; |
|
const char* pVal = args.FindArg( "-team" ); |
|
if ( pVal ) |
|
{ |
|
if ( stricmp( pVal, "red" ) == 0 ) |
|
{ |
|
iTeam = TF_TEAM_RED; |
|
} |
|
else if ( stricmp( pVal, "blue" ) == 0 ) |
|
{ |
|
iTeam = TF_TEAM_BLUE; |
|
} |
|
else if ( stricmp( pVal, "all" ) == 0 ) |
|
{ |
|
iTeam = TEAM_ANY; |
|
} |
|
else |
|
{ |
|
iTeam = TEAM_ANY; |
|
} |
|
} |
|
|
|
// Get the name if the put one in |
|
pVal = args.FindArg( "-name" ); |
|
const char *pPlayerName = ""; |
|
if( pVal ) |
|
{ |
|
pPlayerName = pVal; |
|
} |
|
|
|
for( int i=1; i<=gpGlobals->maxClients; ++i ) |
|
{ |
|
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); |
|
|
|
if ( !player ) |
|
continue; |
|
|
|
if ( FNullEnt( player->edict() ) ) |
|
continue; |
|
|
|
if ( player->GetFlags() & FL_FAKECLIENT ) |
|
{ |
|
if ( iTeam == TEAM_ANY || |
|
FStrEq( pPlayerName, player->GetPlayerName() ) || |
|
( player->GetTeamNumber() == iTeam ) || |
|
( player->GetTeamNumber() == iTeam ) ) |
|
{ |
|
player->m_iHealth -= nDamage; |
|
|
|
if ( bBurn ) |
|
{ |
|
CTFPlayer *pBot = ToTFPlayer( player ); |
|
pBot->m_Shared.Burn( nullptr, nullptr, 10.0f ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
inline bool isTempBot( CTFPlayer* pPlayer ) |
|
{ |
|
return ( pPlayer && ( pPlayer->GetFlags() & FL_FAKECLIENT ) && pPlayer->GetPlayerType() == CTFPlayer::TEMP_BOT ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Run through all the Bots in the game and let them think. |
|
//----------------------------------------------------------------------------- |
|
void Bot_RunAll( void ) |
|
{ |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); |
|
|
|
if ( isTempBot( pPlayer ) && pPlayer->MyNextBotPointer() == NULL ) |
|
{ |
|
Bot_Think( pPlayer ); |
|
} |
|
} |
|
} |
|
|
|
bool RunMimicCommand( CUserCmd& cmd ) |
|
{ |
|
if ( bot_mimic.GetInt() <= 0 ) |
|
return false; |
|
|
|
if ( bot_mimic.GetInt() > gpGlobals->maxClients ) |
|
return false; |
|
|
|
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() ); |
|
if ( !pPlayer ) |
|
return false; |
|
|
|
if ( !pPlayer->GetLastUserCommand() ) |
|
return false; |
|
|
|
cmd = *pPlayer->GetLastUserCommand(); |
|
cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat(); |
|
|
|
if ( bot_mimic_inverse.GetBool() ) |
|
{ |
|
cmd.forwardmove *= -1; |
|
cmd.sidemove *= -1; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Simulates a single frame of movement for a player |
|
// Input : *fakeclient - |
|
// *viewangles - |
|
// forwardmove - |
|
// sidemove - |
|
// upmove - |
|
// buttons - |
|
// impulse - |
|
// msec - |
|
// Output : virtual void |
|
//----------------------------------------------------------------------------- |
|
static void RunPlayerMove( CTFPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime ) |
|
{ |
|
if ( !fakeclient ) |
|
return; |
|
|
|
CUserCmd cmd; |
|
|
|
// Store off the globals.. they're gonna get whacked |
|
float flOldFrametime = gpGlobals->frametime; |
|
float flOldCurtime = gpGlobals->curtime; |
|
|
|
float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime; |
|
fakeclient->SetTimeBase( flTimeBase ); |
|
|
|
Q_memset( &cmd, 0, sizeof( cmd ) ); |
|
|
|
if ( !RunMimicCommand( cmd ) ) |
|
{ |
|
VectorCopy( viewangles, cmd.viewangles ); |
|
cmd.forwardmove = forwardmove; |
|
cmd.sidemove = sidemove; |
|
cmd.upmove = upmove; |
|
cmd.buttons = buttons; |
|
cmd.impulse = impulse; |
|
cmd.random_seed = g_pServerBenchmark->RandomInt( 0, 0x7fffffff ); |
|
if ( sv_usercmd_custom_random_seed.GetBool() ) |
|
{ |
|
float fltTimeNow = float( Plat_FloatTime() * 1000.0 ); |
|
cmd.server_random_seed = *reinterpret_cast<int*>( (char*)&fltTimeNow ); |
|
} |
|
else |
|
{ |
|
cmd.server_random_seed = cmd.random_seed; |
|
} |
|
} |
|
|
|
if ( bot_dontmove.GetBool() ) |
|
{ |
|
cmd.forwardmove = 0; |
|
cmd.sidemove = 0; |
|
cmd.upmove = 0; |
|
} |
|
else |
|
{ |
|
float flStunAmount = fakeclient->m_Shared.GetAmountStunned( TF_STUN_MOVEMENT ); |
|
if ( flStunAmount ) |
|
{ |
|
cmd.forwardmove *= (1.0 - flStunAmount); |
|
cmd.sidemove *= (1.0 - flStunAmount); |
|
} |
|
} |
|
|
|
if ( fakeclient->m_Shared.IsControlStunned() || fakeclient->m_Shared.IsLoserStateStunned() ) |
|
{ |
|
cmd.weaponselect = 0; |
|
cmd.buttons = 0; |
|
if ( fakeclient->m_Shared.IsControlStunned() ) |
|
{ |
|
cmd.forwardmove = 0; |
|
cmd.sidemove = 0; |
|
cmd.upmove = 0; |
|
} |
|
} |
|
|
|
MoveHelperServer()->SetHost( fakeclient ); |
|
fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() ); |
|
|
|
// save off the last good usercmd |
|
fakeclient->SetLastUserCommand( cmd ); |
|
|
|
// Clear out any fixangle that has been set |
|
fakeclient->pl.fixangle = FIXANGLE_NONE; |
|
|
|
// Restore the globals.. |
|
gpGlobals->frametime = flOldFrametime; |
|
gpGlobals->curtime = flOldCurtime; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Run this Bot's AI for one frame. |
|
//----------------------------------------------------------------------------- |
|
void Bot_Think( CTFPlayer *pBot ) |
|
{ |
|
// Make sure we stay being a bot |
|
pBot->AddFlag( FL_FAKECLIENT ); |
|
|
|
botdata_t *botdata = BotData(pBot); |
|
|
|
Vector vecMove( 0, 0, 0 ); |
|
byte impulse = 0; |
|
float frametime = gpGlobals->frametime; |
|
QAngle vecViewAngles = pBot->EyeAngles(); |
|
botdata->buttons = 0; |
|
|
|
MDLCACHE_CRITICAL_SECTION(); |
|
|
|
// Create some random values |
|
if ( pBot->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime ) |
|
{ |
|
const char *pszTeam = NULL; |
|
switch ( botdata->m_WantedTeam ) |
|
{ |
|
case TF_TEAM_RED: |
|
pszTeam = "red"; |
|
break; |
|
case TF_TEAM_BLUE: |
|
pszTeam = "blue"; |
|
break; |
|
case TEAM_SPECTATOR: |
|
pszTeam = "spectator"; |
|
break; |
|
default: |
|
pszTeam = "auto"; |
|
break; |
|
} |
|
pBot->HandleCommand_JoinTeam( pszTeam ); |
|
} |
|
else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->GetPlayerClass()->IsClass( TF_CLASS_UNDEFINED ) ) |
|
{ |
|
// If they're on a team but haven't picked a class, choose a random class.. |
|
const char *pszClassname = ( botdata->m_WantedClass == TF_CLASS_RANDOM ) ? "random" : GetPlayerClassData( botdata->m_WantedClass )->m_szClassName; |
|
pBot->HandleCommand_JoinClass( pszClassname ); |
|
} |
|
else if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) ) |
|
{ |
|
botdata->Bot_AliveThink( &vecViewAngles, &vecMove ); |
|
} |
|
else |
|
{ |
|
botdata->Bot_DeadThink( &vecViewAngles, &vecMove ); |
|
} |
|
|
|
RunPlayerMove( pBot, vecViewAngles, vecMove[0], vecMove[1], vecMove[2], botdata->buttons, impulse, frametime ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle the Bot AI for a live bot |
|
//----------------------------------------------------------------------------- |
|
void botdata_t::Bot_AliveThink( QAngle *vecAngles, Vector *vecMove ) |
|
{ |
|
trace_t trace; |
|
|
|
m_bWasDead = false; |
|
|
|
// In item testing mode, we run custom logic |
|
if ( TFGameRules()->IsInItemTestingMode() ) |
|
{ |
|
Bot_ItemTestingThink( vecAngles, vecMove ); |
|
return; |
|
} |
|
|
|
UpdatePlanForCommand(); |
|
Bot_AliveMovementThink( vecAngles, vecMove ); |
|
Bot_AliveWeaponThink( vecAngles, vecMove ); |
|
|
|
// Miscellaneous |
|
if ( bot_saveme.GetInt() > 0 ) |
|
{ |
|
m_hBot->SaveMe(); |
|
bot_saveme.SetValue( bot_saveme.GetInt() - 1 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle movement |
|
//----------------------------------------------------------------------------- |
|
void botdata_t::Bot_AliveMovementThink( QAngle *vecAngles, Vector *vecMove ) |
|
{ |
|
if ( m_hBot->IsEFlagSet(EFL_BOT_FROZEN) ) |
|
{ |
|
(*vecMove)[0] = 0; |
|
(*vecMove)[1] = 0; |
|
(*vecMove)[2] = 0; |
|
return; |
|
} |
|
|
|
if ( bot_jump.GetBool() && m_hBot->GetFlags() & FL_ONGROUND ) |
|
{ |
|
buttons |= IN_JUMP; |
|
} |
|
|
|
if ( bot_crouch.GetBool() ) |
|
{ |
|
buttons |= IN_DUCK; |
|
} |
|
|
|
// If we don't have a faceto target, but we're moving to a waypoint, look at that. |
|
Vector vecFaceTo = m_vecFaceToTarget; |
|
if ( vecFaceTo == vec3_origin ) |
|
{ |
|
if ( m_hEnemy ) |
|
{ |
|
vecFaceTo = m_hEnemy->EyePosition(); |
|
} |
|
else if ( GetCommandTarget() ) |
|
{ |
|
vecFaceTo = GetCommandTarget()->EyePosition(); |
|
} |
|
else if ( HasPath() ) |
|
{ |
|
vecFaceTo = m_path[ m_iPathIndex ].pos; |
|
vecFaceTo[2] = m_hBot->EyePosition()[2]; |
|
} |
|
} |
|
// Should we face something? |
|
if ( vecFaceTo != vec3_origin ) |
|
{ |
|
Vector vecViewTarget = (vecFaceTo - m_hBot->EyePosition()); |
|
VectorNormalize(vecViewTarget); |
|
QAngle vecTargetViewAngles; |
|
VectorAngles( vecViewTarget, Vector(0,0,1), vecTargetViewAngles ); |
|
(*vecAngles)[YAW] = ApproachAngle( vecTargetViewAngles[YAW], (*vecAngles)[YAW], bot_nav_turnspeed.GetFloat() ); |
|
} |
|
|
|
// Are we moving to a waypoint? |
|
if ( HasPath() ) |
|
{ |
|
m_lastKnownArea = TheNavMesh->GetNavArea( m_hBot->GetAbsOrigin() ); |
|
if ( !UpdatePath() ) |
|
{ |
|
FinishCommand(); |
|
return; |
|
} |
|
|
|
if ( !m_Commands.Count() ) |
|
return; |
|
|
|
float flDistance = bot_nav_wpdistance.GetFloat(); |
|
CBaseEntity *pTargetEnt = m_Commands[0].pTarget; |
|
|
|
// We might run into our target entity earlier. |
|
if ( CommandHasATarget() && ShouldFinishCommandOnArrival() ) |
|
{ |
|
float flEntDistance = pTargetEnt->BoundingRadius() + flDistance; |
|
flEntDistance *= flEntDistance; |
|
if ( (pTargetEnt->GetAbsOrigin() - m_hBot->GetAbsOrigin()).Length2DSqr() < flEntDistance ) |
|
{ |
|
ResetPath(); |
|
FinishCommand(); |
|
return; |
|
} |
|
} |
|
|
|
// Have we reached the next waypoint? |
|
flDistance *= flDistance; |
|
if ( (m_path[ m_iPathIndex ].pos - m_hBot->GetAbsOrigin()).Length2DSqr() < flDistance ) |
|
{ |
|
AdvancePath(); |
|
} |
|
else if ( bot_nav_simplifypaths.GetBool() ) |
|
{ |
|
// If we can see our next waypoint already, stop moving to this one |
|
while ( m_iPathLength >= (m_iPathIndex+1) && |
|
m_iPathIndex < (m_iPathLength - 1) && |
|
m_hBot->FVisible( m_path[ m_iPathIndex+1 ].pos ) ) |
|
{ |
|
AdvancePath(); |
|
} |
|
} |
|
|
|
if ( bot_debug.GetInt() == 1 ) |
|
{ |
|
for ( int i = 0; i < m_iPathLength-1; i++ ) |
|
{ |
|
NDebugOverlay::Line( m_path[i].pos, m_path[i+1].pos, 0, i == m_iPathIndex ? 255 : 64, 0, true, 0.1 ); |
|
NDebugOverlay::Box( m_path[i].pos, -Vector(3,3,3), Vector(3,3,3), 0, i == m_iPathIndex ? 255 : 64, 0, 0, 0.1 ); |
|
} |
|
} |
|
|
|
Vector vecTarget = m_path[ m_iPathIndex ].pos; |
|
Vector vecDesiredVelocity = (vecTarget - m_hBot->GetAbsOrigin()); |
|
if ( bot_nav_usefeelers.GetBool() && m_iPathIndex < (m_iPathLength - 1) ) |
|
{ |
|
Bot_AliveMovementThink_ExtendFeelers( vecAngles, vecMove, &vecDesiredVelocity ); |
|
} |
|
flDistance = VectorNormalize( vecDesiredVelocity ); |
|
|
|
// If we're approaching our last waypoint, decelerate to the point. |
|
if ( m_iPathLength == 1 ) |
|
{ |
|
float flDecelDistance = bot_nav_wpdeceldistance.GetFloat(); |
|
float flSpeed = MIN( m_hBot->MaxSpeed() * (flDistance / flDecelDistance), m_hBot->MaxSpeed() ); |
|
vecDesiredVelocity *= flSpeed; |
|
} |
|
else |
|
{ |
|
vecDesiredVelocity *= m_hBot->MaxSpeed(); |
|
} |
|
|
|
if ( bot_debug.GetInt() == 10 ) |
|
{ |
|
NDebugOverlay::HorzArrow( m_hBot->GetAbsOrigin(), m_hBot->GetAbsOrigin() + vecDesiredVelocity, 3, 255, 0, 0, 0, true, 0.1 ); |
|
} |
|
|
|
// Convert the velocity into forward/sidemove |
|
Vector vecForward, vecRight; |
|
AngleVectors( *vecAngles, &vecForward, &vecRight, NULL ); |
|
(*vecMove)[0] = DotProduct( vecForward, vecDesiredVelocity ); |
|
(*vecMove)[1] = DotProduct( vecRight, vecDesiredVelocity ); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------------------------------------ |
|
#define COS_TABLE_SIZE 256 |
|
static float cosTable[ COS_TABLE_SIZE ]; |
|
static bool bBotTrigInitted = false; |
|
|
|
void InitBotTrig( void ) |
|
{ |
|
if ( bBotTrigInitted ) |
|
return; |
|
bBotTrigInitted = true; |
|
for( int i=0; i<COS_TABLE_SIZE; ++i ) |
|
{ |
|
float angle = (float)(2.0f * M_PI * i / (float)(COS_TABLE_SIZE-1)); |
|
cosTable[i] = (float)cos( angle ); |
|
} |
|
} |
|
|
|
float BotCOS( float angle ) |
|
{ |
|
angle = AngleNormalizePositive( angle ); |
|
int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f ); |
|
return cosTable[i]; |
|
} |
|
|
|
float BotSIN( float angle ) |
|
{ |
|
angle = AngleNormalizePositive( angle - 90 ); |
|
int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f ); |
|
return cosTable[i]; |
|
} |
|
|
|
// Find "simple" ground height, treating current nav area as part of the floor |
|
bool GetSimpleGroundHeightWithFloor( CNavArea *pArea, const Vector &pos, float *height, Vector *normal ) |
|
{ |
|
if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal )) |
|
{ |
|
// our current nav area also serves as a ground polygon |
|
if ( pArea && pArea->IsOverlapping( pos )) |
|
{ |
|
*height = MAX( (*height), pArea->GetZ( pos ) ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
// Purpose: Fire feelers out to either side and steer to avoid collisions |
|
//-------------------------------------------------------------------------------------------------------------- |
|
void botdata_t::Bot_AliveMovementThink_ExtendFeelers( QAngle *vecAngles, Vector *vecMove, Vector *vecCurVelocity ) |
|
{ |
|
VectorNormalize( *vecCurVelocity ); |
|
|
|
float flForwardAngle = UTIL_VecToYaw( *vecCurVelocity ); |
|
|
|
Vector dir( BotCOS( flForwardAngle ), BotSIN( flForwardAngle ), 0.0f ); |
|
Vector lat( -dir.y, dir.x, 0.0f ); |
|
|
|
const float feelerOffset = 20.0f; |
|
const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747) |
|
const float feelerHeight = StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it |
|
|
|
// Feelers must follow floor slope |
|
float ground; |
|
Vector normal; |
|
Vector eye = m_hBot->EyePosition(); |
|
if (GetSimpleGroundHeightWithFloor( m_lastKnownArea, eye, &ground, &normal ) == false) |
|
return; |
|
|
|
// get forward vector along floor |
|
dir = CrossProduct( lat, normal ); |
|
|
|
// correct the sideways vector |
|
lat = CrossProduct( dir, normal ); |
|
|
|
Vector feet = m_hBot->GetAbsOrigin(); |
|
feet.z += feelerHeight; |
|
|
|
Vector from = feet + feelerOffset * lat; |
|
Vector to = from + feelerLengthRun * dir; |
|
|
|
bool leftClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ); |
|
if ( bot_debug.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::Line( from, to, leftClear ? 0 : 255, leftClear ? 255 : 0, 0, true, 0.1 ); |
|
} |
|
|
|
from = feet - feelerOffset * lat; |
|
to = from + feelerLengthRun * dir; |
|
|
|
bool rightClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ); |
|
if ( bot_debug.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::Line( from, to, rightClear ? 0 : 255, rightClear ? 255 : 0, 0, true, 0.1 ); |
|
} |
|
|
|
Vector vecDebug; |
|
if ( bot_debug.GetInt() == 3 && (!leftClear || !rightClear) ) |
|
{ |
|
vecDebug = (*vecCurVelocity * 100); |
|
NDebugOverlay::Line( m_hBot->GetAbsOrigin(), m_hBot->GetAbsOrigin() + vecDebug, 0, 0, 255, true, 0.1 ); |
|
} |
|
|
|
const float avoidRange = 300.0f; // 50, 300 |
|
if (!rightClear) |
|
{ |
|
if (leftClear) |
|
{ |
|
// right hit, left clear - veer left |
|
*vecCurVelocity = (*vecCurVelocity * avoidRange) + avoidRange * lat; |
|
} |
|
} |
|
else if (!leftClear) |
|
{ |
|
// right clear, left hit - veer right |
|
*vecCurVelocity = (*vecCurVelocity * avoidRange) - avoidRange * lat; |
|
} |
|
|
|
if ( bot_debug.GetInt() == 3 && (!leftClear || !rightClear) ) |
|
{ |
|
vecDebug = (*vecCurVelocity); |
|
VectorNormalize( vecDebug ); |
|
vecDebug *= 100; |
|
NDebugOverlay::Line( m_hBot->GetAbsOrigin(), m_hBot->GetAbsOrigin() + vecDebug, 64, 64, 255, true, 0.1 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle weapon switching / firing |
|
//----------------------------------------------------------------------------- |
|
void botdata_t::Bot_AliveWeaponThink( QAngle *vecAngles, Vector *vecMove ) |
|
{ |
|
if ( bot_selectweaponslot.GetInt() >= 0 ) |
|
{ |
|
int slot = bot_selectweaponslot.GetInt(); |
|
|
|
CBaseCombatWeapon *pWpn = m_hBot->Weapon_GetSlot( slot ); |
|
|
|
if ( pWpn ) |
|
{ |
|
m_hBot->Weapon_Switch( pWpn ); |
|
} |
|
|
|
bot_selectweaponslot.SetValue( -1 ); |
|
} |
|
|
|
const char *pszWeapon = bot_forcefireweapon.GetString(); |
|
if ( (pszWeapon && pszWeapon[0]) || g_pServerBenchmark->IsBenchmarkRunning() ) |
|
{ |
|
// If bots are being forced to fire a weapon, see if I have it |
|
// Manually look through weapons to ignore subtype |
|
CBaseCombatWeapon *pWeapon = NULL; |
|
|
|
if ( g_pServerBenchmark->IsBenchmarkRunning() ) |
|
{ |
|
if ( !m_bChoseWeapon ) |
|
{ |
|
m_bChoseWeapon = true; |
|
|
|
// Choose any weapon out of the available ones. |
|
CUtlVector<CBaseCombatWeapon*> weapons; |
|
for (int i=0;i<MAX_WEAPONS;i++) |
|
{ |
|
if ( m_hBot->GetWeapon(i) ) |
|
{ |
|
weapons.AddToTail( m_hBot->GetWeapon( i ) ); |
|
} |
|
} |
|
|
|
if ( weapons.Count() > 0 ) |
|
{ |
|
pWeapon = weapons[ g_pServerBenchmark->RandomInt( 0, weapons.Count() - 1 ) ]; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Look for a specific weapon name here. |
|
for (int i=0;i<MAX_WEAPONS;i++) |
|
{ |
|
if ( m_hBot->GetWeapon(i) && FClassnameIs( m_hBot->GetWeapon(i), pszWeapon ) ) |
|
{ |
|
pWeapon = m_hBot->GetWeapon(i); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( pWeapon ) |
|
{ |
|
// Switch to it if we don't have it out |
|
CBaseCombatWeapon *pActiveWeapon = m_hBot->GetActiveWeapon(); |
|
|
|
// Switch? |
|
if ( pActiveWeapon != pWeapon ) |
|
{ |
|
m_hBot->Weapon_Switch( pWeapon ); |
|
} |
|
else |
|
{ |
|
// Start firing |
|
// Some weapons require releases, so randomise firing |
|
if ( bot_forceattack_down.GetBool() || (g_pServerBenchmark->RandomFloat(0.0,1.0) > 0.5) ) |
|
{ |
|
buttons |= IN_ATTACK; |
|
} |
|
|
|
if ( bot_forceattack2.GetBool() ) |
|
{ |
|
buttons |= IN_ATTACK2; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( bot_forceattack.GetInt() ) |
|
{ |
|
if ( bot_forceattack_down.GetBool() || (g_pServerBenchmark->RandomFloat(0.0,1.0) > 0.5) ) |
|
{ |
|
buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle the Bot AI for a dead bot |
|
//----------------------------------------------------------------------------- |
|
void botdata_t::Bot_DeadThink( QAngle *vecAngles, Vector *vecMove ) |
|
{ |
|
// Wait for Reinforcement wave |
|
if ( !m_hBot->IsAlive() ) |
|
{ |
|
if ( m_bWasDead ) |
|
{ |
|
// Wait for a few seconds before respawning. |
|
if ( gpGlobals->curtime - m_flDeadTime > 3 ) |
|
{ |
|
// Respawn the bot |
|
buttons |= IN_JUMP; |
|
} |
|
} |
|
else |
|
{ |
|
// Start a timer to respawn them in a few seconds. |
|
m_bWasDead = true; |
|
m_flDeadTime = gpGlobals->curtime; |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: sends the specified command from a bot |
|
//------------------------------------------------------------------------------ |
|
void cc_bot_sendcommand( const CCommand &args ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 3, "Usage: bot_command <bot name> <command string...>", &botVector ); |
|
if ( botVector.IsEmpty() ) |
|
return; |
|
|
|
const char *commandline = args.GetCommandString(); |
|
|
|
// find the rest of the command line past the bot index |
|
commandline = strstr( commandline, args[2] ); |
|
Assert( commandline ); |
|
|
|
int iSize = Q_strlen(commandline) + 1; |
|
char *pBuf = (char *)malloc(iSize); |
|
Q_snprintf( pBuf, iSize, "%s", commandline ); |
|
|
|
if ( pBuf[iSize-2] == '"' ) |
|
{ |
|
pBuf[iSize-2] = '\0'; |
|
} |
|
|
|
// make a command object with the intended command line |
|
CCommand command; |
|
command.Tokenize( pBuf ); |
|
|
|
// send the command |
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
TFGameRules()->ClientCommand( botVector[i], command ); |
|
} |
|
} |
|
static ConCommand bot_sendcommand( "bot_command", cc_bot_sendcommand, "<bot id> <command string...>. Sends specified command on behalf of specified bot", FCVAR_CHEAT ); |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Kill the specified bot |
|
//------------------------------------------------------------------------------ |
|
void cc_bot_kill( const CCommand &args ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 2, "Usage: bot_kill <bot name>", &botVector ); |
|
if ( botVector.IsEmpty() ) |
|
return; |
|
|
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
botVector[i]->CommitSuicide(); |
|
} |
|
} |
|
|
|
static ConCommand bot_kill( "bot_kill", cc_bot_kill, "Kills a bot. Usage: bot_kill <bot name>", FCVAR_CHEAT ); |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force all bots to swap teams |
|
//------------------------------------------------------------------------------ |
|
CON_COMMAND_F( bot_changeteams, "Make all bots change teams", FCVAR_CHEAT ) |
|
{ |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); |
|
|
|
if ( isTempBot( pPlayer ) ) |
|
{ |
|
int iTeam = pPlayer->GetTeamNumber(); |
|
if ( TF_TEAM_BLUE == iTeam || TF_TEAM_RED == iTeam ) |
|
{ |
|
// toggle team between red & blue |
|
pPlayer->ChangeTeam( TF_TEAM_BLUE + TF_TEAM_RED - iTeam ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Refill all bot ammo counts |
|
//------------------------------------------------------------------------------ |
|
CON_COMMAND_F( bot_refill, "Refill all bot ammo counts", FCVAR_CHEAT ) |
|
{ |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); |
|
|
|
if ( isTempBot( pPlayer ) ) |
|
{ |
|
pPlayer->GiveAmmo( 1000, TF_AMMO_PRIMARY ); |
|
pPlayer->GiveAmmo( 1000, TF_AMMO_SECONDARY ); |
|
pPlayer->GiveAmmo( 1000, TF_AMMO_METAL ); |
|
pPlayer->TakeHealth( 999, DMG_GENERIC ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Deliver lethal damage from the player to the specified bot |
|
//------------------------------------------------------------------------------ |
|
CON_COMMAND_F( bot_whack, "Deliver lethal damage from player to specified bot. Usage: bot_whack <bot name>", FCVAR_CHEAT ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 2, "Usage: bot_whack <bot name>", &botVector ); |
|
if ( botVector.IsEmpty() ) |
|
return; |
|
|
|
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_GetCommandClient() ); |
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
CTakeDamageInfo info( botVector[i], pTFPlayer, 1000, DMG_BULLET ); |
|
info.SetInflictor( pTFPlayer->GetActiveTFWeapon() ); |
|
botVector[i]->TakeDamage( info ); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force the specified bot to teleport to the specified position |
|
//------------------------------------------------------------------------------ |
|
CON_COMMAND_F( bot_teleport, "Teleport the specified bot to the specified position & angles.\n\tFormat: bot_teleport <bot name> <X> <Y> <Z> <Pitch> <Yaw> <Roll>", FCVAR_CHEAT ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 8, "Usage: bot_teleport <bot name> <X> <Y> <Z> <Pitch> <Yaw> <Roll>", &botVector ); |
|
if ( botVector.IsEmpty() ) |
|
return; |
|
|
|
Vector vecPos( atof(args[2]), atof(args[3]), atof(args[4]) ); |
|
QAngle vecAng( atof(args[5]), atof(args[6]), atof(args[7]) ); |
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
botVector[i]->Teleport( &vecPos, &vecAng, NULL ); |
|
} |
|
|
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force the specified bot to create & equip an item |
|
//------------------------------------------------------------------------------ |
|
void BotGenerateAndWearItem( CTFPlayer *pBot, const char *itemName ) |
|
{ |
|
if ( !pBot ) |
|
return; |
|
|
|
CItemSelectionCriteria criteria; |
|
criteria.SetItemLevel( AE_USE_SCRIPT_VALUE ); |
|
criteria.SetQuality( AE_USE_SCRIPT_VALUE ); |
|
criteria.BAddCondition( "name", k_EOperator_String_EQ, itemName, true ); |
|
|
|
CBaseEntity *pItem = ItemGeneration()->GenerateRandomItem( &criteria, pBot->GetAbsOrigin(), vec3_angle ); |
|
if ( pItem ) |
|
{ |
|
// If it's a weapon, remove the current one, and give us this one. |
|
CBaseEntity *pExisting = pBot->Weapon_OwnsThisType(pItem->GetClassname()); |
|
if ( pExisting ) |
|
{ |
|
CBaseCombatWeapon *pWpn = dynamic_cast<CBaseCombatWeapon *>(pExisting); |
|
pBot->Weapon_Detach( pWpn ); |
|
UTIL_Remove( pExisting ); |
|
} |
|
|
|
// Fake global id |
|
static int s_nFakeID = 1; |
|
static_cast<CEconEntity*>(pItem)->GetAttributeContainer()->GetItem()->SetItemID( s_nFakeID++ ); |
|
|
|
DispatchSpawn( pItem ); |
|
static_cast<CEconEntity*>(pItem)->GiveTo( pBot ); |
|
|
|
pBot->PostInventoryApplication(); |
|
} |
|
else |
|
{ |
|
#ifdef STAGING_ONLY |
|
extern ConVar tf_bot_use_items; |
|
if ( !tf_bot_use_items.GetInt() ) |
|
#endif |
|
{ |
|
Msg( "Failed to create an item named %s\n", itemName ); |
|
} |
|
} |
|
} |
|
|
|
void BotGenerateAndWearItem( CTFPlayer *pBot, CEconItemView *pItem ) |
|
{ |
|
int iClass = pBot->GetPlayerClass()->GetClassIndex(); |
|
int iItemSlot = pItem->GetStaticData()->GetLoadoutSlot( iClass ); |
|
CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase*>( pBot->GetEntityForLoadoutSlot( iItemSlot ) ); |
|
|
|
// we need to force translating the name here. |
|
// GiveNamedItem will not translate if we force creating the item |
|
const char *pTranslatedWeaponName = TranslateWeaponEntForClass( pItem->GetStaticData()->GetItemClass(), iClass ); |
|
CTFWeaponBase *pNewItem = dynamic_cast<CTFWeaponBase*>( pBot->GiveNamedItem( pTranslatedWeaponName, 0, pItem, true ) ); |
|
if ( pNewItem ) |
|
{ |
|
CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>( (CBaseEntity*)pNewItem ); |
|
if ( pBuilder ) |
|
{ |
|
pBuilder->SetSubType( pBot->GetPlayerClass()->GetData()->m_aBuildable[0] ); |
|
} |
|
|
|
// make sure we removed our current weapon |
|
if ( pWeapon ) |
|
{ |
|
pBot->Weapon_Detach( pWeapon ); |
|
UTIL_Remove( pWeapon ); |
|
} |
|
|
|
pNewItem->MarkAttachedEntityAsValidated(); |
|
pNewItem->GiveTo( pBot ); |
|
} |
|
else |
|
{ |
|
BotGenerateAndWearItem( pBot, pItem->GetItemDefinition()->GetDefinitionName() ); |
|
} |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void BotMirrorPlayerClassAndItems( CTFPlayer *pBot, CTFPlayer *pPlayer ) |
|
{ |
|
if ( !pBot || !pPlayer ) |
|
return; |
|
|
|
// Force them to be our class |
|
if ( pPlayer->GetPlayerClass()->GetClassIndex() != pBot->GetPlayerClass()->GetClassIndex() ) |
|
{ |
|
pBot->AllowInstantSpawn(); |
|
pBot->HandleCommand_JoinClass( pPlayer->GetPlayerClass()->GetName() ); |
|
} |
|
|
|
int nLastSlot = LOADOUT_POSITION_MISC2; |
|
|
|
#ifdef STAGING_ONLY |
|
//nLastSlot = LOADOUT_POSITION_MISC10; |
|
#endif // STAGING_ONLY |
|
|
|
pBot->RemoveAllItems( false ); |
|
|
|
for ( int i = 0; i <= nLastSlot; ++i ) |
|
{ |
|
CEconItemView *pPlayerItem = pPlayer->GetLoadoutItem( pPlayer->GetPlayerClass()->GetClassIndex(), i ); |
|
if ( !pPlayerItem ) |
|
continue; |
|
|
|
BotGenerateAndWearItem( pBot, pPlayerItem ); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
CON_COMMAND_F( bot_mirror, "Forces the specified bot to be the same class, and use the same items, as you.", FCVAR_CHEAT ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_GetCommandClient() ); |
|
if ( !pTFPlayer ) |
|
return; |
|
|
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 2, "Usage: bot_mirror <bot name>, or bot_mirror all", &botVector ); |
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
BotMirrorPlayerClassAndItems( botVector[i], pTFPlayer ); |
|
} |
|
} |
|
|
|
#ifdef STAGING_ONLY |
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force the specified bot to create & equip an item |
|
//------------------------------------------------------------------------------ |
|
void cc_bot_equip( const CCommand &args ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 3, "Usage: bot_equip <bot name> <item name>", &botVector ); |
|
|
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
BotGenerateAndWearItem( botVector[i], args[2] ); |
|
} |
|
} |
|
static ConCommand bot_equip("bot_equip", cc_bot_equip, "Generate an item and have the bot equip it.\n\tFormat: bot_equip <bot name> <item name>", FCVAR_CHEAT ); |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force the specified bot to disguise themselves. Needed because the disguise command is clientside. |
|
//------------------------------------------------------------------------------ |
|
void cc_bot_disguise( const CCommand &args ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 4, "Usage: bot_disguise <bot name> <team> <class>", &botVector ); |
|
|
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
if ( botVector[i]->CanDisguise() ) |
|
{ |
|
// intercepting the team value and reassigning what gets passed into Disguise() |
|
// because the team numbers in the client menu don't match the #define values for the teams |
|
botVector[i]->m_Shared.Disguise( Q_atoi( args[2] ), Q_atoi( args[3] ) ); |
|
} |
|
} |
|
} |
|
static ConCommand bot_disguise("bot_disguise", cc_bot_disguise, "Force the specified bot to disguise themselves.\n\tFormat: bot_disguise <bot name> <team> <class>", FCVAR_CHEAT ); |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force the specified bot to taunt with specific taunt name |
|
//------------------------------------------------------------------------------ |
|
void cc_bot_taunt( const CCommand &args ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 3, "Usage: bot_taunt <bot name> <taunt_name>", &botVector ); |
|
|
|
if ( botVector.IsEmpty() ) |
|
return; |
|
|
|
const char *pszTauntName = args.ArgS() + V_strlen( args[1] ) + 1; |
|
|
|
CEconItemDefinition *pItemDef = ItemSystem()->GetStaticDataForItemByName( pszTauntName ); |
|
if ( !pItemDef ) |
|
{ |
|
Msg( "bot_taunt: failed to find taunt name <%s>\n", pszTauntName ); |
|
return; |
|
} |
|
|
|
static CEconItemView item; |
|
item.SetItemDefIndex( pItemDef->GetDefinitionIndex() ); |
|
item.SetItemQuality( pItemDef->GetQuality() ); |
|
item.SetInitialized( true ); |
|
|
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
botVector[i]->PlayTauntSceneFromItem( &item ); |
|
} |
|
} |
|
static ConCommand bot_taunt("bot_taunt", cc_bot_taunt, "Force the specified bot to taunt with specific taunt name.\n\tFormat: bot_taunt <bot name> <taunt_name>", FCVAR_CHEAT ); |
|
|
|
|
|
#endif // STAGING_ONLY |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force the specified bot to select a weapon in the specified slot |
|
//------------------------------------------------------------------------------ |
|
CON_COMMAND_F( cc_bot_selectweapon, "Force a bot to select a weapon in a slot. Usage: bot_selectweapon <bot name> <weapon slot>", FCVAR_CHEAT ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 3, "Usage: bot_selectweapon <bot name> <weapon slot>", &botVector ); |
|
if ( botVector.IsEmpty() ) |
|
return; |
|
|
|
int slot = atoi( args[2] ); |
|
|
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
CBaseCombatWeapon *pWpn = botVector[i]->Weapon_GetSlot( slot ); |
|
if ( pWpn ) |
|
{ |
|
botVector[i]->Weapon_Switch( pWpn ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force the specified bot to drop all his gear. |
|
//------------------------------------------------------------------------------ |
|
CON_COMMAND_F( bot_drop, "Force the specified bot to drop his active weapon. Usage: bot_drop <bot name>", FCVAR_CHEAT ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 2, "Usage: bot_drop <bot name>", &botVector ); |
|
|
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
CBaseCombatWeapon* pWpn = botVector[i]->GetActiveWeapon(); |
|
if ( pWpn ) |
|
{ |
|
UTIL_Remove( pWpn ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Force the specified bot to move to the point under your crosshair |
|
//------------------------------------------------------------------------------ |
|
CON_COMMAND_F( bot_moveto, "Force the specified bot to move to the point under your crosshair. Usage: bot_moveto <bot name>", FCVAR_CHEAT ) |
|
{ |
|
CUtlVector< CTFPlayer* > botVector; |
|
GetBotsFromCommand( args, 2, "Usage: bot_moveto <bot name>", &botVector ); |
|
if ( botVector.IsEmpty() ) |
|
return; |
|
|
|
CBasePlayer *pPlayer = UTIL_GetCommandClient(); |
|
trace_t tr; |
|
Vector forward; |
|
pPlayer->EyeVectors( &forward ); |
|
UTIL_TraceLine( pPlayer->EyePosition(), pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.fraction != 1.0 ) |
|
{ |
|
FOR_EACH_VEC( botVector, i ) |
|
{ |
|
botdata_t *botdata = BotData( botVector[i] ); |
|
botdata->FindPathTo( tr.endpos ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
bool botdata_t::FindPathTo( Vector vecTarget ) |
|
{ |
|
m_flRecomputePathAt = gpGlobals->curtime + bot_nav_recomputetime.GetFloat(); |
|
m_vecLastPathTarget = vecTarget; |
|
|
|
ShortestPathCost cost; |
|
CNavArea *pCurrentArea = TheNavMesh->GetNavArea( m_hBot->GetAbsOrigin() ); |
|
if ( !pCurrentArea ) |
|
{ |
|
Warning( "Bot '%s' not on the nav mesh.\n", m_hBot->GetPlayerName() ); |
|
return false; |
|
} |
|
|
|
CNavArea *pTargetArea = TheNavMesh->GetNavArea( vecTarget ); |
|
if ( !pTargetArea ) |
|
{ |
|
Warning( "Bot '%s' tried to move to a point that isn't on the nav mesh (%.2f %.2f %.2f).\n", m_hBot->GetPlayerName(), vecTarget.x, vecTarget.y, vecTarget.z ); |
|
return false; |
|
} |
|
|
|
// If the path stays within a single area, build a single waypoint and we're done. |
|
if ( pTargetArea == pCurrentArea ) |
|
{ |
|
m_path[0].pos = vecTarget; |
|
m_path[0].area = pCurrentArea; |
|
m_path[0].how = NUM_TRAVERSE_TYPES; |
|
m_iPathLength = 1; |
|
m_iPathIndex = 0; |
|
return true; |
|
} |
|
|
|
if ( NavAreaBuildPath( pCurrentArea, pTargetArea, &vecTarget, cost ) == false ) |
|
{ |
|
Warning( "Bot '%s' couldn't find a path from nav mesh %d to nav mesh %d.\n", m_hBot->GetPlayerName(), pCurrentArea->GetID(), pTargetArea->GetID() ); |
|
return false; |
|
} |
|
|
|
m_iPathIndex = 0; |
|
|
|
// Count the number of areas in the path |
|
int count = 0; |
|
CNavArea *area; |
|
for( area = pTargetArea; area; area = area->GetParent() ) |
|
{ |
|
++count; |
|
} |
|
|
|
bool bUseOffsetPaths = bot_nav_useoffsetpaths.GetBool(); |
|
if ( bUseOffsetPaths ) |
|
{ |
|
count = (count * 2) - 1; |
|
} |
|
m_iPathLength = count; |
|
|
|
// Move the areas into our path |
|
for( area = pTargetArea; count && area; area = area->GetParent() ) |
|
{ |
|
--count; |
|
m_path[ count ].area = area; |
|
m_path[ count ].how = area->GetParentHow(); |
|
m_path[ count ].flOffset = bUseOffsetPaths ? -bot_nav_offsetpathinset.GetFloat() : 0; |
|
|
|
if ( bUseOffsetPaths && count >= 1 ) |
|
{ |
|
--count; |
|
m_path[ count ].area = m_path[ count+1 ].area; |
|
m_path[ count ].how = m_path[ count+1 ].how; |
|
m_path[ count ].flOffset = m_path[ count+1 ].flOffset * -1; |
|
} |
|
} |
|
|
|
if ( !ComputePathPositions() ) |
|
{ |
|
ResetPath(); |
|
return false; |
|
} |
|
|
|
// append path end position |
|
m_path[ m_iPathLength ].area = pTargetArea; |
|
m_path[ m_iPathLength ].pos = vecTarget; |
|
m_path[ m_iPathLength ].how = NUM_TRAVERSE_TYPES; |
|
m_path[ m_iPathLength ].flOffset = 0; |
|
++m_iPathLength; |
|
return true; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Given a path of areas, compute waypoints along the path |
|
//------------------------------------------------------------------------------ |
|
bool botdata_t::ComputePathPositions( void ) |
|
{ |
|
if (m_iPathLength == 0) |
|
return false; |
|
|
|
m_path[0].pos = m_hBot->GetAbsOrigin();//m_path[0].area->GetCenter(); |
|
m_path[0].how = NUM_TRAVERSE_TYPES; |
|
|
|
for( int i=1; i<m_iPathLength; ++i ) |
|
{ |
|
const ConnectInfo *from = &m_path[ i-1 ]; |
|
ConnectInfo *to = &m_path[ i ]; |
|
Vector vecFrom = from->pos; |
|
|
|
if ( to->flOffset < 0 && i >= 2 ) |
|
{ |
|
from = &m_path[ i-2 ]; |
|
} |
|
|
|
from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, from->pos, &to->pos ); |
|
to->pos.z = from->area->GetZ( to->pos ); |
|
|
|
if ( to->flOffset ) |
|
{ |
|
// Create a waypoint just inside our current area |
|
AddDirectionVector( &to->pos, (NavDirType)to->how, -to->flOffset ); |
|
} |
|
|
|
if ( bot_debug.GetInt() == 2 ) |
|
{ |
|
NDebugOverlay::HorzArrow( vecFrom, to->pos, 3, 0, 96, 0, 0, true, 10.0 ); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
bool botdata_t::UpdatePath( void ) |
|
{ |
|
// If we're going to an entity, make sure it hasn't moved. |
|
if ( CommandHasATarget() ) |
|
{ |
|
if ( m_flRecomputePathAt < gpGlobals->curtime && m_Commands[0].pTarget ) |
|
{ |
|
Vector vecNewTarget = m_Commands[0].pTarget->GetAbsOrigin(); |
|
if ( (m_vecLastPathTarget - vecNewTarget).LengthSqr() > (50 * 50) ) |
|
return FindPathTo( vecNewTarget ); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::UpdatePlanForCommand( void ) |
|
{ |
|
if ( !m_Commands.Count() ) |
|
return; |
|
|
|
if ( !m_bStartedCommand ) |
|
{ |
|
StartNewCommand(); |
|
m_bStartedCommand = true; |
|
} |
|
|
|
switch ( m_Commands[0].iCommand ) |
|
{ |
|
case BC_ATTACK: |
|
{ |
|
RunAttackPlan(); |
|
} |
|
break; |
|
case BC_MOVETO_ENTITY: |
|
{ |
|
if ( !HasPath() ) |
|
{ |
|
if ( FindPathTo( m_Commands[0].pTarget->GetAbsOrigin() ) == false ) |
|
{ |
|
FinishCommand(); |
|
} |
|
} |
|
} |
|
break; |
|
case BC_MOVETO_POINT: |
|
{ |
|
if ( !HasPath() ) |
|
{ |
|
if ( FindPathTo( GetCommandPosition() ) == false ) |
|
{ |
|
FinishCommand(); |
|
} |
|
} |
|
} |
|
break; |
|
case BC_SWITCH_WEAPON: |
|
{ |
|
CBaseCombatWeapon *pWpn = m_hBot->Weapon_GetSlot( (int)m_Commands[0].flData ); |
|
if ( pWpn ) |
|
{ |
|
m_hBot->Weapon_Switch( pWpn ); |
|
} |
|
FinishCommand(); |
|
} |
|
break; |
|
case BC_DEFEND: |
|
{ |
|
RunDefendPlan(); |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::RunAttackPlan( void ) |
|
{ |
|
CBaseEntity *pEnt = GetCommandTarget(); |
|
if ( !pEnt ) |
|
return; |
|
|
|
if ( !pEnt->IsAlive() ) |
|
{ |
|
FinishCommand(); |
|
return; |
|
} |
|
|
|
bool bCanSeeTarget = m_hBot->FVisible( pEnt ); |
|
bool bNeedsAPath = !bCanSeeTarget; |
|
|
|
if ( bCanSeeTarget ) |
|
{ |
|
float flDistance = (pEnt->GetAbsOrigin() - m_hBot->GetAbsOrigin()).LengthSqr(); |
|
|
|
// If it's a melee weapon, we need to close. Otherwise, stay at a nice looking distance. |
|
if ( m_hBot->GetActiveTFWeapon() && m_hBot->GetActiveTFWeapon()->IsMeleeWeapon() ) |
|
{ |
|
float flRange = bot_com_meleerange.GetFloat(); |
|
flRange *= flRange; |
|
bNeedsAPath = ( flDistance > flRange ); |
|
if ( !bNeedsAPath ) |
|
{ |
|
buttons |= IN_ATTACK; |
|
} |
|
} |
|
else |
|
{ |
|
float flRange = bot_com_wpnrange.GetFloat(); |
|
flRange *= flRange; |
|
bNeedsAPath = ( flDistance > (500 * 500) ); |
|
buttons |= IN_ATTACK; |
|
} |
|
} |
|
|
|
if ( bNeedsAPath && !HasPath() ) |
|
{ |
|
if ( FindPathTo( m_Commands[0].pTarget->GetAbsOrigin() ) == false ) |
|
{ |
|
FinishCommand(); |
|
return; |
|
} |
|
} |
|
else if ( !bNeedsAPath && HasPath() ) |
|
{ |
|
ResetPath(); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::RunDefendPlan( void ) |
|
{ |
|
if ( bot_debug.GetInt() == 5 ) |
|
{ |
|
NDebugOverlay::Line( m_hBot->GetAbsOrigin(), GetCommandPosition(), 0, 128, 0, true, 0.1 ); |
|
} |
|
|
|
if ( !FindEnemyTarget() ) |
|
{ |
|
if ( !HasPath() ) |
|
{ |
|
// If we're far from our defend point, move back to it. |
|
float flDistance = (GetCommandPosition() - m_hBot->GetAbsOrigin()).LengthSqr(); |
|
if ( flDistance > (32*32) ) |
|
{ |
|
if ( FindPathTo( GetCommandPosition() ) == false ) |
|
{ |
|
// Can't get back to our defend position |
|
FinishCommand(); |
|
return; |
|
} |
|
} |
|
} |
|
return; |
|
} |
|
|
|
Assert( m_hEnemy.Get() ); |
|
|
|
if ( bot_debug.GetInt() == 5 ) |
|
{ |
|
NDebugOverlay::Line( m_hBot->GetAbsOrigin(), m_hEnemy->GetAbsOrigin(), 255, 0, 0, true, 0.1 ); |
|
} |
|
|
|
// We've got an enemy. |
|
float flDefendRange = m_Commands[0].flData * m_Commands[0].flData; |
|
float flDistanceToEnemy = (m_hEnemy->GetAbsOrigin() - m_hBot->GetAbsOrigin()).LengthSqr(); |
|
float flDistanceToEnemyFromDefend = (m_hEnemy->GetAbsOrigin() - GetCommandPosition()).LengthSqr(); |
|
|
|
bool bNeedsAPath = false; |
|
|
|
// If it's a melee weapon, we need to close. Otherwise, stay at a nice looking distance. |
|
if ( m_hBot->GetActiveTFWeapon() && m_hBot->GetActiveTFWeapon()->IsMeleeWeapon() ) |
|
{ |
|
// We can only close if we're allowed to move to the target's distance. |
|
if ( flDistanceToEnemyFromDefend <= flDefendRange ) |
|
{ |
|
float flRange = bot_com_meleerange.GetFloat(); |
|
flRange *= flRange; |
|
bNeedsAPath = ( flDistanceToEnemy > flRange ); |
|
if ( !bNeedsAPath ) |
|
{ |
|
buttons |= IN_ATTACK; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
buttons |= IN_ATTACK; |
|
} |
|
|
|
if ( bNeedsAPath && !HasPath() ) |
|
{ |
|
FindPathTo( m_hEnemy->GetAbsOrigin() ); |
|
} |
|
else if ( !bNeedsAPath && HasPath() ) |
|
{ |
|
ResetPath(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Look for a target |
|
//----------------------------------------------------------------------------- |
|
bool botdata_t::FindEnemyTarget( void ) |
|
{ |
|
Vector vecEyePosition = m_hBot->EyePosition(); |
|
|
|
// find the enemy team |
|
int iEnemyTeam = ( m_hBot->GetTeamNumber() == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE; |
|
CTFTeam *pTeam = TFTeamMgr()->GetTeam( iEnemyTeam ); |
|
if ( !pTeam ) |
|
return false; |
|
|
|
// If we have an enemy get his minimum distance to check against. |
|
Vector vecSegment; |
|
Vector vecTargetCenter; |
|
float flMinDist2 = bot_com_viewrange.GetFloat(); |
|
flMinDist2 *= flMinDist2; |
|
CBaseEntity *pTargetCurrent = NULL; |
|
CBaseEntity *pTargetOld = m_hEnemy; |
|
float flOldTargetDist2 = FLT_MAX; |
|
|
|
// Try to target players first, then objects. However, if the enemy held was an object it will continue |
|
// to try and attack it first. |
|
int nTeamCount = pTeam->GetNumPlayers(); |
|
for ( int iPlayer = 0; iPlayer < nTeamCount; ++iPlayer ) |
|
{ |
|
CTFPlayer *pTargetPlayer = static_cast<CTFPlayer*>( pTeam->GetPlayer( iPlayer ) ); |
|
if ( pTargetPlayer == NULL ) |
|
continue; |
|
|
|
// Make sure the player is alive. |
|
if ( !pTargetPlayer->IsAlive() ) |
|
continue; |
|
|
|
if ( pTargetPlayer->GetFlags() & FL_NOTARGET ) |
|
continue; |
|
|
|
vecTargetCenter = pTargetPlayer->GetAbsOrigin(); |
|
vecTargetCenter += pTargetPlayer->GetViewOffset(); |
|
VectorSubtract( vecTargetCenter, vecEyePosition, vecSegment ); |
|
float flDist2 = vecSegment.LengthSqr(); |
|
|
|
// Check to see if the target is closer than the already validated target. |
|
if ( flDist2 > flMinDist2 ) |
|
continue; |
|
|
|
// It is closer, check to see if the target is valid. |
|
if ( ValidTargetPlayer( pTargetPlayer, vecEyePosition, vecTargetCenter ) ) |
|
{ |
|
flMinDist2 = flDist2; |
|
pTargetCurrent = pTargetPlayer; |
|
|
|
// Store the current target distance if we come across it |
|
if ( pTargetPlayer == pTargetOld ) |
|
{ |
|
flOldTargetDist2 = flDist2; |
|
} |
|
} |
|
} |
|
|
|
// If we already have a target, don't check objects. |
|
if ( pTargetCurrent == NULL ) |
|
{ |
|
int nTeamObjectCount = pTeam->GetNumObjects(); |
|
for ( int iObject = 0; iObject < nTeamObjectCount; ++iObject ) |
|
{ |
|
CBaseObject *pTargetObject = pTeam->GetObject( iObject ); |
|
if ( !pTargetObject ) |
|
continue; |
|
|
|
vecTargetCenter = pTargetObject->GetAbsOrigin(); |
|
vecTargetCenter += pTargetObject->GetViewOffset(); |
|
VectorSubtract( vecTargetCenter, vecEyePosition, vecSegment ); |
|
float flDist2 = vecSegment.LengthSqr(); |
|
|
|
// Store the current target distance if we come across it |
|
if ( pTargetObject == pTargetOld ) |
|
{ |
|
flOldTargetDist2 = flDist2; |
|
} |
|
|
|
// Check to see if the target is closer than the already validated target. |
|
if ( flDist2 > flMinDist2 ) |
|
continue; |
|
|
|
// It is closer, check to see if the target is valid. |
|
if ( ValidTargetObject( pTargetObject, vecEyePosition, vecTargetCenter ) ) |
|
{ |
|
flMinDist2 = flDist2; |
|
pTargetCurrent = pTargetObject; |
|
} |
|
} |
|
} |
|
|
|
// We have a target. |
|
if ( pTargetCurrent ) |
|
{ |
|
if ( pTargetCurrent != pTargetOld ) |
|
{ |
|
// flMinDist2 is the new target's distance |
|
// flOldTargetDist2 is the old target's distance |
|
// Don't switch unless the new target is closer by some percentage |
|
if ( flMinDist2 < ( flOldTargetDist2 * 0.75f ) ) |
|
{ |
|
m_hEnemy = pTargetCurrent; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool botdata_t::ValidTargetPlayer( CTFPlayer *pPlayer, const Vector &vecStart, const Vector &vecEnd ) |
|
{ |
|
if ( m_bIgnoreHumans && !pPlayer->IsFakeClient() ) |
|
return false; |
|
|
|
// Keep shooting at spies that go invisible after we acquire them as a target. |
|
if ( pPlayer->m_Shared.GetPercentInvisible() > 0.5 ) |
|
return false; |
|
|
|
// Keep shooting at spies that disguise after we acquire them as at a target. |
|
if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->m_Shared.GetDisguiseTeam() == m_hBot->GetTeamNumber() && pPlayer != m_hEnemy ) |
|
return false; |
|
|
|
// Not across water boundary. |
|
if ( ( m_hBot->GetWaterLevel() == 0 && pPlayer->GetWaterLevel() >= 3 ) || ( m_hBot->GetWaterLevel() == 3 && pPlayer->GetWaterLevel() <= 0 ) ) |
|
return false; |
|
|
|
// Ray trace!!! |
|
return m_hBot->FVisible( pPlayer, MASK_SHOT | CONTENTS_GRATE ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool botdata_t::ValidTargetObject( CBaseObject *pObject, const Vector &vecStart, const Vector &vecEnd ) |
|
{ |
|
// Ignore objects being placed, they are not real objects yet. |
|
if ( pObject->IsPlacing() ) |
|
return false; |
|
|
|
// Ignore sappers. |
|
if ( pObject->MustBeBuiltOnAttachmentPoint() ) |
|
return false; |
|
|
|
// Not across water boundary. |
|
if ( ( m_hBot->GetWaterLevel() == 0 && pObject->GetWaterLevel() >= 3 ) || ( m_hBot->GetWaterLevel() == 3 && pObject->GetWaterLevel() <= 0 ) ) |
|
return false; |
|
|
|
if ( pObject->GetObjectFlags() & OF_DOESNT_HAVE_A_MODEL ) |
|
return false; |
|
|
|
// Ray trace. |
|
return m_hBot->FVisible( pObject, MASK_SHOT | CONTENTS_GRATE ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::StartNewCommand( void ) |
|
{ |
|
switch ( m_Commands[0].iCommand ) |
|
{ |
|
case BC_DEFEND: |
|
{ |
|
// Mark our current position as the point we're defending |
|
m_Commands[0].vecTarget = m_hBot->GetAbsOrigin(); |
|
} |
|
break; |
|
|
|
case BC_ATTACK: |
|
case BC_MOVETO_ENTITY: |
|
case BC_MOVETO_POINT: |
|
case BC_SWITCH_WEAPON: |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::FinishCommand( void ) |
|
{ |
|
if ( m_Commands.Count() > 0 ) |
|
{ |
|
m_hBotController->m_outputOnCommandFinished.FireOutput( m_hBot, m_hBot ); |
|
m_Commands.Remove(0); |
|
} |
|
m_bStartedCommand = false; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::AddAttackCommand( CBaseEntity *pTarget ) |
|
{ |
|
int iIndex = m_Commands.AddToTail(); |
|
m_Commands[iIndex].iCommand = BC_ATTACK; |
|
m_Commands[iIndex].pTarget = pTarget; |
|
m_Commands[iIndex].vecTarget = vec3_origin; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::AddMoveToCommand( CBaseEntity *pTarget, Vector vecTarget ) |
|
{ |
|
int iIndex = m_Commands.AddToTail(); |
|
m_Commands[iIndex].iCommand = pTarget ? BC_MOVETO_ENTITY : BC_MOVETO_POINT; |
|
m_Commands[iIndex].pTarget = pTarget; |
|
m_Commands[iIndex].vecTarget = vecTarget; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::AddSwitchWeaponCommand( int iSlot ) |
|
{ |
|
int iIndex = m_Commands.AddToTail(); |
|
m_Commands[iIndex].iCommand = BC_SWITCH_WEAPON; |
|
m_Commands[iIndex].flData = iSlot; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::AddDefendCommand( float flRange ) |
|
{ |
|
int iIndex = m_Commands.AddToTail(); |
|
m_Commands[iIndex].iCommand = BC_DEFEND; |
|
m_Commands[iIndex].flData = flRange; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void botdata_t::ClearQueue( void ) |
|
{ |
|
FinishCommand(); |
|
m_Commands.Purge(); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
bool botdata_t::RunningMovementCommand( void ) |
|
{ |
|
if ( m_Commands.Count() ) |
|
return ( m_Commands[0].iCommand == BC_MOVETO_ENTITY || m_Commands[0].iCommand == BC_MOVETO_POINT ); |
|
return false; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
bool botdata_t::CommandHasATarget( void ) |
|
{ |
|
return ( m_Commands.Count() && (m_Commands[0].iCommand == BC_MOVETO_ENTITY || m_Commands[0].iCommand == BC_ATTACK) ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
bool botdata_t::ShouldFinishCommandOnArrival( void ) |
|
{ |
|
return ( m_Commands.Count() && (m_Commands[0].iCommand == BC_MOVETO_ENTITY || m_Commands[0].iCommand == BC_MOVETO_POINT) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle movement |
|
//----------------------------------------------------------------------------- |
|
void botdata_t::Bot_ItemTestingThink( QAngle *vecAngles, Vector *vecMove ) |
|
{ |
|
switch ( TFGameRules()->ItemTesting_GetBotAnim() ) |
|
{ |
|
default: |
|
case TI_BOTANIM_IDLE: |
|
break; |
|
|
|
case TI_BOTANIM_CROUCH: |
|
buttons |= IN_DUCK; |
|
break; |
|
|
|
case TI_BOTANIM_RUN: |
|
break; |
|
|
|
case TI_BOTANIM_CROUCH_WALK: |
|
buttons |= IN_DUCK; |
|
break; |
|
|
|
case TI_BOTANIM_JUMP: |
|
if ( m_hBot->GetFlags() & FL_ONGROUND ) |
|
{ |
|
buttons |= IN_JUMP; |
|
} |
|
break; |
|
} |
|
|
|
if ( TFGameRules()->ItemTesting_GetBotForceFire() ) |
|
{ |
|
// Hack to make them not fire faster than the anims being played? |
|
//if ( !m_hBot->GetAnimState()->IsGestureSlotActive( GESTURE_SLOT_ATTACK_AND_RELOAD ) ) |
|
buttons |= IN_ATTACK; |
|
} |
|
|
|
if ( TFGameRules()->ItemTesting_GetBotTurntable() ) |
|
{ |
|
(*vecAngles)[YAW] += (1.0f * TFGameRules()->ItemTesting_GetBotAnimSpeed()); |
|
if ( (*vecAngles)[YAW] > 360 ) |
|
{ |
|
(*vecAngles)[YAW] = 0; |
|
} |
|
} |
|
|
|
(*vecMove)[0] = 0; |
|
(*vecMove)[1] = 0; |
|
(*vecMove)[2] = 0; |
|
} |
|
|
|
//=================================================================================================================== |
|
// Purpose: Mapmaker bot control entity. Used by mapmakers to add & script bot behaviors. |
|
BEGIN_DATADESC( CTFBotController ) |
|
DEFINE_KEYFIELD( m_iszBotName, FIELD_STRING, "bot_name" ), |
|
DEFINE_KEYFIELD( m_iBotClass, FIELD_INTEGER, "bot_class" ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "CreateBot", InputCreateBot ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "RespawnBot", InputRespawnBot ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "AddCommandMoveToEntity", InputBotAddCommandMoveToEntity ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "AddCommandAttackEntity", InputBotAddCommandAttackEntity ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddCommandSwitchWeapon", InputBotAddCommandSwitchWeapon ), |
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "AddCommandDefend", InputBotAddCommandDefend ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetIgnoreHumans", InputBotSetIgnoreHumans ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "PreventMovement", InputBotPreventMovement ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearQueue", InputBotClearQueue ), |
|
|
|
DEFINE_OUTPUT( m_outputOnCommandFinished, "OnCommandFinished" ), |
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( bot_controller, CTFBotController ); |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputCreateBot( inputdata_t &inputdata ) |
|
{ |
|
m_hBot = ToTFPlayer( BotPutInServer( false, false, GetTeamNumber(), m_iBotClass ? m_iBotClass : TF_CLASS_RANDOM, STRING(m_iszBotName) ) ); |
|
if ( m_hBot ) |
|
{ |
|
BotData(m_hBot)->m_hBotController = this; |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputRespawnBot( inputdata_t &inputdata ) |
|
{ |
|
if ( !m_hBot->GetPlayerClass() || m_hBot->GetPlayerClass()->GetClassIndex() == TF_CLASS_UNDEFINED ) |
|
{ |
|
// Allow them to spawn instantly when they do choose |
|
m_hBot->AllowInstantSpawn(); |
|
return; |
|
} |
|
|
|
m_hBot->ForceRespawn(); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputBotAddCommandMoveToEntity( inputdata_t &inputdata ) |
|
{ |
|
const char *pszEntName = inputdata.value.String(); |
|
if ( pszEntName && pszEntName[0] ) |
|
{ |
|
CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, pszEntName ); |
|
if ( pEnt ) |
|
{ |
|
BotData(m_hBot)->AddMoveToCommand( pEnt, vec3_origin ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputBotAddCommandAttackEntity( inputdata_t &inputdata ) |
|
{ |
|
const char *pszEntName = inputdata.value.String(); |
|
if ( pszEntName && pszEntName[0] ) |
|
{ |
|
CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, pszEntName ); |
|
if ( pEnt ) |
|
{ |
|
BotData(m_hBot)->AddAttackCommand( pEnt ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputBotAddCommandSwitchWeapon( inputdata_t &inputdata ) |
|
{ |
|
BotData(m_hBot)->AddSwitchWeaponCommand( inputdata.value.Int() ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputBotAddCommandDefend( inputdata_t &inputdata ) |
|
{ |
|
BotData(m_hBot)->AddDefendCommand( inputdata.value.Float() ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputBotSetIgnoreHumans( inputdata_t &inputdata ) |
|
{ |
|
BotData(m_hBot)->SetIgnoreHumans( inputdata.value.Int() > 0 ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputBotPreventMovement( inputdata_t &inputdata ) |
|
{ |
|
BotData(m_hBot)->SetFrozen( inputdata.value.Bool() ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CTFBotController::InputBotClearQueue( inputdata_t &inputdata ) |
|
{ |
|
BotData(m_hBot)->ClearQueue(); |
|
} |
|
|
|
|