Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // The copyright to the contents herein is the property of Valve, L.L.C.
  4. // The contents may be used and/or copied only with the written permission of
  5. // Valve, L.L.C., or in accordance with the terms and conditions stipulated in
  6. // the agreement/contract under which the contents have been supplied.
  7. //
  8. // Purpose: Basic BOT handling.
  9. //
  10. // $Workfile: $
  11. // $Date: $
  12. //
  13. //-----------------------------------------------------------------------------
  14. // $Log: $
  15. //
  16. // $NoKeywords: $
  17. //=============================================================================
  18. #include "cbase.h"
  19. #include "player.h"
  20. #include "tf_player.h"
  21. #include "tf_gamerules.h"
  22. #include "in_buttons.h"
  23. #include "movehelper_server.h"
  24. #include "tf_playerclass_shared.h"
  25. #include "datacache/imdlcache.h"
  26. #include "serverbenchmark_base.h"
  27. #include "econ_entity_creation.h"
  28. #include "nav_mesh.h"
  29. #include "nav_pathfind.h"
  30. #include "tf_team.h"
  31. #include "tf_obj.h"
  32. #include "player.h"
  33. #ifdef STAGING_ONLY
  34. #include "econ_item_system.h"
  35. #endif // STAGING_ONLY
  36. void InitBotTrig( void );
  37. void ClientPutInServer( edict_t *pEdict, const char *playername );
  38. void Bot_Think( CTFPlayer *pBot );
  39. ConVar bot_debug( "bot_debug", "0", FCVAR_CHEAT, "Bot debugging." );
  40. #define DbgBotMsg( pszMsg ) \
  41. do \
  42. { \
  43. if ( bot_debug.GetBool() ) \
  44. DevMsg( "[Bot] %s", static_cast<const char *>(pszMsg) ); \
  45. } while (0)
  46. ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
  47. ConVar bot_forceattack( "bot_forceattack", "0", 0, "When on, all bots fire their guns." );
  48. ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
  49. ConVar bot_forceattack_down( "bot_forceattack_down", "1", 0, "When firing, don't tap fire, hold it down." );
  50. ConVar bot_changeclass( "bot_changeclass", "0", 0, "Force all bots to change to the specified class." );
  51. ConVar bot_dontmove( "bot_dontmove", "0", FCVAR_CHEAT );
  52. ConVar bot_saveme( "bot_saveme", "0", FCVAR_CHEAT );
  53. static ConVar bot_mimic_inverse( "bot_mimic_inverse", "0", 0, "Bot uses usercmd of player by index." );
  54. static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "180", 0, "Offsets the bot yaw." );
  55. ConVar bot_selectweaponslot( "bot_selectweaponslot", "-1", FCVAR_CHEAT, "set to weapon slot that bot should switch to." );
  56. ConVar bot_randomnames( "bot_randomnames", "0", FCVAR_CHEAT );
  57. ConVar bot_jump( "bot_jump", "0", FCVAR_CHEAT, "Force all bots to repeatedly jump." );
  58. ConVar bot_crouch( "bot_crouch", "0", FCVAR_CHEAT, "Force all bots to crouch." );
  59. ConVar bot_nav_turnspeed( "bot_nav_turnspeed", "5", FCVAR_CHEAT, "Rate at which bots turn to face their targets." );
  60. ConVar bot_nav_wpdistance( "bot_nav_wpdistance", "16", FCVAR_CHEAT, "Distance to a waypoint within which a bot considers as having reached it." );
  61. ConVar bot_nav_wpdeceldistance( "bot_nav_wpdeceldistance", "128", FCVAR_CHEAT, "Distance to a waypoint to which a bot starts to decelerate to reach it." );
  62. ConVar bot_nav_simplifypaths( "bot_nav_simplifypaths", "1", FCVAR_CHEAT, "If set, bots will skip waypoints if they already see the waypoint post." );
  63. 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." );
  64. ConVar bot_nav_offsetpathinset( "bot_nav_offsetpathinset", "20", FCVAR_CHEAT, "Distance into an area that waypoints should be generated when pathfinding through portals." );
  65. ConVar bot_nav_usefeelers( "bot_nav_usefeelers", "1", FCVAR_CHEAT, "If set, bots will extend feelers to their sides to find & avoid upcoming collisions." );
  66. 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." );
  67. ConVar bot_com_meleerange( "bot_com_meleerange", "80", FCVAR_CHEAT, "Distance to a target that a melee bot wants to be within to attack." );
  68. ConVar bot_com_wpnrange( "bot_com_wpnrange", "400", FCVAR_CHEAT, "Distance to a target that a ranged bot wants to be within to attack." );
  69. ConVar bot_com_viewrange( "bot_com_viewrange", "2000", FCVAR_CHEAT, "Distance within which bots looking for any enemies will find them." );
  70. extern ConVar bot_mimic;
  71. extern ConVar sv_usercmd_custom_random_seed;
  72. // Utility function to get the specified bot from the command arguments
  73. void GetBotsFromCommand( const CCommand &args, int iArgsRequired, const char *pszUsage, CUtlVector< CTFPlayer* > *botVector )
  74. {
  75. if ( !botVector)
  76. return;
  77. if ( args.ArgC() < iArgsRequired )
  78. {
  79. Msg( "Too few parameters. %s\n", pszUsage );
  80. return;
  81. }
  82. const char *pszBotName = args[1];
  83. if ( FStrEq( pszBotName, "all" ) )
  84. {
  85. CUtlVector< CTFPlayer* > playerVector;
  86. CollectPlayers( &playerVector );
  87. for ( int i=0; i<playerVector.Count(); ++i )
  88. {
  89. if ( playerVector[i]->IsFakeClient() )
  90. {
  91. botVector->AddToTail( playerVector[i] );
  92. }
  93. }
  94. return;
  95. }
  96. // get the bot's player object
  97. CTFPlayer *pBot = ToTFPlayer( UTIL_PlayerByName( pszBotName ) );
  98. if ( !pBot || !pBot->IsFakeClient() )
  99. {
  100. Msg( "No bot with name %s\n", args[1] );
  101. return;
  102. }
  103. botVector->AddToTail( pBot );
  104. }
  105. static int BotNumber = 1;
  106. static int g_iNextBotTeam = -1;
  107. static int g_iNextBotClass = -1;
  108. //===================================================================================================================
  109. // Purpose: Mapmaker bot control entity. Used by mapmakers to add & script bot behaviors.
  110. class CTFBotController : public CPointEntity
  111. {
  112. DECLARE_CLASS( CTFBotController, CPointEntity );
  113. public:
  114. DECLARE_DATADESC();
  115. // Input.
  116. void InputCreateBot( inputdata_t &inputdata );
  117. void InputRespawnBot( inputdata_t &inputdata );
  118. void InputBotAddCommandMoveToEntity( inputdata_t &inputdata );
  119. void InputBotAddCommandAttackEntity( inputdata_t &inputdata );
  120. void InputBotAddCommandSwitchWeapon( inputdata_t &inputdata );
  121. void InputBotAddCommandDefend( inputdata_t &inputdata );
  122. void InputBotSetIgnoreHumans( inputdata_t &inputdata );
  123. void InputBotPreventMovement( inputdata_t &inputdata );
  124. void InputBotClearQueue( inputdata_t &inputdata );
  125. public:
  126. COutputEvent m_outputOnCommandFinished; // Fired when the entity is done respawning the players.
  127. CHandle<CTFPlayer> m_hBot;
  128. string_t m_iszBotName;
  129. int m_iBotClass;
  130. };
  131. enum botcommands_t
  132. {
  133. BC_NONE,
  134. BC_MOVETO_ENTITY,
  135. BC_MOVETO_POINT,
  136. BC_ATTACK,
  137. BC_SWITCH_WEAPON,
  138. BC_DEFEND,
  139. };
  140. typedef struct botdata_t
  141. {
  142. CHandle<CTFPlayer> m_hBot;
  143. bool backwards;
  144. float nextturntime;
  145. bool lastturntoright;
  146. float nextstrafetime;
  147. float sidemove;
  148. QAngle forwardAngle;
  149. QAngle lastAngles;
  150. float m_flJoinTeamTime;
  151. int m_WantedTeam;
  152. int m_WantedClass;
  153. bool m_bChoseWeapon;
  154. bool m_bWasDead;
  155. float m_flDeadTime;
  156. //------------------------------------------------------
  157. // Input into the next command
  158. unsigned short buttons;
  159. //------------------------------------------------------
  160. // Base thinking
  161. void Bot_AliveThink( QAngle *vecAngles, Vector *vecMove );
  162. void Bot_AliveWeaponThink( QAngle *vecAngles, Vector *vecMove );
  163. void Bot_AliveMovementThink( QAngle *vecAngles, Vector *vecMove );
  164. void Bot_AliveMovementThink_ExtendFeelers( QAngle *vecAngles, Vector *vecMove, Vector *vecCurVelocity );
  165. void Bot_DeadThink( QAngle *vecAngles, Vector *vecMove );
  166. void Bot_ItemTestingThink( QAngle *vecAngles, Vector *vecMove );
  167. //------------------------------------------------------
  168. // Navigation
  169. bool FindPathTo( Vector vecTarget );
  170. bool ComputePathPositions( void );
  171. bool UpdatePath( void );
  172. void AdvancePath( void )
  173. {
  174. m_iPathIndex++;
  175. if ( m_iPathIndex >= m_iPathLength )
  176. {
  177. if ( RunningMovementCommand() )
  178. {
  179. // We're not done if we're moving to an entity
  180. if ( !CommandHasATarget() && ShouldFinishCommandOnArrival() )
  181. {
  182. ResetPath();
  183. FinishCommand();
  184. }
  185. else
  186. {
  187. // We finished our path, so find another one.
  188. m_flRecomputePathAt = 0;
  189. if ( !UpdatePath() )
  190. {
  191. FinishCommand();
  192. }
  193. }
  194. }
  195. }
  196. }
  197. bool HasPath( void )
  198. {
  199. return (m_iPathLength > 0);
  200. }
  201. void ResetPath( void )
  202. {
  203. m_iPathIndex = 0;
  204. m_iPathLength = 0;
  205. }
  206. Vector m_vecFaceToTarget;
  207. enum { MAX_PATH_LENGTH = 256 };
  208. struct ConnectInfo
  209. {
  210. CNavArea *area; ///< the area along the path
  211. NavTraverseType how; ///< how to enter this area from the previous one
  212. Vector pos; ///< our movement goal position at this point in the path
  213. float flOffset;
  214. };
  215. ConnectInfo m_path[MAX_PATH_LENGTH];
  216. int m_iPathLength;
  217. int m_iPathIndex;
  218. CNavArea *m_lastKnownArea;
  219. float m_flRecomputePathAt;
  220. Vector m_vecLastPathTarget;
  221. //------------------------------------------------------
  222. // Sensing
  223. void SetIgnoreHumans( bool bIgnore ) { m_bIgnoreHumans = bIgnore; }
  224. void SetFrozen( bool bFrozen ) { if ( bFrozen ) m_hBot->AddEFlags( EFL_BOT_FROZEN ); else m_hBot->RemoveEFlags( EFL_BOT_FROZEN ); }
  225. bool FindEnemyTarget( void );
  226. bool ValidTargetPlayer( CTFPlayer *pPlayer, const Vector &vecStart, const Vector &vecEnd );
  227. bool ValidTargetObject( CBaseObject *pObject, const Vector &vecStart, const Vector &vecEnd );
  228. EHANDLE m_hEnemy;
  229. bool m_bIgnoreHumans;
  230. //------------------------------------------------------
  231. // Command Queue
  232. void UpdatePlanForCommand( void );
  233. void StartNewCommand( void );
  234. void FinishCommand( void );
  235. void RunAttackPlan( void );
  236. void RunDefendPlan( void );
  237. void AddAttackCommand( CBaseEntity *pTarget );
  238. void AddMoveToCommand( CBaseEntity *pTarget, Vector vecTarget );
  239. void AddSwitchWeaponCommand( int iSlot );
  240. void AddDefendCommand( float flRange );
  241. void ClearQueue( void );
  242. bool RunningMovementCommand( void );
  243. bool CommandHasATarget( void );
  244. bool ShouldFinishCommandOnArrival( void );
  245. CBaseEntity *GetCommandTarget( void ) { return m_Commands.Count() ? m_Commands[0].pTarget : NULL; }
  246. Vector GetCommandPosition( void ) { return m_Commands.Count() ? m_Commands[0].vecTarget : vec3_origin; }
  247. struct CommandInfo
  248. {
  249. botcommands_t iCommand;
  250. EHANDLE pTarget;
  251. Vector vecTarget;
  252. float flData;
  253. };
  254. CUtlVector<CommandInfo> m_Commands;
  255. bool m_bStartedCommand;
  256. CHandle<CTFBotController> m_hBotController;
  257. } botdata_t;
  258. static botdata_t g_BotData[ MAX_PLAYERS ];
  259. inline botdata_t *BotData( CBasePlayer *pBot )
  260. {
  261. return &g_BotData[ pBot->entindex() - 1 ];
  262. }
  263. //-----------------------------------------------------------------------------
  264. // Purpose: Create a new Bot and put it in the game.
  265. // Output : Pointer to the new Bot, or NULL if there's no free clients.
  266. //-----------------------------------------------------------------------------
  267. CBasePlayer *BotPutInServer( bool bTargetDummy, bool bFrozen, int iTeam, int iClass, const char *pszCustomName )
  268. {
  269. InitBotTrig();
  270. g_iNextBotTeam = iTeam;
  271. g_iNextBotClass = iClass;
  272. char botname[ 64 ];
  273. if ( pszCustomName && pszCustomName[0] )
  274. {
  275. Q_strncpy( botname, pszCustomName, sizeof( botname ) );
  276. }
  277. else if ( bot_randomnames.GetBool() )
  278. {
  279. switch( g_pServerBenchmark->RandomInt(0,5) )
  280. {
  281. case 0: Q_snprintf( botname, sizeof( botname ), "Bot" ); break;
  282. case 1: Q_snprintf( botname, sizeof( botname ), "This is a medium Bot" ); break;
  283. case 2: Q_snprintf( botname, sizeof( botname ), "This is a super long bot name that is too long for the game to allow" ); break;
  284. case 3: Q_snprintf( botname, sizeof( botname ), "Another bot" ); break;
  285. case 4: Q_snprintf( botname, sizeof( botname ), "Yet more Bot names, medium sized" ); break;
  286. default: Q_snprintf( botname, sizeof( botname ), "B" ); break;
  287. }
  288. }
  289. else
  290. {
  291. Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber );
  292. }
  293. edict_t *pEdict = engine->CreateFakeClient( botname );
  294. if (!pEdict)
  295. {
  296. Msg( "Failed to create Bot.\n");
  297. return NULL;
  298. }
  299. // Allocate a CBasePlayer for the bot, and call spawn
  300. //ClientPutInServer( pEdict, botname );
  301. CTFPlayer *pPlayer = ((CTFPlayer *)CBaseEntity::Instance( pEdict ));
  302. pPlayer->ClearFlags();
  303. pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
  304. pPlayer->SetPlayerType( CTFPlayer::TEMP_BOT );
  305. if ( bFrozen )
  306. {
  307. pPlayer->AddEFlags( EFL_BOT_FROZEN );
  308. }
  309. if ( bTargetDummy )
  310. {
  311. pPlayer->SetTargetDummy();
  312. }
  313. BotNumber++;
  314. botdata_t *pBot = BotData(pPlayer);
  315. pBot->m_hBot = pPlayer;
  316. pBot->m_bWasDead = false;
  317. pBot->m_WantedTeam = iTeam;
  318. pBot->m_WantedClass = iClass;
  319. pBot->m_bChoseWeapon = false;
  320. pBot->m_flJoinTeamTime = gpGlobals->curtime + 0.3;
  321. pBot->m_vecFaceToTarget.Zero();
  322. pBot->m_lastKnownArea = NULL;
  323. pBot->m_flRecomputePathAt = 0;
  324. pBot->ResetPath();
  325. pBot->m_bIgnoreHumans = false;
  326. pBot->m_bStartedCommand = false;
  327. return pPlayer;
  328. }
  329. //-----------------------------------------------------------------------------------------------------
  330. CON_COMMAND_F( bot_kick, "Remove a bot by name, or an entire team (\"red\" or \"blue\"), or all bots (\"all\").", FCVAR_CHEAT )
  331. {
  332. // Listenserver host or rcon access only!
  333. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  334. return;
  335. if ( args.ArgC() != 2 )
  336. {
  337. DevMsg( "%s <bot name>, \"red\", \"blue\", or \"all\"\n", args.Arg(0) );
  338. return;
  339. }
  340. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  341. {
  342. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  343. if ( !player )
  344. continue;
  345. if ( FNullEnt( player->edict() ) )
  346. continue;
  347. if ( player->GetFlags() & FL_FAKECLIENT )
  348. {
  349. if ( FStrEq( args.Arg( 1 ), "all" ) ||
  350. FStrEq( args.Arg( 1 ), player->GetPlayerName() ) ||
  351. ( FStrEq( args.Arg( 1 ), "red" ) && player->GetTeamNumber() == TF_TEAM_RED ) ||
  352. ( FStrEq( args.Arg( 1 ), "blue" ) && player->GetTeamNumber() == TF_TEAM_BLUE ) )
  353. {
  354. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
  355. }
  356. }
  357. }
  358. }
  359. // Handler for the "bot" command.
  360. CON_COMMAND_F( bot, "Add a bot.", FCVAR_NONE )
  361. {
  362. // Listenserver host or rcon access only!
  363. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  364. return;
  365. // Allow bots in item testing mode
  366. if ( !sv_cheats->GetBool() && !TFGameRules()->IsInItemTestingMode() )
  367. return;
  368. //CDODPlayer *pPlayer = CDODPlayer::Instance( UTIL_GetCommandClientIndex() );
  369. // The bot command uses switches like command-line switches.
  370. // -count <count> tells how many bots to spawn.
  371. // -team <index> selects the bot's team. Default is -1 which chooses randomly.
  372. // Note: if you do -team !, then it
  373. // -class <index> selects the bot's class. Default is -1 which chooses randomly.
  374. // -frozen prevents the bots from running around when they spawn in.
  375. // -name sets the bot's name
  376. // -targetdummy sets their health to 1,000,000. Useful for testing damage via hud_damagemeter.
  377. // -teleport teleports the bot to the location the player is pointing at
  378. // Look at -count.
  379. int count = args.FindArgInt( "-count", 1 );
  380. count = clamp( count, 1, 16 );
  381. if ( args.FindArg( "-all" ) )
  382. {
  383. count = 9;
  384. }
  385. // Look at -frozen.
  386. bool bFrozen = !!args.FindArg( "-frozen" );
  387. bool bTargetDummy = !!args.FindArg( "-targetdummy" );
  388. bool bTeleport = !!args.FindArg( "-teleport" );
  389. // Ok, spawn all the bots.
  390. while ( --count >= 0 )
  391. {
  392. // What class do they want?
  393. int iClass = g_pServerBenchmark->RandomInt( 1, TF_CLASS_COUNT-2 ); // -2 so we skip the Civilian class
  394. char const *pVal = args.FindArg( "-class" );
  395. if ( pVal )
  396. {
  397. for ( int i=1; i < TF_CLASS_COUNT_ALL; i++ )
  398. {
  399. if ( !Q_stricmp(pVal, g_aPlayerClassNames_NonLocalized[i] ) )
  400. {
  401. iClass = i;
  402. break;
  403. }
  404. }
  405. }
  406. if ( args.FindArg( "-all" ) )
  407. {
  408. iClass = 9 - count ;
  409. }
  410. int iTeam = TEAM_UNASSIGNED;
  411. pVal = args.FindArg( "-team" );
  412. if ( pVal )
  413. {
  414. if ( stricmp( pVal, "red" ) == 0 )
  415. {
  416. iTeam = TF_TEAM_RED;
  417. }
  418. else if ( stricmp( pVal, "spectator" ) == 0 )
  419. {
  420. iTeam = TEAM_SPECTATOR;
  421. }
  422. else if ( stricmp( pVal, "random" ) == 0 )
  423. {
  424. iTeam = g_pServerBenchmark->RandomInt( 0, 100 ) < 50 ? TF_TEAM_BLUE : TF_TEAM_RED;
  425. }
  426. else
  427. {
  428. iTeam = TF_TEAM_BLUE;
  429. }
  430. }
  431. char const *pName = args.FindArg( "-name" );
  432. CBasePlayer *pTemp = BotPutInServer( bTargetDummy, bFrozen, iTeam, iClass, pName );
  433. if ( bTeleport && pTemp )
  434. {
  435. CTFPlayer *pBot = ToTFPlayer( pTemp );
  436. CBasePlayer *pPlayer = UTIL_GetCommandClient();
  437. if ( pPlayer && pBot )
  438. {
  439. trace_t tr;
  440. Vector forward;
  441. pPlayer->EyeVectors( &forward );
  442. UTIL_TraceLine( pPlayer->EyePosition(),
  443. pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_NPCSOLID,
  444. pPlayer, COLLISION_GROUP_NONE, &tr );
  445. if ( tr.fraction != 1.0 )
  446. {
  447. botdata_t *pBotData = BotData( pBot );
  448. if ( pBotData )
  449. {
  450. pBotData->m_flJoinTeamTime = gpGlobals->curtime - 0.1;
  451. }
  452. const char *pszTeam = "auto";
  453. switch ( pBotData->m_WantedTeam )
  454. {
  455. case TF_TEAM_RED:
  456. pszTeam = "red";
  457. break;
  458. case TF_TEAM_BLUE:
  459. pszTeam = "blue";
  460. break;
  461. case TEAM_SPECTATOR:
  462. pszTeam = "spectator";
  463. break;
  464. default:
  465. pszTeam = "auto";
  466. break;
  467. }
  468. pBot->HandleCommand_JoinTeam( pszTeam );
  469. const char *pszClassname = ( pBotData->m_WantedClass == TF_CLASS_RANDOM ) ? "random" : GetPlayerClassData( pBotData->m_WantedClass )->m_szClassName;
  470. pBot->HandleCommand_JoinClass( pszClassname );
  471. pBot->ForceRespawn();
  472. pBot->Teleport( &tr.endpos, NULL, NULL );
  473. }
  474. }
  475. }
  476. }
  477. }
  478. //-----------------------------------------------------------------------------------------------------
  479. CON_COMMAND_F( bot_hurt, "Hurt a bot by team, or all bots (\"all\").", FCVAR_GAMEDLL )
  480. {
  481. // Listenserver host or rcon access only!
  482. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  483. return;
  484. if ( args.ArgC() < 2 )
  485. {
  486. DevMsg( "%s (-team <red/blue/all>>) (-name <name>) (-damage <int>) (-burn)\n", args.Arg(0) );
  487. return;
  488. }
  489. int nDamage = 50;
  490. const char* pszDamage = args.FindArg( "-damage" );
  491. if( pszDamage )
  492. {
  493. nDamage = atoi(pszDamage);
  494. }
  495. bool bBurn = args.FindArg( "-burn" ) != nullptr;
  496. // Figure out which team if specified
  497. int iTeam = TEAM_UNASSIGNED;
  498. const char* pVal = args.FindArg( "-team" );
  499. if ( pVal )
  500. {
  501. if ( stricmp( pVal, "red" ) == 0 )
  502. {
  503. iTeam = TF_TEAM_RED;
  504. }
  505. else if ( stricmp( pVal, "blue" ) == 0 )
  506. {
  507. iTeam = TF_TEAM_BLUE;
  508. }
  509. else if ( stricmp( pVal, "all" ) == 0 )
  510. {
  511. iTeam = TEAM_ANY;
  512. }
  513. else
  514. {
  515. iTeam = TEAM_ANY;
  516. }
  517. }
  518. // Get the name if the put one in
  519. pVal = args.FindArg( "-name" );
  520. const char *pPlayerName = "";
  521. if( pVal )
  522. {
  523. pPlayerName = pVal;
  524. }
  525. for( int i=1; i<=gpGlobals->maxClients; ++i )
  526. {
  527. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  528. if ( !player )
  529. continue;
  530. if ( FNullEnt( player->edict() ) )
  531. continue;
  532. if ( player->GetFlags() & FL_FAKECLIENT )
  533. {
  534. if ( iTeam == TEAM_ANY ||
  535. FStrEq( pPlayerName, player->GetPlayerName() ) ||
  536. ( player->GetTeamNumber() == iTeam ) ||
  537. ( player->GetTeamNumber() == iTeam ) )
  538. {
  539. player->m_iHealth -= nDamage;
  540. if ( bBurn )
  541. {
  542. CTFPlayer *pBot = ToTFPlayer( player );
  543. pBot->m_Shared.Burn( nullptr, nullptr, 10.0f );
  544. }
  545. }
  546. }
  547. }
  548. }
  549. inline bool isTempBot( CTFPlayer* pPlayer )
  550. {
  551. return ( pPlayer && ( pPlayer->GetFlags() & FL_FAKECLIENT ) && pPlayer->GetPlayerType() == CTFPlayer::TEMP_BOT );
  552. }
  553. //-----------------------------------------------------------------------------
  554. // Purpose: Run through all the Bots in the game and let them think.
  555. //-----------------------------------------------------------------------------
  556. void Bot_RunAll( void )
  557. {
  558. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  559. {
  560. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  561. if ( isTempBot( pPlayer ) && pPlayer->MyNextBotPointer() == NULL )
  562. {
  563. Bot_Think( pPlayer );
  564. }
  565. }
  566. }
  567. bool RunMimicCommand( CUserCmd& cmd )
  568. {
  569. if ( bot_mimic.GetInt() <= 0 )
  570. return false;
  571. if ( bot_mimic.GetInt() > gpGlobals->maxClients )
  572. return false;
  573. CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
  574. if ( !pPlayer )
  575. return false;
  576. if ( !pPlayer->GetLastUserCommand() )
  577. return false;
  578. cmd = *pPlayer->GetLastUserCommand();
  579. cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
  580. if ( bot_mimic_inverse.GetBool() )
  581. {
  582. cmd.forwardmove *= -1;
  583. cmd.sidemove *= -1;
  584. }
  585. return true;
  586. }
  587. //-----------------------------------------------------------------------------
  588. // Purpose: Simulates a single frame of movement for a player
  589. // Input : *fakeclient -
  590. // *viewangles -
  591. // forwardmove -
  592. // sidemove -
  593. // upmove -
  594. // buttons -
  595. // impulse -
  596. // msec -
  597. // Output : virtual void
  598. //-----------------------------------------------------------------------------
  599. static void RunPlayerMove( CTFPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
  600. {
  601. if ( !fakeclient )
  602. return;
  603. CUserCmd cmd;
  604. // Store off the globals.. they're gonna get whacked
  605. float flOldFrametime = gpGlobals->frametime;
  606. float flOldCurtime = gpGlobals->curtime;
  607. float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
  608. fakeclient->SetTimeBase( flTimeBase );
  609. Q_memset( &cmd, 0, sizeof( cmd ) );
  610. if ( !RunMimicCommand( cmd ) )
  611. {
  612. VectorCopy( viewangles, cmd.viewangles );
  613. cmd.forwardmove = forwardmove;
  614. cmd.sidemove = sidemove;
  615. cmd.upmove = upmove;
  616. cmd.buttons = buttons;
  617. cmd.impulse = impulse;
  618. cmd.random_seed = g_pServerBenchmark->RandomInt( 0, 0x7fffffff );
  619. if ( sv_usercmd_custom_random_seed.GetBool() )
  620. {
  621. float fltTimeNow = float( Plat_FloatTime() * 1000.0 );
  622. cmd.server_random_seed = *reinterpret_cast<int*>( (char*)&fltTimeNow );
  623. }
  624. else
  625. {
  626. cmd.server_random_seed = cmd.random_seed;
  627. }
  628. }
  629. if ( bot_dontmove.GetBool() )
  630. {
  631. cmd.forwardmove = 0;
  632. cmd.sidemove = 0;
  633. cmd.upmove = 0;
  634. }
  635. else
  636. {
  637. float flStunAmount = fakeclient->m_Shared.GetAmountStunned( TF_STUN_MOVEMENT );
  638. if ( flStunAmount )
  639. {
  640. cmd.forwardmove *= (1.0 - flStunAmount);
  641. cmd.sidemove *= (1.0 - flStunAmount);
  642. }
  643. }
  644. if ( fakeclient->m_Shared.IsControlStunned() || fakeclient->m_Shared.IsLoserStateStunned() )
  645. {
  646. cmd.weaponselect = 0;
  647. cmd.buttons = 0;
  648. if ( fakeclient->m_Shared.IsControlStunned() )
  649. {
  650. cmd.forwardmove = 0;
  651. cmd.sidemove = 0;
  652. cmd.upmove = 0;
  653. }
  654. }
  655. MoveHelperServer()->SetHost( fakeclient );
  656. fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
  657. // save off the last good usercmd
  658. fakeclient->SetLastUserCommand( cmd );
  659. // Clear out any fixangle that has been set
  660. fakeclient->pl.fixangle = FIXANGLE_NONE;
  661. // Restore the globals..
  662. gpGlobals->frametime = flOldFrametime;
  663. gpGlobals->curtime = flOldCurtime;
  664. }
  665. //-----------------------------------------------------------------------------
  666. // Purpose: Run this Bot's AI for one frame.
  667. //-----------------------------------------------------------------------------
  668. void Bot_Think( CTFPlayer *pBot )
  669. {
  670. // Make sure we stay being a bot
  671. pBot->AddFlag( FL_FAKECLIENT );
  672. botdata_t *botdata = BotData(pBot);
  673. Vector vecMove( 0, 0, 0 );
  674. byte impulse = 0;
  675. float frametime = gpGlobals->frametime;
  676. QAngle vecViewAngles = pBot->EyeAngles();
  677. botdata->buttons = 0;
  678. MDLCACHE_CRITICAL_SECTION();
  679. // Create some random values
  680. if ( pBot->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime )
  681. {
  682. const char *pszTeam = NULL;
  683. switch ( botdata->m_WantedTeam )
  684. {
  685. case TF_TEAM_RED:
  686. pszTeam = "red";
  687. break;
  688. case TF_TEAM_BLUE:
  689. pszTeam = "blue";
  690. break;
  691. case TEAM_SPECTATOR:
  692. pszTeam = "spectator";
  693. break;
  694. default:
  695. pszTeam = "auto";
  696. break;
  697. }
  698. pBot->HandleCommand_JoinTeam( pszTeam );
  699. }
  700. else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->GetPlayerClass()->IsClass( TF_CLASS_UNDEFINED ) )
  701. {
  702. // If they're on a team but haven't picked a class, choose a random class..
  703. const char *pszClassname = ( botdata->m_WantedClass == TF_CLASS_RANDOM ) ? "random" : GetPlayerClassData( botdata->m_WantedClass )->m_szClassName;
  704. pBot->HandleCommand_JoinClass( pszClassname );
  705. }
  706. else if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
  707. {
  708. botdata->Bot_AliveThink( &vecViewAngles, &vecMove );
  709. }
  710. else
  711. {
  712. botdata->Bot_DeadThink( &vecViewAngles, &vecMove );
  713. }
  714. RunPlayerMove( pBot, vecViewAngles, vecMove[0], vecMove[1], vecMove[2], botdata->buttons, impulse, frametime );
  715. }
  716. //-----------------------------------------------------------------------------
  717. // Purpose: Handle the Bot AI for a live bot
  718. //-----------------------------------------------------------------------------
  719. void botdata_t::Bot_AliveThink( QAngle *vecAngles, Vector *vecMove )
  720. {
  721. trace_t trace;
  722. m_bWasDead = false;
  723. // In item testing mode, we run custom logic
  724. if ( TFGameRules()->IsInItemTestingMode() )
  725. {
  726. Bot_ItemTestingThink( vecAngles, vecMove );
  727. return;
  728. }
  729. UpdatePlanForCommand();
  730. Bot_AliveMovementThink( vecAngles, vecMove );
  731. Bot_AliveWeaponThink( vecAngles, vecMove );
  732. // Miscellaneous
  733. if ( bot_saveme.GetInt() > 0 )
  734. {
  735. m_hBot->SaveMe();
  736. bot_saveme.SetValue( bot_saveme.GetInt() - 1 );
  737. }
  738. }
  739. //-----------------------------------------------------------------------------
  740. // Purpose: Handle movement
  741. //-----------------------------------------------------------------------------
  742. void botdata_t::Bot_AliveMovementThink( QAngle *vecAngles, Vector *vecMove )
  743. {
  744. if ( m_hBot->IsEFlagSet(EFL_BOT_FROZEN) )
  745. {
  746. (*vecMove)[0] = 0;
  747. (*vecMove)[1] = 0;
  748. (*vecMove)[2] = 0;
  749. return;
  750. }
  751. if ( bot_jump.GetBool() && m_hBot->GetFlags() & FL_ONGROUND )
  752. {
  753. buttons |= IN_JUMP;
  754. }
  755. if ( bot_crouch.GetBool() )
  756. {
  757. buttons |= IN_DUCK;
  758. }
  759. // If we don't have a faceto target, but we're moving to a waypoint, look at that.
  760. Vector vecFaceTo = m_vecFaceToTarget;
  761. if ( vecFaceTo == vec3_origin )
  762. {
  763. if ( m_hEnemy )
  764. {
  765. vecFaceTo = m_hEnemy->EyePosition();
  766. }
  767. else if ( GetCommandTarget() )
  768. {
  769. vecFaceTo = GetCommandTarget()->EyePosition();
  770. }
  771. else if ( HasPath() )
  772. {
  773. vecFaceTo = m_path[ m_iPathIndex ].pos;
  774. vecFaceTo[2] = m_hBot->EyePosition()[2];
  775. }
  776. }
  777. // Should we face something?
  778. if ( vecFaceTo != vec3_origin )
  779. {
  780. Vector vecViewTarget = (vecFaceTo - m_hBot->EyePosition());
  781. VectorNormalize(vecViewTarget);
  782. QAngle vecTargetViewAngles;
  783. VectorAngles( vecViewTarget, Vector(0,0,1), vecTargetViewAngles );
  784. (*vecAngles)[YAW] = ApproachAngle( vecTargetViewAngles[YAW], (*vecAngles)[YAW], bot_nav_turnspeed.GetFloat() );
  785. }
  786. // Are we moving to a waypoint?
  787. if ( HasPath() )
  788. {
  789. m_lastKnownArea = TheNavMesh->GetNavArea( m_hBot->GetAbsOrigin() );
  790. if ( !UpdatePath() )
  791. {
  792. FinishCommand();
  793. return;
  794. }
  795. if ( !m_Commands.Count() )
  796. return;
  797. float flDistance = bot_nav_wpdistance.GetFloat();
  798. CBaseEntity *pTargetEnt = m_Commands[0].pTarget;
  799. // We might run into our target entity earlier.
  800. if ( CommandHasATarget() && ShouldFinishCommandOnArrival() )
  801. {
  802. float flEntDistance = pTargetEnt->BoundingRadius() + flDistance;
  803. flEntDistance *= flEntDistance;
  804. if ( (pTargetEnt->GetAbsOrigin() - m_hBot->GetAbsOrigin()).Length2DSqr() < flEntDistance )
  805. {
  806. ResetPath();
  807. FinishCommand();
  808. return;
  809. }
  810. }
  811. // Have we reached the next waypoint?
  812. flDistance *= flDistance;
  813. if ( (m_path[ m_iPathIndex ].pos - m_hBot->GetAbsOrigin()).Length2DSqr() < flDistance )
  814. {
  815. AdvancePath();
  816. }
  817. else if ( bot_nav_simplifypaths.GetBool() )
  818. {
  819. // If we can see our next waypoint already, stop moving to this one
  820. while ( m_iPathLength >= (m_iPathIndex+1) &&
  821. m_iPathIndex < (m_iPathLength - 1) &&
  822. m_hBot->FVisible( m_path[ m_iPathIndex+1 ].pos ) )
  823. {
  824. AdvancePath();
  825. }
  826. }
  827. if ( bot_debug.GetInt() == 1 )
  828. {
  829. for ( int i = 0; i < m_iPathLength-1; i++ )
  830. {
  831. NDebugOverlay::Line( m_path[i].pos, m_path[i+1].pos, 0, i == m_iPathIndex ? 255 : 64, 0, true, 0.1 );
  832. NDebugOverlay::Box( m_path[i].pos, -Vector(3,3,3), Vector(3,3,3), 0, i == m_iPathIndex ? 255 : 64, 0, 0, 0.1 );
  833. }
  834. }
  835. Vector vecTarget = m_path[ m_iPathIndex ].pos;
  836. Vector vecDesiredVelocity = (vecTarget - m_hBot->GetAbsOrigin());
  837. if ( bot_nav_usefeelers.GetBool() && m_iPathIndex < (m_iPathLength - 1) )
  838. {
  839. Bot_AliveMovementThink_ExtendFeelers( vecAngles, vecMove, &vecDesiredVelocity );
  840. }
  841. flDistance = VectorNormalize( vecDesiredVelocity );
  842. // If we're approaching our last waypoint, decelerate to the point.
  843. if ( m_iPathLength == 1 )
  844. {
  845. float flDecelDistance = bot_nav_wpdeceldistance.GetFloat();
  846. float flSpeed = MIN( m_hBot->MaxSpeed() * (flDistance / flDecelDistance), m_hBot->MaxSpeed() );
  847. vecDesiredVelocity *= flSpeed;
  848. }
  849. else
  850. {
  851. vecDesiredVelocity *= m_hBot->MaxSpeed();
  852. }
  853. if ( bot_debug.GetInt() == 10 )
  854. {
  855. NDebugOverlay::HorzArrow( m_hBot->GetAbsOrigin(), m_hBot->GetAbsOrigin() + vecDesiredVelocity, 3, 255, 0, 0, 0, true, 0.1 );
  856. }
  857. // Convert the velocity into forward/sidemove
  858. Vector vecForward, vecRight;
  859. AngleVectors( *vecAngles, &vecForward, &vecRight, NULL );
  860. (*vecMove)[0] = DotProduct( vecForward, vecDesiredVelocity );
  861. (*vecMove)[1] = DotProduct( vecRight, vecDesiredVelocity );
  862. }
  863. }
  864. //------------------------------------------------------------------------------------------------------------
  865. #define COS_TABLE_SIZE 256
  866. static float cosTable[ COS_TABLE_SIZE ];
  867. static bool bBotTrigInitted = false;
  868. void InitBotTrig( void )
  869. {
  870. if ( bBotTrigInitted )
  871. return;
  872. bBotTrigInitted = true;
  873. for( int i=0; i<COS_TABLE_SIZE; ++i )
  874. {
  875. float angle = (float)(2.0f * M_PI * i / (float)(COS_TABLE_SIZE-1));
  876. cosTable[i] = (float)cos( angle );
  877. }
  878. }
  879. float BotCOS( float angle )
  880. {
  881. angle = AngleNormalizePositive( angle );
  882. int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
  883. return cosTable[i];
  884. }
  885. float BotSIN( float angle )
  886. {
  887. angle = AngleNormalizePositive( angle - 90 );
  888. int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
  889. return cosTable[i];
  890. }
  891. // Find "simple" ground height, treating current nav area as part of the floor
  892. bool GetSimpleGroundHeightWithFloor( CNavArea *pArea, const Vector &pos, float *height, Vector *normal )
  893. {
  894. if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
  895. {
  896. // our current nav area also serves as a ground polygon
  897. if ( pArea && pArea->IsOverlapping( pos ))
  898. {
  899. *height = MAX( (*height), pArea->GetZ( pos ) );
  900. }
  901. return true;
  902. }
  903. return false;
  904. }
  905. //--------------------------------------------------------------------------------------------------------------
  906. // Purpose: Fire feelers out to either side and steer to avoid collisions
  907. //--------------------------------------------------------------------------------------------------------------
  908. void botdata_t::Bot_AliveMovementThink_ExtendFeelers( QAngle *vecAngles, Vector *vecMove, Vector *vecCurVelocity )
  909. {
  910. VectorNormalize( *vecCurVelocity );
  911. float flForwardAngle = UTIL_VecToYaw( *vecCurVelocity );
  912. Vector dir( BotCOS( flForwardAngle ), BotSIN( flForwardAngle ), 0.0f );
  913. Vector lat( -dir.y, dir.x, 0.0f );
  914. const float feelerOffset = 20.0f;
  915. const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747)
  916. const float feelerHeight = StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it
  917. // Feelers must follow floor slope
  918. float ground;
  919. Vector normal;
  920. Vector eye = m_hBot->EyePosition();
  921. if (GetSimpleGroundHeightWithFloor( m_lastKnownArea, eye, &ground, &normal ) == false)
  922. return;
  923. // get forward vector along floor
  924. dir = CrossProduct( lat, normal );
  925. // correct the sideways vector
  926. lat = CrossProduct( dir, normal );
  927. Vector feet = m_hBot->GetAbsOrigin();
  928. feet.z += feelerHeight;
  929. Vector from = feet + feelerOffset * lat;
  930. Vector to = from + feelerLengthRun * dir;
  931. bool leftClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
  932. if ( bot_debug.GetInt() == 3 )
  933. {
  934. NDebugOverlay::Line( from, to, leftClear ? 0 : 255, leftClear ? 255 : 0, 0, true, 0.1 );
  935. }
  936. from = feet - feelerOffset * lat;
  937. to = from + feelerLengthRun * dir;
  938. bool rightClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
  939. if ( bot_debug.GetInt() == 3 )
  940. {
  941. NDebugOverlay::Line( from, to, rightClear ? 0 : 255, rightClear ? 255 : 0, 0, true, 0.1 );
  942. }
  943. Vector vecDebug;
  944. if ( bot_debug.GetInt() == 3 && (!leftClear || !rightClear) )
  945. {
  946. vecDebug = (*vecCurVelocity * 100);
  947. NDebugOverlay::Line( m_hBot->GetAbsOrigin(), m_hBot->GetAbsOrigin() + vecDebug, 0, 0, 255, true, 0.1 );
  948. }
  949. const float avoidRange = 300.0f; // 50, 300
  950. if (!rightClear)
  951. {
  952. if (leftClear)
  953. {
  954. // right hit, left clear - veer left
  955. *vecCurVelocity = (*vecCurVelocity * avoidRange) + avoidRange * lat;
  956. }
  957. }
  958. else if (!leftClear)
  959. {
  960. // right clear, left hit - veer right
  961. *vecCurVelocity = (*vecCurVelocity * avoidRange) - avoidRange * lat;
  962. }
  963. if ( bot_debug.GetInt() == 3 && (!leftClear || !rightClear) )
  964. {
  965. vecDebug = (*vecCurVelocity);
  966. VectorNormalize( vecDebug );
  967. vecDebug *= 100;
  968. NDebugOverlay::Line( m_hBot->GetAbsOrigin(), m_hBot->GetAbsOrigin() + vecDebug, 64, 64, 255, true, 0.1 );
  969. }
  970. }
  971. //-----------------------------------------------------------------------------
  972. // Purpose: Handle weapon switching / firing
  973. //-----------------------------------------------------------------------------
  974. void botdata_t::Bot_AliveWeaponThink( QAngle *vecAngles, Vector *vecMove )
  975. {
  976. if ( bot_selectweaponslot.GetInt() >= 0 )
  977. {
  978. int slot = bot_selectweaponslot.GetInt();
  979. CBaseCombatWeapon *pWpn = m_hBot->Weapon_GetSlot( slot );
  980. if ( pWpn )
  981. {
  982. m_hBot->Weapon_Switch( pWpn );
  983. }
  984. bot_selectweaponslot.SetValue( -1 );
  985. }
  986. const char *pszWeapon = bot_forcefireweapon.GetString();
  987. if ( (pszWeapon && pszWeapon[0]) || g_pServerBenchmark->IsBenchmarkRunning() )
  988. {
  989. // If bots are being forced to fire a weapon, see if I have it
  990. // Manually look through weapons to ignore subtype
  991. CBaseCombatWeapon *pWeapon = NULL;
  992. if ( g_pServerBenchmark->IsBenchmarkRunning() )
  993. {
  994. if ( !m_bChoseWeapon )
  995. {
  996. m_bChoseWeapon = true;
  997. // Choose any weapon out of the available ones.
  998. CUtlVector<CBaseCombatWeapon*> weapons;
  999. for (int i=0;i<MAX_WEAPONS;i++)
  1000. {
  1001. if ( m_hBot->GetWeapon(i) )
  1002. {
  1003. weapons.AddToTail( m_hBot->GetWeapon( i ) );
  1004. }
  1005. }
  1006. if ( weapons.Count() > 0 )
  1007. {
  1008. pWeapon = weapons[ g_pServerBenchmark->RandomInt( 0, weapons.Count() - 1 ) ];
  1009. }
  1010. }
  1011. }
  1012. else
  1013. {
  1014. // Look for a specific weapon name here.
  1015. for (int i=0;i<MAX_WEAPONS;i++)
  1016. {
  1017. if ( m_hBot->GetWeapon(i) && FClassnameIs( m_hBot->GetWeapon(i), pszWeapon ) )
  1018. {
  1019. pWeapon = m_hBot->GetWeapon(i);
  1020. break;
  1021. }
  1022. }
  1023. }
  1024. if ( pWeapon )
  1025. {
  1026. // Switch to it if we don't have it out
  1027. CBaseCombatWeapon *pActiveWeapon = m_hBot->GetActiveWeapon();
  1028. // Switch?
  1029. if ( pActiveWeapon != pWeapon )
  1030. {
  1031. m_hBot->Weapon_Switch( pWeapon );
  1032. }
  1033. else
  1034. {
  1035. // Start firing
  1036. // Some weapons require releases, so randomise firing
  1037. if ( bot_forceattack_down.GetBool() || (g_pServerBenchmark->RandomFloat(0.0,1.0) > 0.5) )
  1038. {
  1039. buttons |= IN_ATTACK;
  1040. }
  1041. if ( bot_forceattack2.GetBool() )
  1042. {
  1043. buttons |= IN_ATTACK2;
  1044. }
  1045. }
  1046. }
  1047. }
  1048. if ( bot_forceattack.GetInt() )
  1049. {
  1050. if ( bot_forceattack_down.GetBool() || (g_pServerBenchmark->RandomFloat(0.0,1.0) > 0.5) )
  1051. {
  1052. buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
  1053. }
  1054. }
  1055. }
  1056. //-----------------------------------------------------------------------------
  1057. // Purpose: Handle the Bot AI for a dead bot
  1058. //-----------------------------------------------------------------------------
  1059. void botdata_t::Bot_DeadThink( QAngle *vecAngles, Vector *vecMove )
  1060. {
  1061. // Wait for Reinforcement wave
  1062. if ( !m_hBot->IsAlive() )
  1063. {
  1064. if ( m_bWasDead )
  1065. {
  1066. // Wait for a few seconds before respawning.
  1067. if ( gpGlobals->curtime - m_flDeadTime > 3 )
  1068. {
  1069. // Respawn the bot
  1070. buttons |= IN_JUMP;
  1071. }
  1072. }
  1073. else
  1074. {
  1075. // Start a timer to respawn them in a few seconds.
  1076. m_bWasDead = true;
  1077. m_flDeadTime = gpGlobals->curtime;
  1078. }
  1079. }
  1080. }
  1081. //------------------------------------------------------------------------------
  1082. // Purpose: sends the specified command from a bot
  1083. //------------------------------------------------------------------------------
  1084. void cc_bot_sendcommand( const CCommand &args )
  1085. {
  1086. CUtlVector< CTFPlayer* > botVector;
  1087. GetBotsFromCommand( args, 3, "Usage: bot_command <bot name> <command string...>", &botVector );
  1088. if ( botVector.IsEmpty() )
  1089. return;
  1090. const char *commandline = args.GetCommandString();
  1091. // find the rest of the command line past the bot index
  1092. commandline = strstr( commandline, args[2] );
  1093. Assert( commandline );
  1094. int iSize = Q_strlen(commandline) + 1;
  1095. char *pBuf = (char *)malloc(iSize);
  1096. Q_snprintf( pBuf, iSize, "%s", commandline );
  1097. if ( pBuf[iSize-2] == '"' )
  1098. {
  1099. pBuf[iSize-2] = '\0';
  1100. }
  1101. // make a command object with the intended command line
  1102. CCommand command;
  1103. command.Tokenize( pBuf );
  1104. // send the command
  1105. FOR_EACH_VEC( botVector, i )
  1106. {
  1107. TFGameRules()->ClientCommand( botVector[i], command );
  1108. }
  1109. }
  1110. static ConCommand bot_sendcommand( "bot_command", cc_bot_sendcommand, "<bot id> <command string...>. Sends specified command on behalf of specified bot", FCVAR_CHEAT );
  1111. //------------------------------------------------------------------------------
  1112. // Purpose: Kill the specified bot
  1113. //------------------------------------------------------------------------------
  1114. void cc_bot_kill( const CCommand &args )
  1115. {
  1116. CUtlVector< CTFPlayer* > botVector;
  1117. GetBotsFromCommand( args, 2, "Usage: bot_kill <bot name>", &botVector );
  1118. if ( botVector.IsEmpty() )
  1119. return;
  1120. FOR_EACH_VEC( botVector, i )
  1121. {
  1122. botVector[i]->CommitSuicide();
  1123. }
  1124. }
  1125. static ConCommand bot_kill( "bot_kill", cc_bot_kill, "Kills a bot. Usage: bot_kill <bot name>", FCVAR_CHEAT );
  1126. //------------------------------------------------------------------------------
  1127. // Purpose: Force all bots to swap teams
  1128. //------------------------------------------------------------------------------
  1129. CON_COMMAND_F( bot_changeteams, "Make all bots change teams", FCVAR_CHEAT )
  1130. {
  1131. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  1132. {
  1133. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  1134. if ( isTempBot( pPlayer ) )
  1135. {
  1136. int iTeam = pPlayer->GetTeamNumber();
  1137. if ( TF_TEAM_BLUE == iTeam || TF_TEAM_RED == iTeam )
  1138. {
  1139. // toggle team between red & blue
  1140. pPlayer->ChangeTeam( TF_TEAM_BLUE + TF_TEAM_RED - iTeam );
  1141. }
  1142. }
  1143. }
  1144. }
  1145. //------------------------------------------------------------------------------
  1146. // Purpose: Refill all bot ammo counts
  1147. //------------------------------------------------------------------------------
  1148. CON_COMMAND_F( bot_refill, "Refill all bot ammo counts", FCVAR_CHEAT )
  1149. {
  1150. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  1151. {
  1152. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  1153. if ( isTempBot( pPlayer ) )
  1154. {
  1155. pPlayer->GiveAmmo( 1000, TF_AMMO_PRIMARY );
  1156. pPlayer->GiveAmmo( 1000, TF_AMMO_SECONDARY );
  1157. pPlayer->GiveAmmo( 1000, TF_AMMO_METAL );
  1158. pPlayer->TakeHealth( 999, DMG_GENERIC );
  1159. }
  1160. }
  1161. }
  1162. //------------------------------------------------------------------------------
  1163. // Purpose: Deliver lethal damage from the player to the specified bot
  1164. //------------------------------------------------------------------------------
  1165. CON_COMMAND_F( bot_whack, "Deliver lethal damage from player to specified bot. Usage: bot_whack <bot name>", FCVAR_CHEAT )
  1166. {
  1167. CUtlVector< CTFPlayer* > botVector;
  1168. GetBotsFromCommand( args, 2, "Usage: bot_whack <bot name>", &botVector );
  1169. if ( botVector.IsEmpty() )
  1170. return;
  1171. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_GetCommandClient() );
  1172. FOR_EACH_VEC( botVector, i )
  1173. {
  1174. CTakeDamageInfo info( botVector[i], pTFPlayer, 1000, DMG_BULLET );
  1175. info.SetInflictor( pTFPlayer->GetActiveTFWeapon() );
  1176. botVector[i]->TakeDamage( info );
  1177. }
  1178. }
  1179. //------------------------------------------------------------------------------
  1180. // Purpose: Force the specified bot to teleport to the specified position
  1181. //------------------------------------------------------------------------------
  1182. 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 )
  1183. {
  1184. CUtlVector< CTFPlayer* > botVector;
  1185. GetBotsFromCommand( args, 8, "Usage: bot_teleport <bot name> <X> <Y> <Z> <Pitch> <Yaw> <Roll>", &botVector );
  1186. if ( botVector.IsEmpty() )
  1187. return;
  1188. Vector vecPos( atof(args[2]), atof(args[3]), atof(args[4]) );
  1189. QAngle vecAng( atof(args[5]), atof(args[6]), atof(args[7]) );
  1190. FOR_EACH_VEC( botVector, i )
  1191. {
  1192. botVector[i]->Teleport( &vecPos, &vecAng, NULL );
  1193. }
  1194. }
  1195. //------------------------------------------------------------------------------
  1196. // Purpose: Force the specified bot to create & equip an item
  1197. //------------------------------------------------------------------------------
  1198. void BotGenerateAndWearItem( CTFPlayer *pBot, const char *itemName )
  1199. {
  1200. if ( !pBot )
  1201. return;
  1202. CItemSelectionCriteria criteria;
  1203. criteria.SetItemLevel( AE_USE_SCRIPT_VALUE );
  1204. criteria.SetQuality( AE_USE_SCRIPT_VALUE );
  1205. criteria.BAddCondition( "name", k_EOperator_String_EQ, itemName, true );
  1206. CBaseEntity *pItem = ItemGeneration()->GenerateRandomItem( &criteria, pBot->GetAbsOrigin(), vec3_angle );
  1207. if ( pItem )
  1208. {
  1209. // If it's a weapon, remove the current one, and give us this one.
  1210. CBaseEntity *pExisting = pBot->Weapon_OwnsThisType(pItem->GetClassname());
  1211. if ( pExisting )
  1212. {
  1213. CBaseCombatWeapon *pWpn = dynamic_cast<CBaseCombatWeapon *>(pExisting);
  1214. pBot->Weapon_Detach( pWpn );
  1215. UTIL_Remove( pExisting );
  1216. }
  1217. // Fake global id
  1218. static int s_nFakeID = 1;
  1219. static_cast<CEconEntity*>(pItem)->GetAttributeContainer()->GetItem()->SetItemID( s_nFakeID++ );
  1220. DispatchSpawn( pItem );
  1221. static_cast<CEconEntity*>(pItem)->GiveTo( pBot );
  1222. pBot->PostInventoryApplication();
  1223. }
  1224. else
  1225. {
  1226. #ifdef STAGING_ONLY
  1227. extern ConVar tf_bot_use_items;
  1228. if ( !tf_bot_use_items.GetInt() )
  1229. #endif
  1230. {
  1231. Msg( "Failed to create an item named %s\n", itemName );
  1232. }
  1233. }
  1234. }
  1235. void BotGenerateAndWearItem( CTFPlayer *pBot, CEconItemView *pItem )
  1236. {
  1237. int iClass = pBot->GetPlayerClass()->GetClassIndex();
  1238. int iItemSlot = pItem->GetStaticData()->GetLoadoutSlot( iClass );
  1239. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase*>( pBot->GetEntityForLoadoutSlot( iItemSlot ) );
  1240. // we need to force translating the name here.
  1241. // GiveNamedItem will not translate if we force creating the item
  1242. const char *pTranslatedWeaponName = TranslateWeaponEntForClass( pItem->GetStaticData()->GetItemClass(), iClass );
  1243. CTFWeaponBase *pNewItem = dynamic_cast<CTFWeaponBase*>( pBot->GiveNamedItem( pTranslatedWeaponName, 0, pItem, true ) );
  1244. if ( pNewItem )
  1245. {
  1246. CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>( (CBaseEntity*)pNewItem );
  1247. if ( pBuilder )
  1248. {
  1249. pBuilder->SetSubType( pBot->GetPlayerClass()->GetData()->m_aBuildable[0] );
  1250. }
  1251. // make sure we removed our current weapon
  1252. if ( pWeapon )
  1253. {
  1254. pBot->Weapon_Detach( pWeapon );
  1255. UTIL_Remove( pWeapon );
  1256. }
  1257. pNewItem->MarkAttachedEntityAsValidated();
  1258. pNewItem->GiveTo( pBot );
  1259. }
  1260. else
  1261. {
  1262. BotGenerateAndWearItem( pBot, pItem->GetItemDefinition()->GetDefinitionName() );
  1263. }
  1264. }
  1265. //-----------------------------------------------------------------------------
  1266. // Purpose:
  1267. //-----------------------------------------------------------------------------
  1268. void BotMirrorPlayerClassAndItems( CTFPlayer *pBot, CTFPlayer *pPlayer )
  1269. {
  1270. if ( !pBot || !pPlayer )
  1271. return;
  1272. // Force them to be our class
  1273. if ( pPlayer->GetPlayerClass()->GetClassIndex() != pBot->GetPlayerClass()->GetClassIndex() )
  1274. {
  1275. pBot->AllowInstantSpawn();
  1276. pBot->HandleCommand_JoinClass( pPlayer->GetPlayerClass()->GetName() );
  1277. }
  1278. int nLastSlot = LOADOUT_POSITION_MISC2;
  1279. #ifdef STAGING_ONLY
  1280. //nLastSlot = LOADOUT_POSITION_MISC10;
  1281. #endif // STAGING_ONLY
  1282. pBot->RemoveAllItems( false );
  1283. for ( int i = 0; i <= nLastSlot; ++i )
  1284. {
  1285. CEconItemView *pPlayerItem = pPlayer->GetLoadoutItem( pPlayer->GetPlayerClass()->GetClassIndex(), i );
  1286. if ( !pPlayerItem )
  1287. continue;
  1288. BotGenerateAndWearItem( pBot, pPlayerItem );
  1289. }
  1290. }
  1291. //------------------------------------------------------------------------------
  1292. // Purpose:
  1293. //------------------------------------------------------------------------------
  1294. CON_COMMAND_F( bot_mirror, "Forces the specified bot to be the same class, and use the same items, as you.", FCVAR_CHEAT )
  1295. {
  1296. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_GetCommandClient() );
  1297. if ( !pTFPlayer )
  1298. return;
  1299. CUtlVector< CTFPlayer* > botVector;
  1300. GetBotsFromCommand( args, 2, "Usage: bot_mirror <bot name>, or bot_mirror all", &botVector );
  1301. FOR_EACH_VEC( botVector, i )
  1302. {
  1303. BotMirrorPlayerClassAndItems( botVector[i], pTFPlayer );
  1304. }
  1305. }
  1306. #ifdef STAGING_ONLY
  1307. //------------------------------------------------------------------------------
  1308. // Purpose: Force the specified bot to create & equip an item
  1309. //------------------------------------------------------------------------------
  1310. void cc_bot_equip( const CCommand &args )
  1311. {
  1312. CUtlVector< CTFPlayer* > botVector;
  1313. GetBotsFromCommand( args, 3, "Usage: bot_equip <bot name> <item name>", &botVector );
  1314. FOR_EACH_VEC( botVector, i )
  1315. {
  1316. BotGenerateAndWearItem( botVector[i], args[2] );
  1317. }
  1318. }
  1319. 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 );
  1320. //------------------------------------------------------------------------------
  1321. // Purpose: Force the specified bot to disguise themselves. Needed because the disguise command is clientside.
  1322. //------------------------------------------------------------------------------
  1323. void cc_bot_disguise( const CCommand &args )
  1324. {
  1325. CUtlVector< CTFPlayer* > botVector;
  1326. GetBotsFromCommand( args, 4, "Usage: bot_disguise <bot name> <team> <class>", &botVector );
  1327. FOR_EACH_VEC( botVector, i )
  1328. {
  1329. if ( botVector[i]->CanDisguise() )
  1330. {
  1331. // intercepting the team value and reassigning what gets passed into Disguise()
  1332. // because the team numbers in the client menu don't match the #define values for the teams
  1333. botVector[i]->m_Shared.Disguise( Q_atoi( args[2] ), Q_atoi( args[3] ) );
  1334. }
  1335. }
  1336. }
  1337. 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 );
  1338. //------------------------------------------------------------------------------
  1339. // Purpose: Force the specified bot to taunt with specific taunt name
  1340. //------------------------------------------------------------------------------
  1341. void cc_bot_taunt( const CCommand &args )
  1342. {
  1343. CUtlVector< CTFPlayer* > botVector;
  1344. GetBotsFromCommand( args, 3, "Usage: bot_taunt <bot name> <taunt_name>", &botVector );
  1345. if ( botVector.IsEmpty() )
  1346. return;
  1347. const char *pszTauntName = args.ArgS() + V_strlen( args[1] ) + 1;
  1348. CEconItemDefinition *pItemDef = ItemSystem()->GetStaticDataForItemByName( pszTauntName );
  1349. if ( !pItemDef )
  1350. {
  1351. Msg( "bot_taunt: failed to find taunt name <%s>\n", pszTauntName );
  1352. return;
  1353. }
  1354. static CEconItemView item;
  1355. item.SetItemDefIndex( pItemDef->GetDefinitionIndex() );
  1356. item.SetItemQuality( pItemDef->GetQuality() );
  1357. item.SetInitialized( true );
  1358. FOR_EACH_VEC( botVector, i )
  1359. {
  1360. botVector[i]->PlayTauntSceneFromItem( &item );
  1361. }
  1362. }
  1363. 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 );
  1364. #endif // STAGING_ONLY
  1365. //------------------------------------------------------------------------------
  1366. // Purpose: Force the specified bot to select a weapon in the specified slot
  1367. //------------------------------------------------------------------------------
  1368. 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 )
  1369. {
  1370. CUtlVector< CTFPlayer* > botVector;
  1371. GetBotsFromCommand( args, 3, "Usage: bot_selectweapon <bot name> <weapon slot>", &botVector );
  1372. if ( botVector.IsEmpty() )
  1373. return;
  1374. int slot = atoi( args[2] );
  1375. FOR_EACH_VEC( botVector, i )
  1376. {
  1377. CBaseCombatWeapon *pWpn = botVector[i]->Weapon_GetSlot( slot );
  1378. if ( pWpn )
  1379. {
  1380. botVector[i]->Weapon_Switch( pWpn );
  1381. }
  1382. }
  1383. }
  1384. //------------------------------------------------------------------------------
  1385. // Purpose: Force the specified bot to drop all his gear.
  1386. //------------------------------------------------------------------------------
  1387. CON_COMMAND_F( bot_drop, "Force the specified bot to drop his active weapon. Usage: bot_drop <bot name>", FCVAR_CHEAT )
  1388. {
  1389. CUtlVector< CTFPlayer* > botVector;
  1390. GetBotsFromCommand( args, 2, "Usage: bot_drop <bot name>", &botVector );
  1391. FOR_EACH_VEC( botVector, i )
  1392. {
  1393. CBaseCombatWeapon* pWpn = botVector[i]->GetActiveWeapon();
  1394. if ( pWpn )
  1395. {
  1396. UTIL_Remove( pWpn );
  1397. }
  1398. }
  1399. }
  1400. //------------------------------------------------------------------------------
  1401. // Purpose: Force the specified bot to move to the point under your crosshair
  1402. //------------------------------------------------------------------------------
  1403. CON_COMMAND_F( bot_moveto, "Force the specified bot to move to the point under your crosshair. Usage: bot_moveto <bot name>", FCVAR_CHEAT )
  1404. {
  1405. CUtlVector< CTFPlayer* > botVector;
  1406. GetBotsFromCommand( args, 2, "Usage: bot_moveto <bot name>", &botVector );
  1407. if ( botVector.IsEmpty() )
  1408. return;
  1409. CBasePlayer *pPlayer = UTIL_GetCommandClient();
  1410. trace_t tr;
  1411. Vector forward;
  1412. pPlayer->EyeVectors( &forward );
  1413. UTIL_TraceLine( pPlayer->EyePosition(), pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
  1414. if ( tr.fraction != 1.0 )
  1415. {
  1416. FOR_EACH_VEC( botVector, i )
  1417. {
  1418. botdata_t *botdata = BotData( botVector[i] );
  1419. botdata->FindPathTo( tr.endpos );
  1420. }
  1421. }
  1422. }
  1423. //------------------------------------------------------------------------------
  1424. // Purpose:
  1425. //------------------------------------------------------------------------------
  1426. bool botdata_t::FindPathTo( Vector vecTarget )
  1427. {
  1428. m_flRecomputePathAt = gpGlobals->curtime + bot_nav_recomputetime.GetFloat();
  1429. m_vecLastPathTarget = vecTarget;
  1430. ShortestPathCost cost;
  1431. CNavArea *pCurrentArea = TheNavMesh->GetNavArea( m_hBot->GetAbsOrigin() );
  1432. if ( !pCurrentArea )
  1433. {
  1434. Warning( "Bot '%s' not on the nav mesh.\n", m_hBot->GetPlayerName() );
  1435. return false;
  1436. }
  1437. CNavArea *pTargetArea = TheNavMesh->GetNavArea( vecTarget );
  1438. if ( !pTargetArea )
  1439. {
  1440. 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 );
  1441. return false;
  1442. }
  1443. // If the path stays within a single area, build a single waypoint and we're done.
  1444. if ( pTargetArea == pCurrentArea )
  1445. {
  1446. m_path[0].pos = vecTarget;
  1447. m_path[0].area = pCurrentArea;
  1448. m_path[0].how = NUM_TRAVERSE_TYPES;
  1449. m_iPathLength = 1;
  1450. m_iPathIndex = 0;
  1451. return true;
  1452. }
  1453. if ( NavAreaBuildPath( pCurrentArea, pTargetArea, &vecTarget, cost ) == false )
  1454. {
  1455. Warning( "Bot '%s' couldn't find a path from nav mesh %d to nav mesh %d.\n", m_hBot->GetPlayerName(), pCurrentArea->GetID(), pTargetArea->GetID() );
  1456. return false;
  1457. }
  1458. m_iPathIndex = 0;
  1459. // Count the number of areas in the path
  1460. int count = 0;
  1461. CNavArea *area;
  1462. for( area = pTargetArea; area; area = area->GetParent() )
  1463. {
  1464. ++count;
  1465. }
  1466. bool bUseOffsetPaths = bot_nav_useoffsetpaths.GetBool();
  1467. if ( bUseOffsetPaths )
  1468. {
  1469. count = (count * 2) - 1;
  1470. }
  1471. m_iPathLength = count;
  1472. // Move the areas into our path
  1473. for( area = pTargetArea; count && area; area = area->GetParent() )
  1474. {
  1475. --count;
  1476. m_path[ count ].area = area;
  1477. m_path[ count ].how = area->GetParentHow();
  1478. m_path[ count ].flOffset = bUseOffsetPaths ? -bot_nav_offsetpathinset.GetFloat() : 0;
  1479. if ( bUseOffsetPaths && count >= 1 )
  1480. {
  1481. --count;
  1482. m_path[ count ].area = m_path[ count+1 ].area;
  1483. m_path[ count ].how = m_path[ count+1 ].how;
  1484. m_path[ count ].flOffset = m_path[ count+1 ].flOffset * -1;
  1485. }
  1486. }
  1487. if ( !ComputePathPositions() )
  1488. {
  1489. ResetPath();
  1490. return false;
  1491. }
  1492. // append path end position
  1493. m_path[ m_iPathLength ].area = pTargetArea;
  1494. m_path[ m_iPathLength ].pos = vecTarget;
  1495. m_path[ m_iPathLength ].how = NUM_TRAVERSE_TYPES;
  1496. m_path[ m_iPathLength ].flOffset = 0;
  1497. ++m_iPathLength;
  1498. return true;
  1499. }
  1500. //------------------------------------------------------------------------------
  1501. // Purpose: Given a path of areas, compute waypoints along the path
  1502. //------------------------------------------------------------------------------
  1503. bool botdata_t::ComputePathPositions( void )
  1504. {
  1505. if (m_iPathLength == 0)
  1506. return false;
  1507. m_path[0].pos = m_hBot->GetAbsOrigin();//m_path[0].area->GetCenter();
  1508. m_path[0].how = NUM_TRAVERSE_TYPES;
  1509. for( int i=1; i<m_iPathLength; ++i )
  1510. {
  1511. const ConnectInfo *from = &m_path[ i-1 ];
  1512. ConnectInfo *to = &m_path[ i ];
  1513. Vector vecFrom = from->pos;
  1514. if ( to->flOffset < 0 && i >= 2 )
  1515. {
  1516. from = &m_path[ i-2 ];
  1517. }
  1518. from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, from->pos, &to->pos );
  1519. to->pos.z = from->area->GetZ( to->pos );
  1520. if ( to->flOffset )
  1521. {
  1522. // Create a waypoint just inside our current area
  1523. AddDirectionVector( &to->pos, (NavDirType)to->how, -to->flOffset );
  1524. }
  1525. if ( bot_debug.GetInt() == 2 )
  1526. {
  1527. NDebugOverlay::HorzArrow( vecFrom, to->pos, 3, 0, 96, 0, 0, true, 10.0 );
  1528. }
  1529. }
  1530. return true;
  1531. }
  1532. //------------------------------------------------------------------------------
  1533. // Purpose:
  1534. //------------------------------------------------------------------------------
  1535. bool botdata_t::UpdatePath( void )
  1536. {
  1537. // If we're going to an entity, make sure it hasn't moved.
  1538. if ( CommandHasATarget() )
  1539. {
  1540. if ( m_flRecomputePathAt < gpGlobals->curtime && m_Commands[0].pTarget )
  1541. {
  1542. Vector vecNewTarget = m_Commands[0].pTarget->GetAbsOrigin();
  1543. if ( (m_vecLastPathTarget - vecNewTarget).LengthSqr() > (50 * 50) )
  1544. return FindPathTo( vecNewTarget );
  1545. }
  1546. }
  1547. return true;
  1548. }
  1549. //------------------------------------------------------------------------------
  1550. // Purpose:
  1551. //------------------------------------------------------------------------------
  1552. void botdata_t::UpdatePlanForCommand( void )
  1553. {
  1554. if ( !m_Commands.Count() )
  1555. return;
  1556. if ( !m_bStartedCommand )
  1557. {
  1558. StartNewCommand();
  1559. m_bStartedCommand = true;
  1560. }
  1561. switch ( m_Commands[0].iCommand )
  1562. {
  1563. case BC_ATTACK:
  1564. {
  1565. RunAttackPlan();
  1566. }
  1567. break;
  1568. case BC_MOVETO_ENTITY:
  1569. {
  1570. if ( !HasPath() )
  1571. {
  1572. if ( FindPathTo( m_Commands[0].pTarget->GetAbsOrigin() ) == false )
  1573. {
  1574. FinishCommand();
  1575. }
  1576. }
  1577. }
  1578. break;
  1579. case BC_MOVETO_POINT:
  1580. {
  1581. if ( !HasPath() )
  1582. {
  1583. if ( FindPathTo( GetCommandPosition() ) == false )
  1584. {
  1585. FinishCommand();
  1586. }
  1587. }
  1588. }
  1589. break;
  1590. case BC_SWITCH_WEAPON:
  1591. {
  1592. CBaseCombatWeapon *pWpn = m_hBot->Weapon_GetSlot( (int)m_Commands[0].flData );
  1593. if ( pWpn )
  1594. {
  1595. m_hBot->Weapon_Switch( pWpn );
  1596. }
  1597. FinishCommand();
  1598. }
  1599. break;
  1600. case BC_DEFEND:
  1601. {
  1602. RunDefendPlan();
  1603. }
  1604. break;
  1605. default:
  1606. break;
  1607. }
  1608. }
  1609. //------------------------------------------------------------------------------
  1610. // Purpose:
  1611. //------------------------------------------------------------------------------
  1612. void botdata_t::RunAttackPlan( void )
  1613. {
  1614. CBaseEntity *pEnt = GetCommandTarget();
  1615. if ( !pEnt )
  1616. return;
  1617. if ( !pEnt->IsAlive() )
  1618. {
  1619. FinishCommand();
  1620. return;
  1621. }
  1622. bool bCanSeeTarget = m_hBot->FVisible( pEnt );
  1623. bool bNeedsAPath = !bCanSeeTarget;
  1624. if ( bCanSeeTarget )
  1625. {
  1626. float flDistance = (pEnt->GetAbsOrigin() - m_hBot->GetAbsOrigin()).LengthSqr();
  1627. // If it's a melee weapon, we need to close. Otherwise, stay at a nice looking distance.
  1628. if ( m_hBot->GetActiveTFWeapon() && m_hBot->GetActiveTFWeapon()->IsMeleeWeapon() )
  1629. {
  1630. float flRange = bot_com_meleerange.GetFloat();
  1631. flRange *= flRange;
  1632. bNeedsAPath = ( flDistance > flRange );
  1633. if ( !bNeedsAPath )
  1634. {
  1635. buttons |= IN_ATTACK;
  1636. }
  1637. }
  1638. else
  1639. {
  1640. float flRange = bot_com_wpnrange.GetFloat();
  1641. flRange *= flRange;
  1642. bNeedsAPath = ( flDistance > (500 * 500) );
  1643. buttons |= IN_ATTACK;
  1644. }
  1645. }
  1646. if ( bNeedsAPath && !HasPath() )
  1647. {
  1648. if ( FindPathTo( m_Commands[0].pTarget->GetAbsOrigin() ) == false )
  1649. {
  1650. FinishCommand();
  1651. return;
  1652. }
  1653. }
  1654. else if ( !bNeedsAPath && HasPath() )
  1655. {
  1656. ResetPath();
  1657. }
  1658. }
  1659. //------------------------------------------------------------------------------
  1660. // Purpose:
  1661. //------------------------------------------------------------------------------
  1662. void botdata_t::RunDefendPlan( void )
  1663. {
  1664. if ( bot_debug.GetInt() == 5 )
  1665. {
  1666. NDebugOverlay::Line( m_hBot->GetAbsOrigin(), GetCommandPosition(), 0, 128, 0, true, 0.1 );
  1667. }
  1668. if ( !FindEnemyTarget() )
  1669. {
  1670. if ( !HasPath() )
  1671. {
  1672. // If we're far from our defend point, move back to it.
  1673. float flDistance = (GetCommandPosition() - m_hBot->GetAbsOrigin()).LengthSqr();
  1674. if ( flDistance > (32*32) )
  1675. {
  1676. if ( FindPathTo( GetCommandPosition() ) == false )
  1677. {
  1678. // Can't get back to our defend position
  1679. FinishCommand();
  1680. return;
  1681. }
  1682. }
  1683. }
  1684. return;
  1685. }
  1686. Assert( m_hEnemy.Get() );
  1687. if ( bot_debug.GetInt() == 5 )
  1688. {
  1689. NDebugOverlay::Line( m_hBot->GetAbsOrigin(), m_hEnemy->GetAbsOrigin(), 255, 0, 0, true, 0.1 );
  1690. }
  1691. // We've got an enemy.
  1692. float flDefendRange = m_Commands[0].flData * m_Commands[0].flData;
  1693. float flDistanceToEnemy = (m_hEnemy->GetAbsOrigin() - m_hBot->GetAbsOrigin()).LengthSqr();
  1694. float flDistanceToEnemyFromDefend = (m_hEnemy->GetAbsOrigin() - GetCommandPosition()).LengthSqr();
  1695. bool bNeedsAPath = false;
  1696. // If it's a melee weapon, we need to close. Otherwise, stay at a nice looking distance.
  1697. if ( m_hBot->GetActiveTFWeapon() && m_hBot->GetActiveTFWeapon()->IsMeleeWeapon() )
  1698. {
  1699. // We can only close if we're allowed to move to the target's distance.
  1700. if ( flDistanceToEnemyFromDefend <= flDefendRange )
  1701. {
  1702. float flRange = bot_com_meleerange.GetFloat();
  1703. flRange *= flRange;
  1704. bNeedsAPath = ( flDistanceToEnemy > flRange );
  1705. if ( !bNeedsAPath )
  1706. {
  1707. buttons |= IN_ATTACK;
  1708. }
  1709. }
  1710. }
  1711. else
  1712. {
  1713. buttons |= IN_ATTACK;
  1714. }
  1715. if ( bNeedsAPath && !HasPath() )
  1716. {
  1717. FindPathTo( m_hEnemy->GetAbsOrigin() );
  1718. }
  1719. else if ( !bNeedsAPath && HasPath() )
  1720. {
  1721. ResetPath();
  1722. }
  1723. }
  1724. //-----------------------------------------------------------------------------
  1725. // Purpose: Look for a target
  1726. //-----------------------------------------------------------------------------
  1727. bool botdata_t::FindEnemyTarget( void )
  1728. {
  1729. Vector vecEyePosition = m_hBot->EyePosition();
  1730. // find the enemy team
  1731. int iEnemyTeam = ( m_hBot->GetTeamNumber() == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
  1732. CTFTeam *pTeam = TFTeamMgr()->GetTeam( iEnemyTeam );
  1733. if ( !pTeam )
  1734. return false;
  1735. // If we have an enemy get his minimum distance to check against.
  1736. Vector vecSegment;
  1737. Vector vecTargetCenter;
  1738. float flMinDist2 = bot_com_viewrange.GetFloat();
  1739. flMinDist2 *= flMinDist2;
  1740. CBaseEntity *pTargetCurrent = NULL;
  1741. CBaseEntity *pTargetOld = m_hEnemy;
  1742. float flOldTargetDist2 = FLT_MAX;
  1743. // Try to target players first, then objects. However, if the enemy held was an object it will continue
  1744. // to try and attack it first.
  1745. int nTeamCount = pTeam->GetNumPlayers();
  1746. for ( int iPlayer = 0; iPlayer < nTeamCount; ++iPlayer )
  1747. {
  1748. CTFPlayer *pTargetPlayer = static_cast<CTFPlayer*>( pTeam->GetPlayer( iPlayer ) );
  1749. if ( pTargetPlayer == NULL )
  1750. continue;
  1751. // Make sure the player is alive.
  1752. if ( !pTargetPlayer->IsAlive() )
  1753. continue;
  1754. if ( pTargetPlayer->GetFlags() & FL_NOTARGET )
  1755. continue;
  1756. vecTargetCenter = pTargetPlayer->GetAbsOrigin();
  1757. vecTargetCenter += pTargetPlayer->GetViewOffset();
  1758. VectorSubtract( vecTargetCenter, vecEyePosition, vecSegment );
  1759. float flDist2 = vecSegment.LengthSqr();
  1760. // Check to see if the target is closer than the already validated target.
  1761. if ( flDist2 > flMinDist2 )
  1762. continue;
  1763. // It is closer, check to see if the target is valid.
  1764. if ( ValidTargetPlayer( pTargetPlayer, vecEyePosition, vecTargetCenter ) )
  1765. {
  1766. flMinDist2 = flDist2;
  1767. pTargetCurrent = pTargetPlayer;
  1768. // Store the current target distance if we come across it
  1769. if ( pTargetPlayer == pTargetOld )
  1770. {
  1771. flOldTargetDist2 = flDist2;
  1772. }
  1773. }
  1774. }
  1775. // If we already have a target, don't check objects.
  1776. if ( pTargetCurrent == NULL )
  1777. {
  1778. int nTeamObjectCount = pTeam->GetNumObjects();
  1779. for ( int iObject = 0; iObject < nTeamObjectCount; ++iObject )
  1780. {
  1781. CBaseObject *pTargetObject = pTeam->GetObject( iObject );
  1782. if ( !pTargetObject )
  1783. continue;
  1784. vecTargetCenter = pTargetObject->GetAbsOrigin();
  1785. vecTargetCenter += pTargetObject->GetViewOffset();
  1786. VectorSubtract( vecTargetCenter, vecEyePosition, vecSegment );
  1787. float flDist2 = vecSegment.LengthSqr();
  1788. // Store the current target distance if we come across it
  1789. if ( pTargetObject == pTargetOld )
  1790. {
  1791. flOldTargetDist2 = flDist2;
  1792. }
  1793. // Check to see if the target is closer than the already validated target.
  1794. if ( flDist2 > flMinDist2 )
  1795. continue;
  1796. // It is closer, check to see if the target is valid.
  1797. if ( ValidTargetObject( pTargetObject, vecEyePosition, vecTargetCenter ) )
  1798. {
  1799. flMinDist2 = flDist2;
  1800. pTargetCurrent = pTargetObject;
  1801. }
  1802. }
  1803. }
  1804. // We have a target.
  1805. if ( pTargetCurrent )
  1806. {
  1807. if ( pTargetCurrent != pTargetOld )
  1808. {
  1809. // flMinDist2 is the new target's distance
  1810. // flOldTargetDist2 is the old target's distance
  1811. // Don't switch unless the new target is closer by some percentage
  1812. if ( flMinDist2 < ( flOldTargetDist2 * 0.75f ) )
  1813. {
  1814. m_hEnemy = pTargetCurrent;
  1815. }
  1816. }
  1817. return true;
  1818. }
  1819. return false;
  1820. }
  1821. //-----------------------------------------------------------------------------
  1822. // Purpose:
  1823. //-----------------------------------------------------------------------------
  1824. bool botdata_t::ValidTargetPlayer( CTFPlayer *pPlayer, const Vector &vecStart, const Vector &vecEnd )
  1825. {
  1826. if ( m_bIgnoreHumans && !pPlayer->IsFakeClient() )
  1827. return false;
  1828. // Keep shooting at spies that go invisible after we acquire them as a target.
  1829. if ( pPlayer->m_Shared.GetPercentInvisible() > 0.5 )
  1830. return false;
  1831. // Keep shooting at spies that disguise after we acquire them as at a target.
  1832. if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->m_Shared.GetDisguiseTeam() == m_hBot->GetTeamNumber() && pPlayer != m_hEnemy )
  1833. return false;
  1834. // Not across water boundary.
  1835. if ( ( m_hBot->GetWaterLevel() == 0 && pPlayer->GetWaterLevel() >= 3 ) || ( m_hBot->GetWaterLevel() == 3 && pPlayer->GetWaterLevel() <= 0 ) )
  1836. return false;
  1837. // Ray trace!!!
  1838. return m_hBot->FVisible( pPlayer, MASK_SHOT | CONTENTS_GRATE );
  1839. }
  1840. //-----------------------------------------------------------------------------
  1841. // Purpose:
  1842. //-----------------------------------------------------------------------------
  1843. bool botdata_t::ValidTargetObject( CBaseObject *pObject, const Vector &vecStart, const Vector &vecEnd )
  1844. {
  1845. // Ignore objects being placed, they are not real objects yet.
  1846. if ( pObject->IsPlacing() )
  1847. return false;
  1848. // Ignore sappers.
  1849. if ( pObject->MustBeBuiltOnAttachmentPoint() )
  1850. return false;
  1851. // Not across water boundary.
  1852. if ( ( m_hBot->GetWaterLevel() == 0 && pObject->GetWaterLevel() >= 3 ) || ( m_hBot->GetWaterLevel() == 3 && pObject->GetWaterLevel() <= 0 ) )
  1853. return false;
  1854. if ( pObject->GetObjectFlags() & OF_DOESNT_HAVE_A_MODEL )
  1855. return false;
  1856. // Ray trace.
  1857. return m_hBot->FVisible( pObject, MASK_SHOT | CONTENTS_GRATE );
  1858. }
  1859. //------------------------------------------------------------------------------
  1860. // Purpose:
  1861. //------------------------------------------------------------------------------
  1862. void botdata_t::StartNewCommand( void )
  1863. {
  1864. switch ( m_Commands[0].iCommand )
  1865. {
  1866. case BC_DEFEND:
  1867. {
  1868. // Mark our current position as the point we're defending
  1869. m_Commands[0].vecTarget = m_hBot->GetAbsOrigin();
  1870. }
  1871. break;
  1872. case BC_ATTACK:
  1873. case BC_MOVETO_ENTITY:
  1874. case BC_MOVETO_POINT:
  1875. case BC_SWITCH_WEAPON:
  1876. default:
  1877. break;
  1878. }
  1879. }
  1880. //------------------------------------------------------------------------------
  1881. // Purpose:
  1882. //------------------------------------------------------------------------------
  1883. void botdata_t::FinishCommand( void )
  1884. {
  1885. if ( m_Commands.Count() > 0 )
  1886. {
  1887. m_hBotController->m_outputOnCommandFinished.FireOutput( m_hBot, m_hBot );
  1888. m_Commands.Remove(0);
  1889. }
  1890. m_bStartedCommand = false;
  1891. }
  1892. //------------------------------------------------------------------------------
  1893. // Purpose:
  1894. //------------------------------------------------------------------------------
  1895. void botdata_t::AddAttackCommand( CBaseEntity *pTarget )
  1896. {
  1897. int iIndex = m_Commands.AddToTail();
  1898. m_Commands[iIndex].iCommand = BC_ATTACK;
  1899. m_Commands[iIndex].pTarget = pTarget;
  1900. m_Commands[iIndex].vecTarget = vec3_origin;
  1901. }
  1902. //------------------------------------------------------------------------------
  1903. // Purpose:
  1904. //------------------------------------------------------------------------------
  1905. void botdata_t::AddMoveToCommand( CBaseEntity *pTarget, Vector vecTarget )
  1906. {
  1907. int iIndex = m_Commands.AddToTail();
  1908. m_Commands[iIndex].iCommand = pTarget ? BC_MOVETO_ENTITY : BC_MOVETO_POINT;
  1909. m_Commands[iIndex].pTarget = pTarget;
  1910. m_Commands[iIndex].vecTarget = vecTarget;
  1911. }
  1912. //------------------------------------------------------------------------------
  1913. // Purpose:
  1914. //------------------------------------------------------------------------------
  1915. void botdata_t::AddSwitchWeaponCommand( int iSlot )
  1916. {
  1917. int iIndex = m_Commands.AddToTail();
  1918. m_Commands[iIndex].iCommand = BC_SWITCH_WEAPON;
  1919. m_Commands[iIndex].flData = iSlot;
  1920. }
  1921. //------------------------------------------------------------------------------
  1922. // Purpose:
  1923. //------------------------------------------------------------------------------
  1924. void botdata_t::AddDefendCommand( float flRange )
  1925. {
  1926. int iIndex = m_Commands.AddToTail();
  1927. m_Commands[iIndex].iCommand = BC_DEFEND;
  1928. m_Commands[iIndex].flData = flRange;
  1929. }
  1930. //------------------------------------------------------------------------------
  1931. // Purpose:
  1932. //------------------------------------------------------------------------------
  1933. void botdata_t::ClearQueue( void )
  1934. {
  1935. FinishCommand();
  1936. m_Commands.Purge();
  1937. }
  1938. //------------------------------------------------------------------------------
  1939. // Purpose:
  1940. //------------------------------------------------------------------------------
  1941. bool botdata_t::RunningMovementCommand( void )
  1942. {
  1943. if ( m_Commands.Count() )
  1944. return ( m_Commands[0].iCommand == BC_MOVETO_ENTITY || m_Commands[0].iCommand == BC_MOVETO_POINT );
  1945. return false;
  1946. }
  1947. //------------------------------------------------------------------------------
  1948. // Purpose:
  1949. //------------------------------------------------------------------------------
  1950. bool botdata_t::CommandHasATarget( void )
  1951. {
  1952. return ( m_Commands.Count() && (m_Commands[0].iCommand == BC_MOVETO_ENTITY || m_Commands[0].iCommand == BC_ATTACK) );
  1953. }
  1954. //------------------------------------------------------------------------------
  1955. // Purpose:
  1956. //------------------------------------------------------------------------------
  1957. bool botdata_t::ShouldFinishCommandOnArrival( void )
  1958. {
  1959. return ( m_Commands.Count() && (m_Commands[0].iCommand == BC_MOVETO_ENTITY || m_Commands[0].iCommand == BC_MOVETO_POINT) );
  1960. }
  1961. //-----------------------------------------------------------------------------
  1962. // Purpose: Handle movement
  1963. //-----------------------------------------------------------------------------
  1964. void botdata_t::Bot_ItemTestingThink( QAngle *vecAngles, Vector *vecMove )
  1965. {
  1966. switch ( TFGameRules()->ItemTesting_GetBotAnim() )
  1967. {
  1968. default:
  1969. case TI_BOTANIM_IDLE:
  1970. break;
  1971. case TI_BOTANIM_CROUCH:
  1972. buttons |= IN_DUCK;
  1973. break;
  1974. case TI_BOTANIM_RUN:
  1975. break;
  1976. case TI_BOTANIM_CROUCH_WALK:
  1977. buttons |= IN_DUCK;
  1978. break;
  1979. case TI_BOTANIM_JUMP:
  1980. if ( m_hBot->GetFlags() & FL_ONGROUND )
  1981. {
  1982. buttons |= IN_JUMP;
  1983. }
  1984. break;
  1985. }
  1986. if ( TFGameRules()->ItemTesting_GetBotForceFire() )
  1987. {
  1988. // Hack to make them not fire faster than the anims being played?
  1989. //if ( !m_hBot->GetAnimState()->IsGestureSlotActive( GESTURE_SLOT_ATTACK_AND_RELOAD ) )
  1990. buttons |= IN_ATTACK;
  1991. }
  1992. if ( TFGameRules()->ItemTesting_GetBotTurntable() )
  1993. {
  1994. (*vecAngles)[YAW] += (1.0f * TFGameRules()->ItemTesting_GetBotAnimSpeed());
  1995. if ( (*vecAngles)[YAW] > 360 )
  1996. {
  1997. (*vecAngles)[YAW] = 0;
  1998. }
  1999. }
  2000. (*vecMove)[0] = 0;
  2001. (*vecMove)[1] = 0;
  2002. (*vecMove)[2] = 0;
  2003. }
  2004. //===================================================================================================================
  2005. // Purpose: Mapmaker bot control entity. Used by mapmakers to add & script bot behaviors.
  2006. BEGIN_DATADESC( CTFBotController )
  2007. DEFINE_KEYFIELD( m_iszBotName, FIELD_STRING, "bot_name" ),
  2008. DEFINE_KEYFIELD( m_iBotClass, FIELD_INTEGER, "bot_class" ),
  2009. DEFINE_INPUTFUNC( FIELD_VOID, "CreateBot", InputCreateBot ),
  2010. DEFINE_INPUTFUNC( FIELD_VOID, "RespawnBot", InputRespawnBot ),
  2011. DEFINE_INPUTFUNC( FIELD_STRING, "AddCommandMoveToEntity", InputBotAddCommandMoveToEntity ),
  2012. DEFINE_INPUTFUNC( FIELD_STRING, "AddCommandAttackEntity", InputBotAddCommandAttackEntity ),
  2013. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddCommandSwitchWeapon", InputBotAddCommandSwitchWeapon ),
  2014. DEFINE_INPUTFUNC( FIELD_FLOAT, "AddCommandDefend", InputBotAddCommandDefend ),
  2015. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetIgnoreHumans", InputBotSetIgnoreHumans ),
  2016. DEFINE_INPUTFUNC( FIELD_VOID, "PreventMovement", InputBotPreventMovement ),
  2017. DEFINE_INPUTFUNC( FIELD_VOID, "ClearQueue", InputBotClearQueue ),
  2018. DEFINE_OUTPUT( m_outputOnCommandFinished, "OnCommandFinished" ),
  2019. END_DATADESC()
  2020. LINK_ENTITY_TO_CLASS( bot_controller, CTFBotController );
  2021. //------------------------------------------------------------------------------
  2022. // Purpose:
  2023. //------------------------------------------------------------------------------
  2024. void CTFBotController::InputCreateBot( inputdata_t &inputdata )
  2025. {
  2026. m_hBot = ToTFPlayer( BotPutInServer( false, false, GetTeamNumber(), m_iBotClass ? m_iBotClass : TF_CLASS_RANDOM, STRING(m_iszBotName) ) );
  2027. if ( m_hBot )
  2028. {
  2029. BotData(m_hBot)->m_hBotController = this;
  2030. }
  2031. }
  2032. //------------------------------------------------------------------------------
  2033. // Purpose:
  2034. //------------------------------------------------------------------------------
  2035. void CTFBotController::InputRespawnBot( inputdata_t &inputdata )
  2036. {
  2037. if ( !m_hBot->GetPlayerClass() || m_hBot->GetPlayerClass()->GetClassIndex() == TF_CLASS_UNDEFINED )
  2038. {
  2039. // Allow them to spawn instantly when they do choose
  2040. m_hBot->AllowInstantSpawn();
  2041. return;
  2042. }
  2043. m_hBot->ForceRespawn();
  2044. }
  2045. //------------------------------------------------------------------------------
  2046. // Purpose:
  2047. //------------------------------------------------------------------------------
  2048. void CTFBotController::InputBotAddCommandMoveToEntity( inputdata_t &inputdata )
  2049. {
  2050. const char *pszEntName = inputdata.value.String();
  2051. if ( pszEntName && pszEntName[0] )
  2052. {
  2053. CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, pszEntName );
  2054. if ( pEnt )
  2055. {
  2056. BotData(m_hBot)->AddMoveToCommand( pEnt, vec3_origin );
  2057. }
  2058. }
  2059. }
  2060. //------------------------------------------------------------------------------
  2061. // Purpose:
  2062. //------------------------------------------------------------------------------
  2063. void CTFBotController::InputBotAddCommandAttackEntity( inputdata_t &inputdata )
  2064. {
  2065. const char *pszEntName = inputdata.value.String();
  2066. if ( pszEntName && pszEntName[0] )
  2067. {
  2068. CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, pszEntName );
  2069. if ( pEnt )
  2070. {
  2071. BotData(m_hBot)->AddAttackCommand( pEnt );
  2072. }
  2073. }
  2074. }
  2075. //------------------------------------------------------------------------------
  2076. // Purpose:
  2077. //------------------------------------------------------------------------------
  2078. void CTFBotController::InputBotAddCommandSwitchWeapon( inputdata_t &inputdata )
  2079. {
  2080. BotData(m_hBot)->AddSwitchWeaponCommand( inputdata.value.Int() );
  2081. }
  2082. //------------------------------------------------------------------------------
  2083. // Purpose:
  2084. //------------------------------------------------------------------------------
  2085. void CTFBotController::InputBotAddCommandDefend( inputdata_t &inputdata )
  2086. {
  2087. BotData(m_hBot)->AddDefendCommand( inputdata.value.Float() );
  2088. }
  2089. //------------------------------------------------------------------------------
  2090. // Purpose:
  2091. //------------------------------------------------------------------------------
  2092. void CTFBotController::InputBotSetIgnoreHumans( inputdata_t &inputdata )
  2093. {
  2094. BotData(m_hBot)->SetIgnoreHumans( inputdata.value.Int() > 0 );
  2095. }
  2096. //------------------------------------------------------------------------------
  2097. // Purpose:
  2098. //------------------------------------------------------------------------------
  2099. void CTFBotController::InputBotPreventMovement( inputdata_t &inputdata )
  2100. {
  2101. BotData(m_hBot)->SetFrozen( inputdata.value.Bool() );
  2102. }
  2103. //------------------------------------------------------------------------------
  2104. // Purpose:
  2105. //------------------------------------------------------------------------------
  2106. void CTFBotController::InputBotClearQueue( inputdata_t &inputdata )
  2107. {
  2108. BotData(m_hBot)->ClearQueue();
  2109. }