summaryrefslogtreecommitdiff
path: root/game/server/tf/tf_bot_temp.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/tf_bot_temp.cpp')
-rw-r--r--game/server/tf/tf_bot_temp.cpp2437
1 files changed, 2437 insertions, 0 deletions
diff --git a/game/server/tf/tf_bot_temp.cpp b/game/server/tf/tf_bot_temp.cpp
new file mode 100644
index 0000000..ea58bc2
--- /dev/null
+++ b/game/server/tf/tf_bot_temp.cpp
@@ -0,0 +1,2437 @@
+//========= 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();
+}
+