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.

4644 lines
121 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // tf_bot.cpp
  3. // Team Fortress NextBot
  4. // Michael Booth, February 2009
  5. #include "cbase.h"
  6. #include "tf_player.h"
  7. #include "tf_gamerules.h"
  8. #include "tf_obj_sentrygun.h"
  9. #include "team_control_point_master.h"
  10. #include "tf_weapon_pipebomblauncher.h"
  11. #include "team_train_watcher.h"
  12. #include "tf_bot.h"
  13. #include "tf_bot_manager.h"
  14. #include "tf_bot_vision.h"
  15. #include "tf_team.h"
  16. #include "bot/map_entities/tf_bot_generator.h"
  17. #include "trigger_area_capture.h"
  18. #include "GameEventListener.h"
  19. #include "NextBotUtil.h"
  20. #include "tier3/tier3.h"
  21. #include "vgui/ILocalize.h"
  22. #include "econ_item_system.h"
  23. #include "bot/behavior/tf_bot_use_item.h"
  24. #include "tf_wearable_item_demoshield.h"
  25. #include "tf_weapon_buff_item.h"
  26. #include "tf_weapon_lunchbox.h"
  27. #include "func_respawnroom.h"
  28. #include "soundenvelope.h"
  29. #include "econ_entity_creation.h"
  30. #include "player_vs_environment/tf_population_manager.h"
  31. #include "bot/behavior/tf_bot_behavior.h"
  32. #include "bot/map_entities/tf_bot_generator.h"
  33. #include "bot/map_entities/tf_bot_hint_entity.h"
  34. ConVar tf_bot_force_class( "tf_bot_force_class", "", FCVAR_GAMEDLL, "If set to a class name, all TFBots will respawn as that class" );
  35. ConVar tf_bot_notice_gunfire_range( "tf_bot_notice_gunfire_range", "3000", FCVAR_GAMEDLL );
  36. ConVar tf_bot_notice_quiet_gunfire_range( "tf_bot_notice_quiet_gunfire_range", "500", FCVAR_GAMEDLL );
  37. ConVar tf_bot_sniper_personal_space_range( "tf_bot_sniper_personal_space_range", "1000", FCVAR_CHEAT, "Enemies beyond this range don't worry the Sniper" );
  38. ConVar tf_bot_pyro_deflect_tolerance( "tf_bot_pyro_deflect_tolerance", "0.5", FCVAR_CHEAT );
  39. ConVar tf_bot_keep_class_after_death( "tf_bot_keep_class_after_death", "0", FCVAR_GAMEDLL );
  40. ConVar tf_bot_prefix_name_with_difficulty( "tf_bot_prefix_name_with_difficulty", "0", FCVAR_GAMEDLL, "Append the skill level of the bot to the bot's name" );
  41. ConVar tf_bot_near_point_travel_distance( "tf_bot_near_point_travel_distance", "750", FCVAR_CHEAT, "If within this travel distance to the current point, bot is 'near' it" );
  42. ConVar tf_bot_pyro_shove_away_range( "tf_bot_pyro_shove_away_range", "250", FCVAR_CHEAT, "If a Pyro bot's target is closer than this, compression blast them away" );
  43. ConVar tf_bot_pyro_always_reflect( "tf_bot_pyro_always_reflect", "0", FCVAR_CHEAT, "Pyro bots will always reflect projectiles fired at them. For tesing/debugging purposes." );
  44. ConVar tf_bot_sniper_spot_min_range( "tf_bot_sniper_spot_min_range", "1000", FCVAR_CHEAT );
  45. ConVar tf_bot_sniper_spot_max_count( "tf_bot_sniper_spot_max_count", "10", FCVAR_CHEAT, "Stop searching for sniper spots when each side has found this many" );
  46. ConVar tf_bot_sniper_spot_search_count( "tf_bot_sniper_spot_search_count", "10", FCVAR_CHEAT, "Search this many times per behavior update frame" );
  47. ConVar tf_bot_sniper_spot_point_tolerance( "tf_bot_sniper_spot_point_tolerance", "750", FCVAR_CHEAT );
  48. ConVar tf_bot_sniper_spot_epsilon( "tf_bot_sniper_spot_epsilon", "100", FCVAR_CHEAT );
  49. ConVar tf_bot_sniper_goal_entity_move_tolerance( "tf_bot_sniper_goal_entity_move_tolerance", "500", FCVAR_CHEAT );
  50. ConVar tf_bot_suspect_spy_touch_interval( "tf_bot_suspect_spy_touch_interval", "5", FCVAR_CHEAT, "How many seconds back to look for touches against suspicious spies" );
  51. ConVar tf_bot_suspect_spy_forget_cooldown( "tf_bot_suspect_spy_forget_cooldown", "5", FCVAR_CHEAT, "How long to consider a suspicious spy as suspicious" );
  52. ConVar tf_bot_debug_tags( "tf_bot_debug_tags", "0", FCVAR_CHEAT, "ent_text will only show tags on bots" );
  53. extern ConVar tf_bot_sniper_spot_max_count;
  54. extern ConVar tf_bot_fire_weapon_min_time;
  55. extern ConVar tf_bot_sniper_misfire_chance;
  56. extern ConVar tf_bot_difficulty;
  57. extern ConVar tf_bot_farthest_visible_theater_sample_count;
  58. extern ConVar tf_bot_sniper_spot_min_range;
  59. extern ConVar tf_bot_sniper_spot_epsilon;
  60. extern ConVar tf_mvm_miniboss_min_health;
  61. extern ConVar tf_bot_path_lookahead_range;
  62. extern ConVar tf_mvm_miniboss_scale;
  63. //-----------------------------------------------------------------------------------------------------
  64. bool IsPlayerClassname( const char *string )
  65. {
  66. for ( int i = TF_CLASS_SCOUT; i < TF_CLASS_COUNT_ALL; ++i )
  67. {
  68. if ( !stricmp( string, GetPlayerClassData( i )->m_szClassName ) )
  69. {
  70. return true;
  71. }
  72. }
  73. return false;
  74. }
  75. //-----------------------------------------------------------------------------------------------------
  76. bool IsTeamName( const char *string )
  77. {
  78. if ( !stricmp( string, "red" ) )
  79. return true;
  80. if ( !stricmp( string, "blue" ) )
  81. return true;
  82. return false;
  83. }
  84. //-----------------------------------------------------------------------------------------------------
  85. CTFBot::DifficultyType StringToDifficultyLevel( const char *string )
  86. {
  87. if ( !stricmp( string, "easy" ) )
  88. return CTFBot::EASY;
  89. if ( !stricmp( string, "normal" ) )
  90. return CTFBot::NORMAL;
  91. if ( !stricmp( string, "hard" ) )
  92. return CTFBot::HARD;
  93. if ( !stricmp( string, "expert" ) )
  94. return CTFBot::EXPERT;
  95. return CTFBot::UNDEFINED;
  96. }
  97. //-----------------------------------------------------------------------------------------------------
  98. const char *DifficultyLevelToString( CTFBot::DifficultyType skill )
  99. {
  100. switch( skill )
  101. {
  102. case CTFBot::EASY: return "Easy ";
  103. case CTFBot::NORMAL: return "Normal ";
  104. case CTFBot::HARD: return "Hard ";
  105. case CTFBot::EXPERT: return "Expert ";
  106. }
  107. return "Undefined ";
  108. }
  109. //-----------------------------------------------------------------------------------------------------
  110. const char *GetRandomBotName( void )
  111. {
  112. static const char *nameList[] =
  113. {
  114. "Chucklenuts",
  115. "CryBaby",
  116. "WITCH",
  117. "ThatGuy",
  118. "Still Alive",
  119. "Hat-Wearing MAN",
  120. "Me",
  121. "Numnutz",
  122. "H@XX0RZ",
  123. "The G-Man",
  124. "Chell",
  125. "The Combine",
  126. "Totally Not A Bot",
  127. "Pow!",
  128. "Zepheniah Mann",
  129. "THEM",
  130. "LOS LOS LOS",
  131. "10001011101",
  132. "DeadHead",
  133. "ZAWMBEEZ",
  134. "MindlessElectrons",
  135. "TAAAAANK!",
  136. "The Freeman",
  137. "Black Mesa",
  138. "Soulless",
  139. "CEDA",
  140. "BeepBeepBoop",
  141. "NotMe",
  142. "CreditToTeam",
  143. "BoomerBile",
  144. "Someone Else",
  145. "Mann Co.",
  146. "Dog",
  147. "Kaboom!",
  148. "AmNot",
  149. "0xDEADBEEF",
  150. "HI THERE",
  151. "SomeDude",
  152. "GLaDOS",
  153. "Hostage",
  154. "Headful of Eyeballs",
  155. "CrySomeMore",
  156. "Aperture Science Prototype XR7",
  157. "Humans Are Weak",
  158. "AimBot",
  159. "C++",
  160. "GutsAndGlory!",
  161. "Nobody",
  162. "Saxton Hale",
  163. "RageQuit",
  164. "Screamin' Eagles",
  165. "Ze Ubermensch",
  166. "Maggot",
  167. "CRITRAWKETS",
  168. "Herr Doktor",
  169. "Gentlemanne of Leisure",
  170. "Companion Cube",
  171. "Target Practice",
  172. "One-Man Cheeseburger Apocalypse",
  173. "Crowbar",
  174. "Delicious Cake",
  175. "IvanTheSpaceBiker",
  176. "I LIVE!",
  177. "Cannon Fodder",
  178. "trigger_hurt",
  179. "Nom Nom Nom",
  180. "Divide by Zero",
  181. "GENTLE MANNE of LEISURE",
  182. "MoreGun",
  183. "Tiny Baby Man",
  184. "Big Mean Muther Hubbard",
  185. "Force of Nature",
  186. "Crazed Gunman",
  187. "Grim Bloody Fable",
  188. "Poopy Joe",
  189. "A Professional With Standards",
  190. "Freakin' Unbelievable",
  191. "SMELLY UNFORTUNATE",
  192. "The Administrator",
  193. "Mentlegen",
  194. "Archimedes!",
  195. "Ribs Grow Back",
  196. "It's Filthy in There!",
  197. "Mega Baboon",
  198. "Kill Me",
  199. "Glorified Toaster with Legs",
  200. #ifdef STAGING_ONLY
  201. "John Spartan",
  202. "Leeloo Dallas Multipass",
  203. "Sho'nuff",
  204. "Bruce Leroy",
  205. "CAN YOUUUUUUUUU DIG IT?!?!?!?!",
  206. "Big Gulp, Huh?",
  207. "Stupid Hot Dog",
  208. "I'm your huckleberry",
  209. "The Crocketeer",
  210. #endif
  211. NULL
  212. };
  213. static int nameCount = 0;
  214. static int nameIndex = 0;
  215. if ( nameCount == 0 )
  216. {
  217. for( ; nameList[ nameCount ]; ++nameCount );
  218. // randomize the initial index
  219. nameIndex = RandomInt( 0, nameCount-1 );
  220. }
  221. const char *name = nameList[ nameIndex++ ];
  222. if ( nameIndex >= nameCount )
  223. nameIndex = 0;
  224. return name;
  225. }
  226. //-----------------------------------------------------------------------------------------------------
  227. void CreateBotName( int iTeam, int iClassIndex, CTFBot::DifficultyType skill, char* pBuffer, int iBufferSize )
  228. {
  229. char szBotNameBuffer[256];
  230. char szEnemyOrFriendlyString[256];
  231. const char *pBotName = "";
  232. const char *pFriendlyOrEnemyTitle = "";
  233. // @note (Tom Bui): it is okay to get localized name in training, since we should be on a listen server
  234. if ( TFGameRules()->IsInTraining() )
  235. {
  236. // get the friendly/enemy title
  237. const char *pBotTitle = NULL;
  238. if ( iTeam != TEAM_UNASSIGNED )
  239. {
  240. int iHumanTeam = TFGameRules()->GetAssignedHumanTeam();
  241. if ( iHumanTeam != TEAM_ANY )
  242. {
  243. if ( iHumanTeam == iTeam )
  244. {
  245. pBotTitle = "#TF_Bot_Title_Friendly";
  246. }
  247. else
  248. {
  249. pBotTitle = "#TF_Bot_Title_Enemy";
  250. }
  251. }
  252. }
  253. wchar_t *pLocalizedTitle = pBotTitle ? g_pVGuiLocalize->Find( pBotTitle ) : NULL;
  254. if ( pLocalizedTitle )
  255. {
  256. g_pVGuiLocalize->ConvertUnicodeToANSI( pLocalizedTitle, szEnemyOrFriendlyString, sizeof( szEnemyOrFriendlyString ) );
  257. pFriendlyOrEnemyTitle = szEnemyOrFriendlyString;
  258. }
  259. // get the class name
  260. wchar_t *pLocalizedName = NULL;
  261. if ( iClassIndex >= TF_FIRST_NORMAL_CLASS && iClassIndex < TF_LAST_NORMAL_CLASS )
  262. {
  263. pLocalizedName = g_pVGuiLocalize->Find( g_aPlayerClassNames[ iClassIndex ] );
  264. }
  265. else
  266. {
  267. pLocalizedName = g_pVGuiLocalize->Find( "#TF_Bot_Generic_ClassName" );
  268. }
  269. g_pVGuiLocalize->ConvertUnicodeToANSI( pLocalizedName, szBotNameBuffer, sizeof( szBotNameBuffer ) );
  270. pBotName = szBotNameBuffer;
  271. }
  272. else
  273. {
  274. pBotName = GetRandomBotName();
  275. }
  276. const char *pDifficultyString = tf_bot_prefix_name_with_difficulty.GetBool() ? DifficultyLevelToString( skill ) : "";
  277. // we use this as our formatting, because we don't know the language of the downstream clients
  278. CFmtStr name( "%s%s%s",
  279. pDifficultyString, pFriendlyOrEnemyTitle, pBotName );
  280. Q_strncpy( pBuffer, name.Access(), iBufferSize );
  281. }
  282. //-----------------------------------------------------------------------------------------------------
  283. CON_COMMAND_F( tf_bot_add, "Add a bot.", FCVAR_GAMEDLL )
  284. {
  285. // Listenserver host or rcon access only!
  286. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  287. return;
  288. bool bQuotaManaged = true;
  289. int botCount = 1;
  290. const char *classname = NULL;
  291. const char *teamname = "auto";
  292. const char *pszBotNameViaArg = NULL;
  293. CTFBot::DifficultyType skill = clamp( (CTFBot::DifficultyType)tf_bot_difficulty.GetInt(), CTFBot::EASY, CTFBot::EXPERT );
  294. int i;
  295. for( i=1; i<args.ArgC(); ++i )
  296. {
  297. CTFBot::DifficultyType trySkill = StringToDifficultyLevel( args.Arg(i) );
  298. int nArgAsInteger = atoi( args.Arg(i) );
  299. // each argument could be a classname, a team, a difficulty level, a count, or a name
  300. if ( IsPlayerClassname( args.Arg(i) ) )
  301. {
  302. classname = args.Arg(i);
  303. }
  304. else if ( IsTeamName( args.Arg(i) ) )
  305. {
  306. teamname = args.Arg(i);
  307. }
  308. else if ( !stricmp( args.Arg( i ), "noquota" ) )
  309. {
  310. bQuotaManaged = false;
  311. }
  312. else if ( trySkill != CTFBot::UNDEFINED )
  313. {
  314. skill = trySkill;
  315. }
  316. else if ( nArgAsInteger > 0 )
  317. {
  318. botCount = nArgAsInteger;
  319. pszBotNameViaArg = NULL; // can't have a custom name if spawning multiple bots
  320. }
  321. else if ( botCount == 1 )
  322. {
  323. pszBotNameViaArg = args.Arg( i );
  324. }
  325. else
  326. {
  327. Warning( "Invalid argument '%s'\n", args.Arg(i) );
  328. }
  329. }
  330. // cvar can override classname
  331. classname = FStrEq( tf_bot_force_class.GetString(), "" ) ? classname : tf_bot_force_class.GetString();
  332. int iClassIndex = classname ? GetClassIndexFromString( classname ) : TF_CLASS_UNDEFINED;
  333. int iTeam = TEAM_UNASSIGNED;
  334. if ( FStrEq( teamname, "red" ) )
  335. {
  336. iTeam = TF_TEAM_RED;
  337. }
  338. else if ( FStrEq( teamname, "blue" ) )
  339. {
  340. iTeam = TF_TEAM_BLUE;
  341. }
  342. if ( TFGameRules()->IsInTraining() )
  343. {
  344. skill = CTFBot::EASY;
  345. }
  346. char name[256];
  347. int iNumAdded = 0;
  348. for( i=0; i<botCount; ++i )
  349. {
  350. CTFBot *pBot = NULL;
  351. const char *pszBotName = NULL;
  352. if ( !pszBotNameViaArg )
  353. {
  354. CreateBotName( iTeam, iClassIndex, skill, name, sizeof(name) );
  355. pszBotName = name;
  356. }
  357. else
  358. {
  359. pszBotName = pszBotNameViaArg;
  360. }
  361. pBot = NextBotCreatePlayerBot< CTFBot >( pszBotName );
  362. if ( pBot )
  363. {
  364. if ( bQuotaManaged )
  365. {
  366. pBot->SetAttribute( CTFBot::QUOTA_MANANGED );
  367. }
  368. pBot->HandleCommand_JoinTeam( teamname );
  369. pBot->SetDifficulty( skill );
  370. // if no class is set, auto-select one
  371. const char *thisClassname = classname ? classname : pBot->GetNextSpawnClassname();
  372. pBot->HandleCommand_JoinClass( thisClassname );
  373. // set up a proper name now that we are in training
  374. if ( TFGameRules()->IsInTraining() )
  375. {
  376. CreateBotName( pBot->GetTeamNumber(), pBot->GetPlayerClass()->GetClassIndex(), skill, name, sizeof(name) );
  377. engine->SetFakeClientConVarValue( pBot->edict(), "name", name );
  378. }
  379. ++iNumAdded;
  380. }
  381. }
  382. if ( bQuotaManaged )
  383. {
  384. TheTFBots().OnForceAddedBots( iNumAdded );
  385. }
  386. }
  387. //-----------------------------------------------------------------------------------------------------
  388. CON_COMMAND_F( tf_bot_kick, "Remove a TFBot by name, or all bots (\"all\").", FCVAR_GAMEDLL )
  389. {
  390. // Listenserver host or rcon access only!
  391. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  392. return;
  393. if ( args.ArgC() < 2 )
  394. {
  395. DevMsg( "%s <bot name>, \"red\", \"blue\", or \"all\"> <optional: \"moveToSpectatorTeam\"> \n", args.Arg(0) );
  396. return;
  397. }
  398. bool bMoveToSpectatorTeam = false;
  399. int iTeam = TEAM_UNASSIGNED;
  400. int i;
  401. const char *pPlayerName = "";
  402. for( i=1; i<args.ArgC(); ++i )
  403. {
  404. // each argument could be a classname, a team, or a count
  405. if ( FStrEq( args.Arg(i), "red" ) )
  406. {
  407. iTeam = TF_TEAM_RED;
  408. }
  409. else if ( FStrEq( args.Arg(i), "blue" ) )
  410. {
  411. iTeam = TF_TEAM_BLUE;
  412. }
  413. else if ( FStrEq( args.Arg(i), "all" ) )
  414. {
  415. iTeam = TEAM_ANY;
  416. }
  417. else if ( FStrEq( args.Arg(i), "moveToSpectatorTeam" ) )
  418. {
  419. bMoveToSpectatorTeam = true;
  420. }
  421. else
  422. {
  423. pPlayerName = args.Arg(i);
  424. }
  425. }
  426. int iNumKicked = 0;
  427. for( int i=1; i<=gpGlobals->maxClients; ++i )
  428. {
  429. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  430. if ( !player )
  431. continue;
  432. if ( FNullEnt( player->edict() ) )
  433. continue;
  434. if ( player->MyNextBotPointer() )
  435. {
  436. if ( iTeam == TEAM_ANY ||
  437. FStrEq( pPlayerName, player->GetPlayerName() ) ||
  438. ( player->GetTeamNumber() == iTeam ) ||
  439. ( player->GetTeamNumber() == iTeam ) )
  440. {
  441. if ( bMoveToSpectatorTeam )
  442. {
  443. player->ChangeTeam( TEAM_SPECTATOR, false, true );
  444. }
  445. else
  446. {
  447. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
  448. }
  449. CTFBot* pBot = dynamic_cast< CTFBot* >( player );
  450. if ( pBot && pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) )
  451. {
  452. ++iNumKicked;
  453. }
  454. }
  455. }
  456. }
  457. TheTFBots().OnForceKickedBots( iNumKicked );
  458. }
  459. //-----------------------------------------------------------------------------------------------------
  460. CON_COMMAND_F( tf_bot_kill, "Kill a TFBot by name, or all bots (\"all\").", FCVAR_GAMEDLL )
  461. {
  462. // Listenserver host or rcon access only!
  463. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  464. return;
  465. if ( args.ArgC() < 2 )
  466. {
  467. DevMsg( "%s <bot name>, \"red\", \"blue\", or \"all\"> <optional: \"moveToSpectatorTeam\"> \n", args.Arg(0) );
  468. return;
  469. }
  470. int iTeam = TEAM_UNASSIGNED;
  471. int i;
  472. const char *pPlayerName = "";
  473. for( i=1; i<args.ArgC(); ++i )
  474. {
  475. // each argument could be a classname, a team, or a count
  476. if ( FStrEq( args.Arg(i), "red" ) )
  477. {
  478. iTeam = TF_TEAM_RED;
  479. }
  480. else if ( FStrEq( args.Arg(i), "blue" ) )
  481. {
  482. iTeam = TF_TEAM_BLUE;
  483. }
  484. else if ( FStrEq( args.Arg(i), "all" ) )
  485. {
  486. iTeam = TEAM_ANY;
  487. }
  488. else if ( FStrEq( args.Arg(i), "moveToSpectatorTeam" ) )
  489. {
  490. // bMoveToSpectatorTeam = true;
  491. }
  492. else
  493. {
  494. pPlayerName = args.Arg(i);
  495. }
  496. }
  497. for( int i=1; i<=gpGlobals->maxClients; ++i )
  498. {
  499. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  500. if ( !player )
  501. continue;
  502. if ( FNullEnt( player->edict() ) )
  503. continue;
  504. if ( player->MyNextBotPointer() )
  505. {
  506. if ( iTeam == TEAM_ANY ||
  507. FStrEq( pPlayerName, player->GetPlayerName() ) ||
  508. ( player->GetTeamNumber() == iTeam ) ||
  509. ( player->GetTeamNumber() == iTeam ) )
  510. {
  511. CTakeDamageInfo info( player, player, 9999999.9f, DMG_ENERGYBEAM, TF_DMG_CUSTOM_NONE );
  512. player->TakeDamage( info );
  513. }
  514. }
  515. }
  516. }
  517. //-----------------------------------------------------------------------------------------------------
  518. void CMD_BotWarpTeamToMe( void )
  519. {
  520. CBasePlayer *player = UTIL_GetListenServerHost();
  521. if ( !player )
  522. return;
  523. CTeam *myTeam = player->GetTeam();
  524. for( int i=0; i<myTeam->GetNumPlayers(); ++i )
  525. {
  526. if ( !myTeam->GetPlayer(i)->IsAlive() )
  527. continue;
  528. myTeam->GetPlayer(i)->SetAbsOrigin( player->GetAbsOrigin() );
  529. }
  530. }
  531. static ConCommand tf_bot_warp_team_to_me( "tf_bot_warp_team_to_me", CMD_BotWarpTeamToMe, "", FCVAR_GAMEDLL | FCVAR_CHEAT );
  532. //-----------------------------------------------------------------------------------------------------
  533. IMPLEMENT_INTENTION_INTERFACE( CTFBot, CTFBotMainAction );
  534. //-----------------------------------------------------------------------------------------------------
  535. LINK_ENTITY_TO_CLASS( tf_bot, CTFBot );
  536. //-----------------------------------------------------------------------------------------------------
  537. /**
  538. * Allocate a bot and bind it to the edict
  539. */
  540. CBasePlayer *CTFBot::AllocatePlayerEntity( edict_t *edict, const char *playerName )
  541. {
  542. CBasePlayer::s_PlayerEdict = edict;
  543. return static_cast< CBasePlayer * >( CreateEntityByName( "tf_bot" ) );
  544. }
  545. //-----------------------------------------------------------------------------------------------------
  546. void CTFBot::PressFireButton( float duration )
  547. {
  548. // can't fire if stunned
  549. // @todo Tom Bui: Eventually, we'll probably want to check the actual weapon for supress fire
  550. if ( m_Shared.IsControlStunned() || m_Shared.IsLoserStateStunned() || HasAttribute( CTFBot::SUPPRESS_FIRE ) )
  551. {
  552. ReleaseFireButton();
  553. return;
  554. }
  555. BaseClass::PressFireButton( duration );
  556. }
  557. //-----------------------------------------------------------------------------------------------------
  558. void CTFBot::PressAltFireButton( float duration )
  559. {
  560. // can't fire if stunned
  561. // @todo Tom Bui: Eventually, we'll probably want to check the actual weapon for supress fire
  562. if ( m_Shared.IsControlStunned() || m_Shared.IsLoserStateStunned() || HasAttribute( CTFBot::SUPPRESS_FIRE ) )
  563. {
  564. ReleaseAltFireButton();
  565. return;
  566. }
  567. BaseClass::PressAltFireButton( duration );
  568. }
  569. //-----------------------------------------------------------------------------------------------------
  570. void CTFBot::PressSpecialFireButton( float duration )
  571. {
  572. // can't fire if stunned
  573. // @todo Tom Bui: Eventually, we'll probably want to check the actual weapon for supress fire
  574. if ( m_Shared.IsControlStunned() || m_Shared.IsLoserStateStunned() || HasAttribute( CTFBot::SUPPRESS_FIRE ) )
  575. {
  576. ReleaseAltFireButton();
  577. return;
  578. }
  579. BaseClass::PressSpecialFireButton( duration );
  580. }
  581. //-----------------------------------------------------------------------------------------------------
  582. class CCountClassMembers
  583. {
  584. public:
  585. CCountClassMembers( const CTFBot *me, int teamID )
  586. {
  587. m_me = me;
  588. m_myTeam = teamID;
  589. m_teamSize = 0;
  590. for( int i=0; i<TF_LAST_NORMAL_CLASS; ++i )
  591. m_count[i] = 0;
  592. }
  593. bool operator() ( CBasePlayer *basePlayer )
  594. {
  595. CTFPlayer *player = (CTFPlayer *)basePlayer;
  596. if ( player->GetTeamNumber() != m_myTeam )
  597. return true;
  598. ++m_teamSize;
  599. if ( m_me->IsSelf( player ) )
  600. return true;
  601. ++m_count[ player->GetDesiredPlayerClassIndex() ];
  602. return true;
  603. }
  604. const CTFBot *m_me;
  605. int m_myTeam;
  606. int m_count[ TF_LAST_NORMAL_CLASS+1 ];
  607. int m_teamSize;
  608. };
  609. //-----------------------------------------------------------------------------------------------------
  610. /**
  611. * NOTE: Assumes bot's difficulty has been set, and the bot is on a team.
  612. */
  613. const char *CTFBot::GetNextSpawnClassname( void ) const
  614. {
  615. struct ClassSelectionInfo
  616. {
  617. int m_class;
  618. int m_minTeamSizeToSelect; // team must have this many members to choose this class
  619. int m_countPerTeamSize; // must have 1 Medic for each 4 team members, for example
  620. int m_minLimit; // minimum that must be present (once other constraints are met)
  621. int m_maxLimit[ NUM_DIFFICULTY_LEVELS ]; // maximum that can be present (-1 for infinite)
  622. };
  623. const int NoLimit = -1;
  624. static ClassSelectionInfo defenseRoster[] =
  625. {
  626. { TF_CLASS_ENGINEER, 0, 4, 1, { 1, 2, 3, 3 } },
  627. { TF_CLASS_SOLDIER, 0, 0, 0, { NoLimit, NoLimit, NoLimit, NoLimit } },
  628. { TF_CLASS_DEMOMAN, 0, 0, 0, { 2, 3, 3, 3 } },
  629. { TF_CLASS_PYRO, 3, 0, 0, { NoLimit, NoLimit, NoLimit, NoLimit } },
  630. { TF_CLASS_HEAVYWEAPONS, 3, 0, 0, { 1, 1, 2, 2 } },
  631. { TF_CLASS_MEDIC, 4, 4, 1, { 1, 1, 2, 2 } },
  632. { TF_CLASS_SNIPER, 5, 0, 0, { 0, 1, 1, 1 } },
  633. { TF_CLASS_SPY, 5, 0, 0, { 0, 1, 2, 2 } },
  634. { TF_CLASS_UNDEFINED, 0, -1 },
  635. };
  636. static ClassSelectionInfo offenseRoster[] =
  637. {
  638. { TF_CLASS_SCOUT, 0, 0, 1, { 3, 3, 3, 3 } },
  639. { TF_CLASS_SOLDIER, 0, 0, 0, { NoLimit, NoLimit, NoLimit, NoLimit } },
  640. { TF_CLASS_DEMOMAN, 0, 0, 0, { 2, 3, 3, 3 } }, // must limit demomen, or the whole team will go demo to take out tough sentryguns
  641. { TF_CLASS_PYRO, 3, 0, 0, { NoLimit, NoLimit, NoLimit, NoLimit } },
  642. { TF_CLASS_HEAVYWEAPONS, 3, 0, 0, { 1, 1, 2, 2 } },
  643. { TF_CLASS_MEDIC, 4, 4, 1, { 1, 1, 2, 2 } },
  644. { TF_CLASS_SNIPER, 5, 0, 0, { 0, 1, 1, 1 } },
  645. { TF_CLASS_SPY, 5, 0, 0, { 0, 1, 2, 2 } },
  646. { TF_CLASS_ENGINEER, 5, 0, 0, { 1, 1, 1, 1 } },
  647. { TF_CLASS_UNDEFINED, 0, -1 },
  648. };
  649. static ClassSelectionInfo compRoster[] =
  650. {
  651. { TF_CLASS_SCOUT, 0, 0, 0, { 0, 0, 2, 2 } },
  652. { TF_CLASS_SOLDIER, 0, 0, 0, { 0, 0, NoLimit, NoLimit } },
  653. { TF_CLASS_DEMOMAN, 0, 0, 0, { 0, 0, 2, 2 } }, // must limit demomen, or the whole team will go demo to take out tough sentryguns
  654. { TF_CLASS_PYRO, 0, -1 },
  655. { TF_CLASS_HEAVYWEAPONS, 3, 0, 0, { 0, 0, 2, 2 } },
  656. { TF_CLASS_MEDIC, 1, 0, 1, { 0, 0, 1, 1 } },
  657. { TF_CLASS_SNIPER, 0, -1 },
  658. { TF_CLASS_SPY, 0, -1 },
  659. { TF_CLASS_ENGINEER, 0, -1 },
  660. { TF_CLASS_UNDEFINED, 0, -1 },
  661. };
  662. // if we are an engineer with an active sentry or teleporters, don't switch
  663. if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
  664. {
  665. if ( const_cast< CTFBot * >( this )->GetObjectOfType( OBJ_SENTRYGUN ) ||
  666. const_cast< CTFBot * >( this )->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ) )
  667. {
  668. return "engineer";
  669. }
  670. }
  671. // count classes in use by my team, not including me
  672. CCountClassMembers currentRoster( this, GetTeamNumber() );
  673. ForEachPlayer( currentRoster );
  674. // assume offense
  675. ClassSelectionInfo *desiredRoster = offenseRoster;
  676. if ( TFGameRules()->IsMatchTypeCompetitive() )
  677. {
  678. desiredRoster = compRoster;
  679. }
  680. else if ( TFGameRules()->IsInKothMode() )
  681. {
  682. CTeamControlPoint *point = GetMyControlPoint();
  683. if ( point )
  684. {
  685. if ( GetTeamNumber() == ObjectiveResource()->GetOwningTeam( point->GetPointIndex() ) )
  686. {
  687. // defend our point
  688. desiredRoster = defenseRoster;
  689. }
  690. }
  691. }
  692. else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP )
  693. {
  694. CUtlVector< CTeamControlPoint * > captureVector;
  695. TFGameRules()->CollectCapturePoints( const_cast< CTFBot * >( this ), &captureVector );
  696. CUtlVector< CTeamControlPoint * > defendVector;
  697. TFGameRules()->CollectDefendPoints( const_cast< CTFBot * >( this ), &defendVector );
  698. // if we have any points we can capture, try to do so
  699. if ( captureVector.Count() > 0 || defendVector.Count() == 0 )
  700. {
  701. desiredRoster = offenseRoster;
  702. }
  703. else
  704. {
  705. desiredRoster = defenseRoster;
  706. }
  707. }
  708. else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
  709. {
  710. if ( GetTeamNumber() == TF_TEAM_RED )
  711. {
  712. desiredRoster = defenseRoster;
  713. }
  714. }
  715. // build vector of classes we can pick from
  716. CUtlVector< int > desiredClassVector;
  717. CUtlVector< int > allowedClassForBotRosterVector;
  718. for( int i=0; desiredRoster[ i ].m_class != TF_CLASS_UNDEFINED; ++i )
  719. {
  720. ClassSelectionInfo *desiredClassInfo = &desiredRoster[ i ];
  721. if ( TFGameRules()->CanBotChooseClass( const_cast< CTFBot * >( this ), desiredClassInfo->m_class ) == false )
  722. {
  723. // not allowed to use this class
  724. continue;
  725. }
  726. // just in case we hit the class limits, we want to make sure we select a class that is allowed
  727. allowedClassForBotRosterVector.AddToTail( desiredClassInfo->m_class );
  728. if ( currentRoster.m_teamSize < desiredClassInfo->m_minTeamSizeToSelect )
  729. {
  730. // team is too small to choose this class
  731. continue;
  732. }
  733. // check limits
  734. if ( currentRoster.m_count[ desiredClassInfo->m_class ] < desiredClassInfo->m_minLimit )
  735. {
  736. // below required limit - choose only this class
  737. desiredClassVector.RemoveAll();
  738. desiredClassVector.AddToTail( desiredClassInfo->m_class );
  739. break;
  740. }
  741. int maxLimit = desiredClassInfo->m_maxLimit[ (int)clamp( GetDifficulty(), CTFBot::EASY, CTFBot::EXPERT ) ];
  742. if ( maxLimit > NoLimit && currentRoster.m_count[ desiredClassInfo->m_class ] >= maxLimit )
  743. {
  744. // at or above limit for this class
  745. continue;
  746. }
  747. if ( desiredClassInfo->m_countPerTeamSize > 0 )
  748. {
  749. // how many of this class should there be at the given "per" count
  750. int maxCountPer = currentRoster.m_teamSize / desiredClassInfo->m_countPerTeamSize;
  751. if ( currentRoster.m_count[ desiredClassInfo->m_class ] - desiredClassInfo->m_minTeamSizeToSelect < maxCountPer )
  752. {
  753. // below required limit - choose only this class
  754. desiredClassVector.RemoveAll();
  755. desiredClassVector.AddToTail( desiredClassInfo->m_class );
  756. break;
  757. }
  758. }
  759. // valid class to choose
  760. desiredClassVector.AddToTail( desiredClassInfo->m_class );
  761. }
  762. if ( desiredClassVector.Count() == 0 )
  763. {
  764. if ( allowedClassForBotRosterVector.Count() == 0 )
  765. {
  766. // nothing available
  767. Warning( "TFBot unable to choose a class, defaulting to 'auto'\n" );
  768. return "auto";
  769. }
  770. else
  771. {
  772. desiredClassVector = allowedClassForBotRosterVector;
  773. }
  774. }
  775. int which = RandomInt( 0, desiredClassVector.Count()-1 );
  776. // if we need to destroy a sentry, pick a class that can do so
  777. if ( GetEnemySentry() )
  778. {
  779. // best sentry demolitions
  780. int demoman = desiredClassVector.Find( TF_CLASS_DEMOMAN );
  781. if ( demoman >= 0 )
  782. {
  783. which = demoman;
  784. }
  785. else
  786. {
  787. // next best sentry demolitions
  788. int spy = desiredClassVector.Find( TF_CLASS_SPY );
  789. if ( spy >= 0 )
  790. {
  791. which = spy;
  792. }
  793. else
  794. {
  795. // good sentry demolitions
  796. int soldier = desiredClassVector.Find( TF_CLASS_SOLDIER );
  797. if ( soldier >= 0 )
  798. {
  799. which = soldier;
  800. }
  801. }
  802. }
  803. }
  804. TFPlayerClassData_t *classData = GetPlayerClassData( desiredClassVector[ which ] );
  805. if ( classData )
  806. {
  807. return classData->m_szClassName;
  808. }
  809. Warning( "TFBot unable to get data for desired class, defaulting to 'auto'\n" );
  810. return "auto";
  811. }
  812. //-----------------------------------------------------------------------------------------------------
  813. CTFBot::CTFBot()
  814. {
  815. m_body = new CTFBotBody( this );
  816. m_locomotor = new CTFBotLocomotion( this );
  817. m_vision = new CTFBotVision( this );
  818. ALLOCATE_INTENTION_INTERFACE( CTFBot );
  819. m_spawnArea = NULL;
  820. m_weaponRestrictionFlags = 0;
  821. m_attributeFlags = 0;
  822. m_homeArea = NULL;
  823. m_squad = NULL;
  824. m_didReselectClass = false;
  825. m_enemySentry = NULL;
  826. m_spotWhereEnemySentryLastInjuredMe = vec3_origin;
  827. m_isLookingAroundForEnemies = true;
  828. m_behaviorFlags = 0;
  829. m_attentionFocusEntity = NULL;
  830. m_noisyTimer.Invalidate();
  831. if ( TFGameRules()->IsInTraining() )
  832. {
  833. m_difficulty = CTFBot::EASY;
  834. }
  835. else
  836. {
  837. m_difficulty = clamp( (CTFBot::DifficultyType)tf_bot_difficulty.GetInt(), CTFBot::EASY, CTFBot::EXPERT );
  838. }
  839. m_actionPoint = NULL;
  840. m_proxy = NULL;
  841. m_spawner = NULL;
  842. m_myControlPoint = NULL;
  843. SetMission( NO_MISSION, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM );
  844. SetMissionTarget( NULL );
  845. m_missionString.Clear();
  846. m_fModelScaleOverride = -1.0f;
  847. m_maxVisionRangeOverride = -1.0f;
  848. m_squadFormationError = 0.0f;
  849. m_hFollowingFlagTarget = NULL;
  850. SetShouldQuickBuild( false );
  851. SetAutoJump( 0.f, 0.f );
  852. ClearSniperSpots();
  853. ListenForGameEvent( "teamplay_point_startcapture" );
  854. ListenForGameEvent( "teamplay_point_captured" );
  855. ListenForGameEvent( "teamplay_round_win" );
  856. ListenForGameEvent( "teamplay_flag_event" );
  857. }
  858. //-----------------------------------------------------------------------------------------------------
  859. CTFBot::~CTFBot()
  860. {
  861. // delete Intention first, since destruction of Actions may access other components
  862. DEALLOCATE_INTENTION_INTERFACE;
  863. if ( m_body )
  864. delete m_body;
  865. if ( m_locomotor )
  866. delete m_locomotor;
  867. if ( m_vision )
  868. delete m_vision;
  869. m_suspectedSpyVector.PurgeAndDeleteElements();
  870. }
  871. //-----------------------------------------------------------------------------------------------------
  872. void CTFBot::Spawn()
  873. {
  874. BaseClass::Spawn();
  875. m_spawnArea = NULL;
  876. m_justLostPointTimer.Invalidate();
  877. m_squad = NULL;
  878. m_didReselectClass = false;
  879. m_isLookingAroundForEnemies = true;
  880. m_attentionFocusEntity = NULL;
  881. m_suspectedSpyVector.PurgeAndDeleteElements();
  882. m_knownSpyVector.RemoveAll();
  883. m_delayedNoticeVector.RemoveAll();
  884. m_myControlPoint = NULL;
  885. ClearSniperSpots();
  886. ClearTags();
  887. m_hFollowingFlagTarget = NULL;
  888. m_requiredWeaponStack.Clear();
  889. SetShouldQuickBuild( false );
  890. SetSquadFormationError( 0.0f );
  891. SetBrokenFormation( false );
  892. GetVisionInterface()->ForgetAllKnownEntities();
  893. }
  894. //-----------------------------------------------------------------------------------------------------
  895. void CTFBot::SetMission( MissionType mission, bool resetBehaviorSystem )
  896. {
  897. SetPrevMission( m_mission );
  898. m_mission = mission;
  899. if ( resetBehaviorSystem )
  900. {
  901. // reset the behavior system to start the given mission
  902. GetIntentionInterface()->Reset();
  903. }
  904. // Temp hack - some missions play an idle loop
  905. if ( m_mission > NO_MISSION )
  906. {
  907. StartIdleSound();
  908. }
  909. }
  910. //-----------------------------------------------------------------------------------------------------
  911. void CTFBot::PhysicsSimulate( void )
  912. {
  913. BaseClass::PhysicsSimulate();
  914. if ( m_spawnArea == NULL )
  915. {
  916. m_spawnArea = GetLastKnownArea();
  917. }
  918. if ( HasAttribute( CTFBot::ALWAYS_CRIT ) && !m_Shared.InCond( TF_COND_CRITBOOSTED_USER_BUFF ) )
  919. {
  920. m_Shared.AddCond( TF_COND_CRITBOOSTED_USER_BUFF );
  921. }
  922. // force my speed to be recalculated to keep squad together and restore speed afterwards
  923. TeamFortress_SetSpeed();
  924. if ( IsInASquad() )
  925. {
  926. if ( GetSquad()->GetMemberCount() <= 1 || GetSquad()->GetLeader() == NULL )
  927. {
  928. // squad has collapsed - disband it
  929. LeaveSquad();
  930. }
  931. }
  932. // If we're dead, choose a new class.
  933. // We need to do this outside of the behavior system, since changing class can
  934. // sometimes force an immediate respawn, which will destroy the bot's existing actions out from under it.
  935. if ( !IsAlive() && !m_didReselectClass && tf_bot_keep_class_after_death.GetBool() == false && TFGameRules()->CanBotChangeClass( this ) )
  936. {
  937. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  938. return;
  939. const char *classname = FStrEq( tf_bot_force_class.GetString(), "" ) ? GetNextSpawnClassname() : tf_bot_force_class.GetString();
  940. HandleCommand_JoinClass( classname );
  941. m_didReselectClass = true;
  942. }
  943. }
  944. //-----------------------------------------------------------------------------------------------------
  945. void CTFBot::Touch( CBaseEntity *pOther )
  946. {
  947. BaseClass::Touch( pOther );
  948. CTFPlayer *them = ToTFPlayer( pOther );
  949. if ( them && IsEnemy( them ) )
  950. {
  951. if ( them->m_Shared.IsStealthed() || them->m_Shared.InCond( TF_COND_DISGUISED ) )
  952. {
  953. // bumped a spy - they are discovered!
  954. if ( TFGameRules()->IsMannVsMachineMode() ) // we have to build up to knowing that they are a spy in MvM
  955. {
  956. SuspectSpy( them );
  957. }
  958. else
  959. {
  960. RealizeSpy( them );
  961. }
  962. }
  963. // always notice if we bump an enemy
  964. TheNextBots().OnWeaponFired( them, them->GetActiveTFWeapon() );
  965. }
  966. }
  967. //-----------------------------------------------------------------------------------------------------
  968. // Avoid penetrating teammates
  969. void CTFBot::AvoidPlayers( CUserCmd *pCmd )
  970. {
  971. // Turn off the avoid player code.
  972. if ( !tf_avoidteammates.GetBool() || !tf_avoidteammates_pushaway.GetBool() )
  973. return;
  974. Vector forward, right;
  975. EyeVectors( &forward, &right );
  976. CUtlVector< CTFPlayer * > playerVector;
  977. CollectPlayers( &playerVector, GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
  978. Vector avoidVector = vec3_origin;
  979. float tooClose = 50.0f;
  980. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  981. {
  982. // bots stay farther apart in MvM mode
  983. tooClose = 150.0f;
  984. }
  985. for( int i=0; i<playerVector.Count(); ++i )
  986. {
  987. CTFPlayer *them = playerVector[i];
  988. if ( IsSelf( them ) )
  989. {
  990. continue;
  991. }
  992. if ( HasTheFlag() )
  993. {
  994. // Don't push around the flag (bomb) carrier.
  995. // We need this for MvM mode so friendly bots don't
  996. // move the bomb jumper and cause him to restart.
  997. continue;
  998. }
  999. if ( IsPlayerClass( TF_CLASS_MEDIC ) )
  1000. {
  1001. if ( !them->IsPlayerClass( TF_CLASS_MEDIC ) )
  1002. {
  1003. // medics only avoid other medics, so they stay with their patient
  1004. continue;
  1005. }
  1006. }
  1007. else if ( IsInASquad() )
  1008. {
  1009. // if I'm a non-Medic in a Squad, I'm part of a formation
  1010. continue;
  1011. }
  1012. Vector between = GetAbsOrigin() - them->GetAbsOrigin();
  1013. if ( between.IsLengthLessThan( tooClose ) )
  1014. {
  1015. float range = between.NormalizeInPlace();
  1016. avoidVector += ( 1.0f - ( range / tooClose ) ) * between;
  1017. }
  1018. }
  1019. if ( avoidVector.IsZero() )
  1020. {
  1021. m_Shared.SetSeparation( false );
  1022. m_Shared.SetSeparationVelocity( vec3_origin );
  1023. return;
  1024. }
  1025. avoidVector.NormalizeInPlace();
  1026. m_Shared.SetSeparation( true );
  1027. const float maxSpeed = 50.0f;
  1028. m_Shared.SetSeparationVelocity( avoidVector * maxSpeed );
  1029. float ahead = maxSpeed * DotProduct( forward, avoidVector );
  1030. float side = maxSpeed * DotProduct( right, avoidVector );
  1031. pCmd->forwardmove += ahead;
  1032. pCmd->sidemove += side;
  1033. }
  1034. //-----------------------------------------------------------------------------------------------------
  1035. void CTFBot::UpdateOnRemove( void )
  1036. {
  1037. StopIdleSound();
  1038. BaseClass::UpdateOnRemove();
  1039. }
  1040. //-----------------------------------------------------------------------------------------------------
  1041. int CTFBot::ShouldTransmit( const CCheckTransmitInfo *pInfo )
  1042. {
  1043. if ( HasAttribute( USE_BOSS_HEALTH_BAR ) )
  1044. {
  1045. return FL_EDICT_ALWAYS;
  1046. }
  1047. return BaseClass::ShouldTransmit( pInfo );
  1048. }
  1049. //-----------------------------------------------------------------------------------------------------
  1050. void CTFBot::ChangeTeam( int iTeamNum, bool bAutoTeam, bool bSilent, bool bAutoBalance /*= false*/ )
  1051. {
  1052. BaseClass::ChangeTeam( iTeamNum, bAutoTeam, bSilent, bAutoBalance );
  1053. if ( TFGameRules()->IsMannVsMachineMode() )
  1054. {
  1055. SetPrevMission( CTFBot::NO_MISSION );
  1056. ClearAllAttributes();
  1057. // Clear Sound
  1058. StopIdleSound();
  1059. }
  1060. }
  1061. //-----------------------------------------------------------------------------------------------------
  1062. bool CTFBot::ShouldGib( const CTakeDamageInfo &info )
  1063. {
  1064. // only gib giant/miniboss
  1065. if ( TFGameRules()->IsMannVsMachineMode() && ( IsMiniBoss() || GetModelScale() > 1.f ) )
  1066. {
  1067. return true;
  1068. }
  1069. return BaseClass::ShouldGib( info );
  1070. }
  1071. //-----------------------------------------------------------------------------------------------------
  1072. bool CTFBot::IsAllowedToPickUpFlag( void ) const
  1073. {
  1074. if ( !BaseClass::IsAllowedToPickUpFlag() )
  1075. {
  1076. return false;
  1077. }
  1078. // only the leader of a squad can pick up the flag
  1079. if ( IsInASquad() && !GetSquad()->IsLeader( const_cast< CTFBot * >( this ) ) )
  1080. return false;
  1081. // mission bots can't pick up the flag
  1082. return !IsOnAnyMission();
  1083. }
  1084. //-----------------------------------------------------------------------------------------------------
  1085. void CTFBot::InitClass( void )
  1086. {
  1087. BaseClass::InitClass();
  1088. }
  1089. void CTFBot::ModifyMaxHealth( int nNewMaxHealth, bool bSetCurrentHealth /*= true*/, bool bAllowModelScaling /*= true*/ )
  1090. {
  1091. if ( GetMaxHealth() != nNewMaxHealth )
  1092. {
  1093. static CSchemaAttributeDefHandle pAttrDef_HiddenMaxHealthNonBuffed( "hidden maxhealth non buffed" );
  1094. if ( !pAttrDef_HiddenMaxHealthNonBuffed )
  1095. {
  1096. Warning( "TFBotSpawner: Invalid attribute 'hidden maxhealth non buffed'\n" );
  1097. }
  1098. else
  1099. {
  1100. CAttributeList *pAttrList = GetAttributeList();
  1101. if ( pAttrList )
  1102. {
  1103. pAttrList->SetRuntimeAttributeValue( pAttrDef_HiddenMaxHealthNonBuffed, nNewMaxHealth - GetMaxHealth() );
  1104. }
  1105. }
  1106. }
  1107. if ( bSetCurrentHealth )
  1108. {
  1109. SetHealth( nNewMaxHealth );
  1110. }
  1111. if ( bAllowModelScaling && IsMiniBoss() )
  1112. {
  1113. SetModelScale( m_fModelScaleOverride > 0.0f ? m_fModelScaleOverride : tf_mvm_miniboss_scale.GetFloat() );
  1114. }
  1115. }
  1116. //-----------------------------------------------------------------------------------------------------
  1117. /**
  1118. * Invoked when a game event occurs
  1119. */
  1120. void CTFBot::FireGameEvent( IGameEvent *event )
  1121. {
  1122. const char *eventName = event->GetName();
  1123. if ( FStrEq( eventName, "teamplay_point_captured" ) )
  1124. {
  1125. ClearMyControlPoint();
  1126. int whoCapped = event->GetInt( "team" );
  1127. int pointID = event->GetInt( "cp" );
  1128. if ( whoCapped == GetTeamNumber() )
  1129. {
  1130. OnTerritoryCaptured( pointID );
  1131. }
  1132. else
  1133. {
  1134. OnTerritoryLost( pointID );
  1135. m_justLostPointTimer.Start( RandomFloat( 10.0f, 20.0f ) );
  1136. }
  1137. }
  1138. else if ( FStrEq( eventName, "teamplay_point_startcapture" ) )
  1139. {
  1140. int pointID = event->GetInt( "cp" );
  1141. OnTerritoryContested( pointID );
  1142. }
  1143. else if ( FStrEq( eventName, "teamplay_flag_event" ) )
  1144. {
  1145. if ( event->GetInt( "eventtype" ) == TF_FLAGEVENT_PICKUP )
  1146. {
  1147. int iPlayer = event->GetInt( "player" );
  1148. if ( iPlayer == entindex() )
  1149. {
  1150. // I just picked up the flag
  1151. OnPickUp( NULL, NULL );
  1152. }
  1153. }
  1154. }
  1155. }
  1156. //-----------------------------------------------------------------------------------------------------
  1157. void CTFBot::Event_Killed( const CTakeDamageInfo &info )
  1158. {
  1159. BaseClass::Event_Killed( info );
  1160. if ( HasProxy() )
  1161. {
  1162. GetProxy()->OnKilled();
  1163. }
  1164. // announce Spies
  1165. if ( TFGameRules()->IsMannVsMachineMode() )
  1166. {
  1167. if ( IsPlayerClass( TF_CLASS_SPY ) )
  1168. {
  1169. CUtlVector< CTFPlayer * > playerVector;
  1170. CollectPlayers( &playerVector, TF_TEAM_PVE_INVADERS, COLLECT_ONLY_LIVING_PLAYERS );
  1171. int spyCount = 0;
  1172. for( int i=0; i<playerVector.Count(); ++i )
  1173. {
  1174. if ( playerVector[i]->IsPlayerClass( TF_CLASS_SPY ) )
  1175. {
  1176. ++spyCount;
  1177. }
  1178. }
  1179. IGameEvent *event = gameeventmanager->CreateEvent( "mvm_mission_update" );
  1180. if ( event )
  1181. {
  1182. event->SetInt( "class", TF_CLASS_SPY );
  1183. event->SetInt( "count", spyCount );
  1184. gameeventmanager->FireEvent( event );
  1185. }
  1186. }
  1187. else if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
  1188. {
  1189. // in MVM, when an engineer dies, we need to decouple his objects so they stay alive when his bot slot gets recycled
  1190. while ( GetObjectCount() > 0 )
  1191. {
  1192. // set to not have owner
  1193. CBaseObject *pObject = GetObject( 0 );
  1194. if ( pObject )
  1195. {
  1196. pObject->SetOwnerEntity( NULL );
  1197. pObject->SetBuilder( NULL );
  1198. }
  1199. RemoveObject( pObject );
  1200. }
  1201. // unown engineer nest if owned any
  1202. for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i )
  1203. {
  1204. CBaseTFBotHintEntity* pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[i] );
  1205. if ( pHint->GetOwnerEntity() == this )
  1206. {
  1207. pHint->SetOwnerEntity( NULL );
  1208. }
  1209. }
  1210. CUtlVector< CTFPlayer* > playerVector;
  1211. CollectPlayers( &playerVector, TF_TEAM_PVE_INVADERS, COLLECT_ONLY_LIVING_PLAYERS );
  1212. bool bShouldAnnounceLastEngineerBotDeath = HasAttribute( CTFBot::TELEPORT_TO_HINT );
  1213. if ( bShouldAnnounceLastEngineerBotDeath )
  1214. {
  1215. for ( int i=0; i<playerVector.Count(); ++i )
  1216. {
  1217. if ( playerVector[i] != this && playerVector[i]->IsPlayerClass( TF_CLASS_ENGINEER ) )
  1218. {
  1219. bShouldAnnounceLastEngineerBotDeath = false;
  1220. break;
  1221. }
  1222. }
  1223. }
  1224. if ( bShouldAnnounceLastEngineerBotDeath )
  1225. {
  1226. bool bEngineerTeleporterInTheWorld = false;
  1227. for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
  1228. {
  1229. CBaseObject* pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
  1230. if ( pObj->GetType() == OBJ_TELEPORTER && pObj->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
  1231. {
  1232. bEngineerTeleporterInTheWorld = true;
  1233. }
  1234. }
  1235. if ( bEngineerTeleporterInTheWorld )
  1236. {
  1237. TFGameRules()->BroadcastSound( 255, "Announcer.MVM_An_Engineer_Bot_Is_Dead_But_Not_Teleporter" );
  1238. }
  1239. else
  1240. {
  1241. TFGameRules()->BroadcastSound( 255, "Announcer.MVM_An_Engineer_Bot_Is_Dead" );
  1242. }
  1243. }
  1244. }
  1245. // remove this bot from following flag
  1246. for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
  1247. {
  1248. for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
  1249. {
  1250. CCaptureFlag *flag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
  1251. flag->RemoveFollower( this );
  1252. }
  1253. }
  1254. } // MvM
  1255. if ( HasSpawner() )
  1256. {
  1257. GetSpawner()->OnBotKilled( this );
  1258. }
  1259. if ( IsInASquad() )
  1260. {
  1261. LeaveSquad();
  1262. }
  1263. CTFNavArea *lastArea = (CTFNavArea *)GetLastKnownArea();
  1264. if ( lastArea )
  1265. {
  1266. // remove us from old visible set
  1267. NavAreaCollector wasVisible;
  1268. lastArea->ForAllPotentiallyVisibleAreas( wasVisible );
  1269. int i;
  1270. for( i=0; i<wasVisible.m_area.Count(); ++i )
  1271. {
  1272. CTFNavArea *area = (CTFNavArea *)wasVisible.m_area[i];
  1273. area->RemovePotentiallyVisibleActor( this );
  1274. }
  1275. }
  1276. if ( info.GetInflictor() && info.GetInflictor()->GetTeamNumber() != GetTeamNumber() )
  1277. {
  1278. CObjectSentrygun *sentrygun = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() );
  1279. if ( sentrygun )
  1280. {
  1281. // we were killed by an enemy sentry - remember it
  1282. RememberEnemySentry( sentrygun, GetAbsOrigin() );
  1283. }
  1284. }
  1285. StopIdleSound();
  1286. }
  1287. //-----------------------------------------------------------------------------------------------------
  1288. CTeamControlPoint *CTFBot::SelectPointToCapture( CUtlVector< CTeamControlPoint * > *captureVector ) const
  1289. {
  1290. if ( !captureVector || captureVector->Count() == 0 )
  1291. {
  1292. return NULL;
  1293. }
  1294. if ( captureVector->Count() == 1 )
  1295. {
  1296. // only one choice
  1297. return captureVector->Element(0);
  1298. }
  1299. // if we're capturing a point, stay on it
  1300. if ( const_cast< CTFBot * >( this )->IsCapturingPoint() )
  1301. {
  1302. CTriggerAreaCapture *trigger = const_cast< CTFBot * >( this )->GetControlPointStandingOn();
  1303. if ( trigger )
  1304. {
  1305. return trigger->GetControlPoint();
  1306. }
  1307. }
  1308. // if we're near a point that is being captured, go help (in the event multiple points are being simultaneously captured)
  1309. CTeamControlPoint *closestPoint = SelectClosestControlPointByTravelDistance( captureVector );
  1310. if ( closestPoint )
  1311. {
  1312. bool alwaysUseClosest = false;
  1313. #ifdef STAGING_ONLY
  1314. alwaysUseClosest = TFGameRules() && TFGameRules()->IsBountyMode();
  1315. #endif // STAGING_ONLY
  1316. if ( IsPointBeingCaptured( closestPoint ) || alwaysUseClosest )
  1317. {
  1318. return closestPoint;
  1319. }
  1320. }
  1321. // if any point is being captured by our team, go help
  1322. for( int i=0; i<captureVector->Count(); ++i )
  1323. {
  1324. CTeamControlPoint *point = captureVector->Element(i);
  1325. if ( IsPointBeingCaptured( point ) )
  1326. {
  1327. return point;
  1328. }
  1329. }
  1330. // no points are currently being captured - pick the point with the least combat
  1331. CTeamControlPoint *safestPoint = NULL;
  1332. float safestPointCombat = FLT_MAX;
  1333. bool areAllPointsCombatFree = true;
  1334. for( int i=0; i<captureVector->Count(); ++i )
  1335. {
  1336. CTeamControlPoint *point = captureVector->Element(i);
  1337. CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );
  1338. if ( !pointArea )
  1339. {
  1340. continue;
  1341. }
  1342. float combat = pointArea->GetCombatIntensity();
  1343. const float minCombat = 0.1f;
  1344. if ( combat > minCombat )
  1345. {
  1346. areAllPointsCombatFree = false;
  1347. }
  1348. if ( combat < safestPointCombat )
  1349. {
  1350. safestPoint = point;
  1351. safestPointCombat = combat;
  1352. }
  1353. }
  1354. // if no points are in combat, pick a random point
  1355. if ( areAllPointsCombatFree )
  1356. {
  1357. const float decisionPeriod = 60.0f;
  1358. int which = captureVector->Count() * TransientlyConsistentRandomValue( decisionPeriod );
  1359. which = clamp( which, 0, captureVector->Count()-1 );
  1360. return captureVector->Element( which );
  1361. }
  1362. // choose the point with the least combat
  1363. return safestPoint;
  1364. }
  1365. //---------------------------------------------------------------------------------------------
  1366. CTeamControlPoint *CTFBot::SelectPointToDefend( CUtlVector< CTeamControlPoint * > *defendVector ) const
  1367. {
  1368. if ( defendVector && defendVector->Count() > 0 )
  1369. {
  1370. if ( HasAttribute( CTFBot::PRIORITIZE_DEFENSE ) )
  1371. {
  1372. return SelectClosestControlPointByTravelDistance( defendVector );
  1373. }
  1374. return defendVector->Element( RandomInt( 0, defendVector->Count()-1 ) );
  1375. }
  1376. return NULL;
  1377. }
  1378. //-----------------------------------------------------------------------------------------------------
  1379. /**
  1380. * Return the point we have decided to capture or defend
  1381. */
  1382. CTeamControlPoint *CTFBot::GetMyControlPoint( void ) const
  1383. {
  1384. if ( m_myControlPoint != NULL && !m_evaluateControlPointTimer.IsElapsed() )
  1385. {
  1386. return m_myControlPoint;
  1387. }
  1388. m_evaluateControlPointTimer.Start( RandomFloat( 1.0f, 2.0f ) );
  1389. CUtlVector< CTeamControlPoint * > captureVector;
  1390. TFGameRules()->CollectCapturePoints( const_cast< CTFBot * >( this ), &captureVector );
  1391. CUtlVector< CTeamControlPoint * > defendVector;
  1392. TFGameRules()->CollectDefendPoints( const_cast< CTFBot * >( this ), &defendVector );
  1393. if ( IsPlayerClass( TF_CLASS_ENGINEER ) || IsPlayerClass( TF_CLASS_SNIPER ) || HasAttribute( CTFBot::PRIORITIZE_DEFENSE ) )
  1394. {
  1395. // engineers always try to defend first
  1396. if ( defendVector.Count() > 0 )
  1397. {
  1398. m_myControlPoint = SelectPointToDefend( &defendVector );
  1399. return m_myControlPoint;
  1400. }
  1401. }
  1402. // if we have a point we can capture - do it
  1403. m_myControlPoint = SelectPointToCapture( &captureVector );
  1404. if ( m_myControlPoint == NULL )
  1405. {
  1406. // otherwise, defend our point(s) from capture
  1407. m_myControlPoint = SelectPointToDefend( &defendVector );
  1408. }
  1409. return m_myControlPoint;
  1410. }
  1411. //-----------------------------------------------------------------------------------------------------
  1412. // Return flag we want to fetch
  1413. CCaptureFlag *CTFBot::GetFlagToFetch( void ) const
  1414. {
  1415. CUtlVector<CCaptureFlag *> flagsVector;
  1416. int nCarriedFlags = 0;
  1417. // MvM Engineer bot never pick up a flag
  1418. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  1419. {
  1420. if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS && IsPlayerClass( TF_CLASS_ENGINEER ) )
  1421. {
  1422. return NULL;
  1423. }
  1424. if( HasAttribute( CTFBot::IGNORE_FLAG ) )
  1425. {
  1426. return NULL;
  1427. }
  1428. if ( TFGameRules()->IsMannVsMachineMode() && HasFlagTaget() )
  1429. {
  1430. return GetFlagTarget();
  1431. }
  1432. }
  1433. // Collect flags
  1434. for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
  1435. {
  1436. CCaptureFlag *flag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
  1437. if ( flag->IsDisabled() )
  1438. continue;
  1439. // If I'm carrying a flag, look for mine and early-out
  1440. if ( HasTheFlag() )
  1441. {
  1442. if ( flag->GetOwnerEntity() == this )
  1443. {
  1444. return flag;
  1445. }
  1446. }
  1447. switch( flag->GetType() )
  1448. {
  1449. case TF_FLAGTYPE_CTF:
  1450. if ( flag->GetTeamNumber() == GetEnemyTeam( GetTeamNumber() ) )
  1451. {
  1452. // we want to steal the other team's flag
  1453. flagsVector.AddToTail( flag );
  1454. }
  1455. break;
  1456. case TF_FLAGTYPE_ATTACK_DEFEND:
  1457. case TF_FLAGTYPE_TERRITORY_CONTROL:
  1458. case TF_FLAGTYPE_INVADE:
  1459. if ( flag->GetTeamNumber() != GetEnemyTeam( GetTeamNumber() ) )
  1460. {
  1461. // we want to move our team's flag or a neutral flag
  1462. flagsVector.AddToTail( flag );
  1463. }
  1464. break;
  1465. }
  1466. if ( flag->IsStolen() )
  1467. {
  1468. nCarriedFlags++;
  1469. }
  1470. }
  1471. CCaptureFlag *pClosestFlag = NULL;
  1472. float flClosestFlagDist = FLT_MAX;
  1473. CCaptureFlag *pClosestUncarriedFlag = NULL;
  1474. float flClosestUncarriedFlagDist = FLT_MAX;
  1475. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  1476. {
  1477. int nMinFollower = INT_MAX;
  1478. FOR_EACH_VEC( flagsVector, i )
  1479. {
  1480. CCaptureFlag *pFlag = flagsVector[i];
  1481. if ( pFlag )
  1482. {
  1483. // find the one which needs the most love
  1484. if ( pFlag->GetNumFollowers() < nMinFollower )
  1485. {
  1486. nMinFollower = pFlag->GetNumFollowers();
  1487. pClosestFlag = NULL;
  1488. flClosestFlagDist = FLT_MAX;
  1489. pClosestUncarriedFlag = NULL;
  1490. flClosestUncarriedFlagDist = FLT_MAX;
  1491. }
  1492. if ( pFlag->GetNumFollowers() == nMinFollower )
  1493. {
  1494. // Find the closest
  1495. float flDist = ( pFlag->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  1496. if ( flDist < flClosestFlagDist )
  1497. {
  1498. pClosestFlag = pFlag;
  1499. flClosestFlagDist = flDist;
  1500. }
  1501. // Find the closest uncarried
  1502. if ( nCarriedFlags < flagsVector.Count() && !pFlag->IsStolen() )
  1503. {
  1504. if ( flDist < flClosestUncarriedFlagDist )
  1505. {
  1506. pClosestUncarriedFlag = flagsVector[i];
  1507. flClosestUncarriedFlagDist = flDist;
  1508. }
  1509. }
  1510. }
  1511. }
  1512. }
  1513. }
  1514. else
  1515. {
  1516. FOR_EACH_VEC( flagsVector, i )
  1517. {
  1518. if ( flagsVector[i] )
  1519. {
  1520. // Find the closest
  1521. float flDist = ( flagsVector[i]->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  1522. if ( flDist < flClosestFlagDist )
  1523. {
  1524. pClosestFlag = flagsVector[i];
  1525. flClosestFlagDist = flDist;
  1526. }
  1527. // Find the closest uncarried
  1528. if ( nCarriedFlags < flagsVector.Count() && !flagsVector[i]->IsStolen() )
  1529. {
  1530. if ( flDist < flClosestUncarriedFlagDist )
  1531. {
  1532. pClosestUncarriedFlag = flagsVector[i];
  1533. flClosestUncarriedFlagDist = flDist;
  1534. }
  1535. }
  1536. }
  1537. }
  1538. }
  1539. // If we have an uncarried flag, prioritize
  1540. if ( pClosestUncarriedFlag )
  1541. return pClosestUncarriedFlag;
  1542. return pClosestFlag;
  1543. }
  1544. //-----------------------------------------------------------------------------------------------------
  1545. // Return capture zone for our flag(s)
  1546. CCaptureZone *CTFBot::GetFlagCaptureZone( void ) const
  1547. {
  1548. for( int i=0; i<ICaptureZoneAutoList::AutoList().Count(); ++i )
  1549. {
  1550. CCaptureZone *zone = static_cast< CCaptureZone* >( ICaptureZoneAutoList::AutoList()[i] );
  1551. if ( zone->GetTeamNumber() == GetTeamNumber() )
  1552. {
  1553. return zone;
  1554. }
  1555. }
  1556. return NULL;
  1557. }
  1558. //-----------------------------------------------------------------------------------------------------
  1559. void CTFBot::ClearMyControlPoint( void )
  1560. {
  1561. m_myControlPoint = NULL;
  1562. m_evaluateControlPointTimer.Invalidate();
  1563. }
  1564. //-----------------------------------------------------------------------------------------------------
  1565. /**
  1566. * Return true if no enemy has contested any point yet
  1567. */
  1568. bool CTFBot::AreAllPointsUncontestedSoFar( void ) const
  1569. {
  1570. CTeamControlPointMaster *master = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  1571. if ( master )
  1572. {
  1573. for( int i=0; i<master->GetNumPoints(); ++i )
  1574. {
  1575. CTeamControlPoint *point = master->GetControlPoint( i );
  1576. if ( point && point->HasBeenContested() )
  1577. return false;
  1578. }
  1579. }
  1580. return true;
  1581. }
  1582. //-----------------------------------------------------------------------------------------------------
  1583. // Return true if the given point is being captured
  1584. bool CTFBot::IsPointBeingCaptured( CTeamControlPoint *point ) const
  1585. {
  1586. if ( point == NULL )
  1587. return false;
  1588. if ( point->LastContestedAt() > 0.0f && ( gpGlobals->curtime - point->LastContestedAt() ) < 5.0f )
  1589. {
  1590. // the point is, or was very recently, contested
  1591. return true;
  1592. }
  1593. return false;
  1594. }
  1595. //---------------------------------------------------------------------------------------------
  1596. // Return true if any point is being captured
  1597. bool CTFBot::IsAnyPointBeingCaptured( void ) const
  1598. {
  1599. CTeamControlPointMaster *master = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  1600. if ( master )
  1601. {
  1602. for( int i=0; i<master->GetNumPoints(); ++i )
  1603. {
  1604. CTeamControlPoint *point = master->GetControlPoint( i );
  1605. if ( IsPointBeingCaptured( point ) )
  1606. return true;
  1607. }
  1608. }
  1609. return false;
  1610. }
  1611. //---------------------------------------------------------------------------------------------
  1612. // Return true if we are within a short travel distance of the current point
  1613. bool CTFBot::IsNearPoint( CTeamControlPoint *point ) const
  1614. {
  1615. CTFNavArea *myArea = GetLastKnownArea();
  1616. if ( !myArea || !point )
  1617. {
  1618. return false;
  1619. }
  1620. CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );
  1621. if ( !pointArea )
  1622. {
  1623. return false;
  1624. }
  1625. float travelToPoint = fabs( myArea->GetIncursionDistance( GetTeamNumber() ) - pointArea->GetIncursionDistance( GetTeamNumber() ) );
  1626. return travelToPoint < tf_bot_near_point_travel_distance.GetFloat();
  1627. }
  1628. //---------------------------------------------------------------------------------------------
  1629. // Return time left to capture the point before we lose the game
  1630. float CTFBot::GetTimeLeftToCapture( void ) const
  1631. {
  1632. if ( TFGameRules()->IsInKothMode() )
  1633. {
  1634. if ( TFGameRules()->GetKothTeamTimer( GetEnemyTeam( GetTeamNumber() ) ) )
  1635. {
  1636. return TFGameRules()->GetKothTeamTimer( GetEnemyTeam( GetTeamNumber() ) )->GetTimeRemaining();
  1637. }
  1638. }
  1639. else if ( TFGameRules()->GetActiveRoundTimer() )
  1640. {
  1641. return TFGameRules()->GetActiveRoundTimer()->GetTimeRemaining();
  1642. }
  1643. return 0.0f;
  1644. }
  1645. //-----------------------------------------------------------------------------------------------------
  1646. // Do internal setup when control point changes
  1647. void CTFBot::SetupSniperSpotAccumulation( void )
  1648. {
  1649. VPROF_BUDGET( "CTFBot::SetupSniperSpotAccumulation", "NextBot" );
  1650. CBaseEntity *goalEntity = NULL;
  1651. if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
  1652. {
  1653. // try to find a payload cart to guard
  1654. CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToPush( GetTeamNumber() );
  1655. if ( !trainWatcher )
  1656. {
  1657. trainWatcher = TFGameRules()->GetPayloadToBlock( GetTeamNumber() );
  1658. }
  1659. if ( trainWatcher )
  1660. {
  1661. goalEntity = trainWatcher->GetTrainEntity();
  1662. }
  1663. }
  1664. else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP )
  1665. {
  1666. goalEntity = GetMyControlPoint();
  1667. }
  1668. if ( !goalEntity )
  1669. {
  1670. ClearSniperSpots();
  1671. return;
  1672. }
  1673. if ( goalEntity == m_snipingGoalEntity )
  1674. {
  1675. // if goal has moved too much (ie: payload cart), recompute our spots
  1676. Vector toGoal = m_snipingGoalEntity->WorldSpaceCenter() - m_lastSnipingGoalEntityPosition;
  1677. if ( toGoal.IsLengthLessThan( tf_bot_sniper_goal_entity_move_tolerance.GetFloat() ) )
  1678. {
  1679. // already set up
  1680. return;
  1681. }
  1682. }
  1683. ClearSniperSpots();
  1684. int myTeam = GetTeamNumber();
  1685. int enemyTeam = ( myTeam == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
  1686. bool isDefendingPoint = false;
  1687. CTFNavArea *goalEntityArea = NULL;
  1688. if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
  1689. {
  1690. // the cart is owned by the invaders
  1691. isDefendingPoint = ( goalEntity->GetTeamNumber() != myTeam );
  1692. goalEntityArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( goalEntity->WorldSpaceCenter(), GETNAVAREA_CHECK_GROUND, 500.0f );
  1693. }
  1694. else
  1695. {
  1696. isDefendingPoint = ( GetMyControlPoint()->GetOwner() == myTeam );
  1697. goalEntityArea = TheTFNavMesh()->GetControlPointCenterArea( GetMyControlPoint()->GetPointIndex() );
  1698. }
  1699. // we are sniping a different control point - setup for new point accumulation
  1700. m_sniperVantageAreaVector.RemoveAll();
  1701. m_sniperTheaterAreaVector.RemoveAll();
  1702. if ( !goalEntityArea )
  1703. {
  1704. return;
  1705. }
  1706. for( int i=0; i<TheNavAreas.Count(); ++i )
  1707. {
  1708. CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
  1709. if ( !area->IsReachableByTeam( myTeam ) || !area->IsReachableByTeam( enemyTeam ) )
  1710. {
  1711. continue;
  1712. }
  1713. if ( area->GetIncursionDistance( enemyTeam ) <= goalEntityArea->GetIncursionDistance( enemyTeam ) )
  1714. {
  1715. m_sniperTheaterAreaVector.AddToTail( area );
  1716. }
  1717. // if this is my point, I can stand on it, or go a bit beyond it
  1718. float myIncursionTolerance = tf_bot_sniper_spot_point_tolerance.GetFloat();
  1719. if ( !isDefendingPoint )
  1720. {
  1721. // not my point, keep back from it a bit
  1722. myIncursionTolerance *= -1.0f;
  1723. }
  1724. if ( area->GetIncursionDistance( myTeam ) <= goalEntityArea->GetIncursionDistance( myTeam ) + myIncursionTolerance )
  1725. {
  1726. m_sniperVantageAreaVector.AddToTail( area );
  1727. }
  1728. }
  1729. m_snipingGoalEntity = goalEntity;
  1730. m_lastSnipingGoalEntityPosition = goalEntity->WorldSpaceCenter();
  1731. }
  1732. //-----------------------------------------------------------------------------------------------------
  1733. // Randomly sample points within candidate areas to find good sniping positions
  1734. void CTFBot::AccumulateSniperSpots( void )
  1735. {
  1736. VPROF_BUDGET( "CTFBot::AccumulateSniperSpots", "NextBot" );
  1737. SetupSniperSpotAccumulation();
  1738. if ( m_sniperVantageAreaVector.Count() == 0 || m_sniperTheaterAreaVector.Count() == 0 )
  1739. {
  1740. // retry every so often to catch cases where the incursion data is invalid during setup time
  1741. // due to blocked/closed off areas, etc.
  1742. if ( m_retrySniperSpotSetupTimer.IsElapsed() )
  1743. {
  1744. // retry
  1745. ClearSniperSpots();
  1746. }
  1747. return;
  1748. }
  1749. SniperSpotInfo info;
  1750. for( int count=0; count<tf_bot_sniper_spot_search_count.GetInt(); ++count )
  1751. {
  1752. // pick a random vantage area to sample
  1753. int which = RandomInt( 0, m_sniperVantageAreaVector.Count()-1 );
  1754. info.m_vantageArea = m_sniperVantageAreaVector[ which ];
  1755. info.m_vantageSpot = info.m_vantageArea->GetRandomPoint();
  1756. // pick a random theater area to sample
  1757. which = RandomInt( 0, m_sniperTheaterAreaVector.Count()-1 );
  1758. info.m_theaterArea = m_sniperTheaterAreaVector[ which ];
  1759. info.m_theaterSpot = info.m_theaterArea->GetRandomPoint();
  1760. info.m_range = ( info.m_vantageSpot - info.m_theaterSpot ).Length();
  1761. if ( info.m_range < tf_bot_sniper_spot_min_range.GetFloat() )
  1762. {
  1763. // not long enough sightline
  1764. continue;
  1765. }
  1766. for( int i=0; i<m_sniperSpotVector.Count(); ++i )
  1767. {
  1768. if ( ( info.m_vantageSpot - m_sniperSpotVector[i].m_vantageSpot ).IsLengthLessThan( tf_bot_sniper_spot_epsilon.GetFloat() ) )
  1769. {
  1770. // too close to existing spot
  1771. continue;
  1772. }
  1773. }
  1774. Vector eyeOffset( 0, 0, 60.0f );
  1775. if ( IsLineOfFireClear( info.m_vantageSpot + eyeOffset, info.m_theaterSpot + eyeOffset ) )
  1776. {
  1777. // valid spot
  1778. // maximize the time it takes the enemy to get to us
  1779. info.m_advantage = info.m_vantageArea->GetIncursionDistance( GetEnemyTeam( GetTeamNumber() ) ) - info.m_theaterArea->GetIncursionDistance( GetEnemyTeam( GetTeamNumber() ) );
  1780. // if we have already maxxed out our sniper spots, replace the worst one if this is better
  1781. if ( m_sniperSpotVector.Count() >= tf_bot_sniper_spot_max_count.GetInt() )
  1782. {
  1783. int worst = -1;
  1784. for( int i=0; i<m_sniperSpotVector.Count(); ++i )
  1785. {
  1786. if ( worst < 0 || m_sniperSpotVector[i].m_advantage < m_sniperSpotVector[ worst ].m_advantage )
  1787. {
  1788. worst = i;
  1789. }
  1790. }
  1791. // if our new spot is better, replace it
  1792. if ( info.m_advantage > m_sniperSpotVector[ worst ].m_advantage )
  1793. {
  1794. m_sniperSpotVector[ worst ] = info;
  1795. }
  1796. }
  1797. else
  1798. {
  1799. m_sniperSpotVector.AddToTail( info );
  1800. }
  1801. }
  1802. }
  1803. if ( IsDebugging( NEXTBOT_BEHAVIOR ) )
  1804. {
  1805. for( int i=0; i<m_sniperSpotVector.Count(); ++i )
  1806. {
  1807. NDebugOverlay::Cross3D( m_sniperSpotVector[i].m_vantageSpot, 5.0f, 255, 0, 255, true, 0.1f );
  1808. NDebugOverlay::Line( m_sniperSpotVector[i].m_vantageSpot, m_sniperSpotVector[i].m_theaterSpot, 0, 200, 0, true, 0.1f );
  1809. }
  1810. }
  1811. }
  1812. //-----------------------------------------------------------------------------------------------------
  1813. void CTFBot::ClearSniperSpots( void )
  1814. {
  1815. m_sniperSpotVector.RemoveAll();
  1816. m_sniperVantageAreaVector.RemoveAll();
  1817. m_sniperTheaterAreaVector.RemoveAll();
  1818. m_snipingGoalEntity = NULL;
  1819. m_retrySniperSpotSetupTimer.Start( RandomFloat( 5.0f, 10.0f ) );
  1820. }
  1821. //---------------------------------------------------------------------------------------------
  1822. class CCollectReachableObjects : public ISearchSurroundingAreasFunctor
  1823. {
  1824. public:
  1825. CCollectReachableObjects( const CTFBot *me, float maxRange, const CUtlVector< CHandle< CBaseEntity > > &potentialVector, CUtlVector< CHandle< CBaseEntity > > *collectionVector ) : m_potentialVector( potentialVector )
  1826. {
  1827. m_me = me;
  1828. m_maxRange = maxRange;
  1829. m_collectionVector = collectionVector;
  1830. }
  1831. virtual bool operator() ( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar )
  1832. {
  1833. // do any of the potential objects overlap this area?
  1834. FOR_EACH_VEC( m_potentialVector, it )
  1835. {
  1836. CBaseEntity *obj = m_potentialVector[ it ];
  1837. if ( obj && area->Contains( obj->WorldSpaceCenter() ) )
  1838. {
  1839. // reachable - keep it
  1840. if ( !m_collectionVector->HasElement( obj ) )
  1841. {
  1842. m_collectionVector->AddToTail( obj );
  1843. }
  1844. }
  1845. }
  1846. return true;
  1847. }
  1848. virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
  1849. {
  1850. if ( adjArea->IsBlocked( m_me->GetTeamNumber() ) )
  1851. {
  1852. return false;
  1853. }
  1854. if ( travelDistanceSoFar > m_maxRange )
  1855. {
  1856. // too far away
  1857. return false;
  1858. }
  1859. return currentArea->IsContiguous( adjArea );
  1860. }
  1861. const CTFBot *m_me;
  1862. float m_maxRange;
  1863. const CUtlVector< CHandle< CBaseEntity > > &m_potentialVector;
  1864. CUtlVector< CHandle< CBaseEntity > > *m_collectionVector;
  1865. };
  1866. //
  1867. // Search outwards from startSearchArea and collect all reachable objects from the given list that pass the given filter
  1868. // Items in selectedObjectVector will be approximately sorted in nearest-to-farthest order (because of SearchSurroundingAreas)
  1869. //
  1870. void CTFBot::SelectReachableObjects( const CUtlVector< CHandle< CBaseEntity > > &candidateObjectVector,
  1871. CUtlVector< CHandle< CBaseEntity > > *selectedObjectVector,
  1872. const INextBotFilter &filter,
  1873. CNavArea *startSearchArea,
  1874. float maxRange ) const
  1875. {
  1876. if ( startSearchArea == NULL || selectedObjectVector == NULL )
  1877. return;
  1878. selectedObjectVector->RemoveAll();
  1879. // filter candidate objects
  1880. CUtlVector< CHandle< CBaseEntity > > filteredObjectVector;
  1881. for( int i=0; i<candidateObjectVector.Count(); ++i )
  1882. {
  1883. if ( filter.IsSelected( candidateObjectVector[i] ) )
  1884. {
  1885. filteredObjectVector.AddToTail( candidateObjectVector[i] );
  1886. }
  1887. }
  1888. // only keep those that are reachable by us
  1889. CCollectReachableObjects collector( this, maxRange, filteredObjectVector, selectedObjectVector );
  1890. SearchSurroundingAreas( startSearchArea, collector );
  1891. }
  1892. //---------------------------------------------------------------------------------------------
  1893. bool CTFBot::IsAmmoLow( void ) const
  1894. {
  1895. CTFWeaponBase *myWeapon = m_Shared.GetActiveTFWeapon();
  1896. if ( myWeapon )
  1897. {
  1898. if ( myWeapon->GetWeaponID() == TF_WEAPON_WRENCH )
  1899. {
  1900. // wrench is special. it's a melee weapon that wants ammo - metal
  1901. return ( GetAmmoCount( TF_AMMO_METAL ) <= 0 );
  1902. }
  1903. if ( myWeapon->IsMeleeWeapon() )
  1904. {
  1905. // we never run out of ammo with a melee weapon
  1906. return false;
  1907. }
  1908. // no projectile, no ammo needed
  1909. const char *weaponAlias = WeaponIdToAlias( myWeapon->GetWeaponID() );
  1910. if ( weaponAlias )
  1911. {
  1912. WEAPON_FILE_INFO_HANDLE weaponInfoHandle = LookupWeaponInfoSlot( weaponAlias );
  1913. if ( weaponInfoHandle != GetInvalidWeaponInfoHandle() )
  1914. {
  1915. CTFWeaponInfo *weaponInfo = static_cast< CTFWeaponInfo * >( GetFileWeaponInfoFromHandle( weaponInfoHandle ) );
  1916. if ( weaponInfo && weaponInfo->GetWeaponData( TF_WEAPON_PRIMARY_MODE ).m_iProjectile == TF_PROJECTILE_NONE )
  1917. {
  1918. // we don't shoot anything, so we don't need ammo
  1919. return false;
  1920. }
  1921. }
  1922. }
  1923. float ratio = (float)GetAmmoCount( TF_AMMO_PRIMARY ) / (float)( const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_PRIMARY ) );
  1924. if ( ratio < 0.2f )
  1925. {
  1926. return true;
  1927. }
  1928. //if ( !myWeapon->HasPrimaryAmmo() && myWeapon->GetWeaponID() != TF_WEAPON_BUILDER && myWeapon->GetWeaponID() != TF_WEAPON_MEDIGUN )
  1929. }
  1930. return false;
  1931. }
  1932. //-----------------------------------------------------------------------------------------------------
  1933. bool CTFBot::IsAmmoFull( void ) const
  1934. {
  1935. bool isPrimaryFull = GetAmmoCount( TF_AMMO_PRIMARY ) >= const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_PRIMARY );
  1936. bool isSecondaryFull = GetAmmoCount( TF_AMMO_SECONDARY ) >= const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_SECONDARY );
  1937. if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
  1938. {
  1939. // wrench is special. it's a melee weapon that wants ammo - metal
  1940. return ( GetAmmoCount( TF_AMMO_METAL ) >= 200 ) && isPrimaryFull && isSecondaryFull;
  1941. }
  1942. return isPrimaryFull && isSecondaryFull;
  1943. /*
  1944. CTFWeaponBase *myWeapon = m_Shared.GetActiveTFWeapon();
  1945. if ( myWeapon )
  1946. {
  1947. if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
  1948. {
  1949. // wrench is special. it's a melee weapon that wants ammo - metal
  1950. return ( GetAmmoCount( TF_AMMO_METAL ) >= 200 );
  1951. }
  1952. if ( myWeapon->IsMeleeWeapon() )
  1953. {
  1954. // we never run out of ammo with a melee weapon
  1955. return true;
  1956. }
  1957. // no projectile, no ammo needed
  1958. const char *weaponAlias = WeaponIdToAlias( myWeapon->GetWeaponID() );
  1959. if ( weaponAlias )
  1960. {
  1961. WEAPON_FILE_INFO_HANDLE weaponInfoHandle = LookupWeaponInfoSlot( weaponAlias );
  1962. if ( weaponInfoHandle != GetInvalidWeaponInfoHandle() )
  1963. {
  1964. CTFWeaponInfo *weaponInfo = static_cast< CTFWeaponInfo * >( GetFileWeaponInfoFromHandle( weaponInfoHandle ) );
  1965. if ( weaponInfo && weaponInfo->GetWeaponData( TF_WEAPON_PRIMARY_MODE ).m_iProjectile == TF_PROJECTILE_NONE )
  1966. {
  1967. // we don't shoot anything, so we don't need ammo
  1968. return true;
  1969. }
  1970. }
  1971. }
  1972. bool isPrimaryFull = GetAmmoCount( TF_AMMO_PRIMARY ) >= const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_PRIMARY );
  1973. bool isSecondaryFull = GetAmmoCount( TF_AMMO_SECONDARY ) >= const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_SECONDARY );
  1974. return isPrimaryFull && isSecondaryFull;
  1975. }
  1976. return false;
  1977. */
  1978. }
  1979. bool CTFBot::IsDormantWhenDead( void ) const
  1980. {
  1981. return false;
  1982. }
  1983. //-----------------------------------------------------------------------------------------------------
  1984. /**
  1985. * When someone fires their weapon
  1986. */
  1987. void CTFBot::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
  1988. {
  1989. VPROF_BUDGET( "CTFBot::OnWeaponFired", "NextBot" );
  1990. BaseClass::OnWeaponFired( whoFired, weapon );
  1991. if ( !whoFired || !whoFired->IsAlive() )
  1992. return;
  1993. if ( IsRangeGreaterThan( whoFired, tf_bot_notice_gunfire_range.GetFloat() ) )
  1994. return;
  1995. int noticeChance = 100;
  1996. if ( IsQuietWeapon( (CTFWeaponBase *)weapon ) )
  1997. {
  1998. if ( IsRangeGreaterThan( whoFired, tf_bot_notice_quiet_gunfire_range.GetFloat() ) )
  1999. {
  2000. // too far away to hear in any event
  2001. return;
  2002. }
  2003. switch( GetDifficulty() )
  2004. {
  2005. case EASY:
  2006. noticeChance = 10;
  2007. break;
  2008. case NORMAL:
  2009. noticeChance = 30;
  2010. break;
  2011. case HARD:
  2012. noticeChance = 60;
  2013. break;
  2014. default:
  2015. case EXPERT:
  2016. noticeChance = 90;
  2017. break;
  2018. }
  2019. if ( IsEnvironmentNoisy() )
  2020. {
  2021. // less likely to notice with all the noise
  2022. noticeChance /= 2;
  2023. }
  2024. }
  2025. else if ( IsRangeLessThan( whoFired, 1000.0f ) )
  2026. {
  2027. // loud gunfire in our area - it's now "noisy" for a bit
  2028. m_noisyTimer.Start( 3.0f );
  2029. }
  2030. if ( RandomInt( 1, 100 ) > noticeChance )
  2031. {
  2032. return;
  2033. }
  2034. // notice the gunfire
  2035. GetVisionInterface()->AddKnownEntity( whoFired );
  2036. }
  2037. //-----------------------------------------------------------------------------------------------------
  2038. // Return true if we match the given debug symbol
  2039. bool CTFBot::IsDebugFilterMatch( const char *name ) const
  2040. {
  2041. // player classname
  2042. if ( !Q_strnicmp( name, const_cast< CTFBot * >( this )->GetPlayerClass()->GetName(), Q_strlen( name ) ) )
  2043. {
  2044. return true;
  2045. }
  2046. return BaseClass::IsDebugFilterMatch( name );
  2047. }
  2048. //-----------------------------------------------------------------------------------------------------
  2049. class CFindClosestPotentiallyVisibleAreaToPos
  2050. {
  2051. public:
  2052. CFindClosestPotentiallyVisibleAreaToPos( const Vector &pos )
  2053. {
  2054. m_pos = pos;
  2055. m_closeArea = NULL;
  2056. m_closeRangeSq = FLT_MAX;
  2057. }
  2058. bool operator() ( CNavArea *baseArea )
  2059. {
  2060. CTFNavArea *area = (CTFNavArea *)baseArea;
  2061. Vector close;
  2062. area->GetClosestPointOnArea( m_pos, &close );
  2063. float rangeSq = ( close - m_pos ).LengthSqr();
  2064. if ( rangeSq < m_closeRangeSq )
  2065. {
  2066. m_closeArea = area;
  2067. m_closeRangeSq = rangeSq;
  2068. }
  2069. return true;
  2070. }
  2071. Vector m_pos;
  2072. CTFNavArea *m_closeArea;
  2073. float m_closeRangeSq;
  2074. };
  2075. //-----------------------------------------------------------------------------------------------------
  2076. // Update our view to watch where members of the given team will be coming from
  2077. void CTFBot::UpdateLookingAroundForIncomingPlayers( bool lookForEnemies )
  2078. {
  2079. if ( !m_lookAtEnemyInvasionAreasTimer.IsElapsed() )
  2080. return;
  2081. const float maxLookInterval = 1.0f;
  2082. m_lookAtEnemyInvasionAreasTimer.Start( RandomFloat( 0.333f, maxLookInterval ) );
  2083. float minGazeRange = m_Shared.InCond( TF_COND_ZOOMED ) ? 750.0f : 150.0f;
  2084. CTFNavArea *myArea = GetLastKnownArea();
  2085. if ( myArea )
  2086. {
  2087. int team = GetTeamNumber();
  2088. // if we want to look where teammates come from, we need to pass in
  2089. // the *enemy* team, since the method collects *enemy* invasion areas
  2090. if ( !lookForEnemies )
  2091. {
  2092. team = GetEnemyTeam( team );
  2093. }
  2094. const CUtlVector< CTFNavArea * > &invasionAreaVector = myArea->GetEnemyInvasionAreaVector( team );
  2095. if ( invasionAreaVector.Count() > 0 )
  2096. {
  2097. // try to not look directly at walls
  2098. const int retryCount = 20.0f;
  2099. for( int r=0; r<retryCount; ++r )
  2100. {
  2101. int which = RandomInt( 0, invasionAreaVector.Count()-1 );
  2102. Vector gazeSpot = invasionAreaVector[ which ]->GetRandomPoint() + Vector( 0, 0, 0.75f * HumanHeight );
  2103. if ( IsRangeGreaterThan( gazeSpot, minGazeRange ) && GetVisionInterface()->IsLineOfSightClear( gazeSpot ) )
  2104. {
  2105. // use maxLookInterval so these looks override body aiming from path following
  2106. GetBodyInterface()->AimHeadTowards( gazeSpot, IBody::INTERESTING, maxLookInterval, NULL, "Looking toward enemy invasion areas" );
  2107. break;
  2108. }
  2109. }
  2110. }
  2111. }
  2112. }
  2113. //-----------------------------------------------------------------------------------------------------
  2114. /**
  2115. * Update our view to keep an eye on areas where the enemy will be coming from
  2116. */
  2117. void CTFBot::UpdateLookingAroundForEnemies( void )
  2118. {
  2119. if ( !m_isLookingAroundForEnemies )
  2120. return;
  2121. if ( HasAttribute( CTFBot::IGNORE_ENEMIES ) )
  2122. return;
  2123. if ( m_Shared.IsControlStunned() )
  2124. return;
  2125. const float maxLookInterval = 1.0f;
  2126. const CKnownEntity *known = GetVisionInterface()->GetPrimaryKnownThreat();
  2127. if ( known )
  2128. {
  2129. if ( known->IsVisibleInFOVNow() )
  2130. {
  2131. if ( IsPlayerClass( TF_CLASS_SPY ) &&
  2132. GetDifficulty() >= CTFBot::HARD &&
  2133. m_Shared.InCond( TF_COND_DISGUISED ) &&
  2134. !m_Shared.IsStealthed() )
  2135. {
  2136. // smart Spies don't look at their victims until it's too late...
  2137. // look around at where *teammates* will be coming from to fool the enemy
  2138. UpdateLookingAroundForIncomingPlayers( LOOK_FOR_FRIENDS );
  2139. return;
  2140. }
  2141. // I see you!
  2142. GetBodyInterface()->AimHeadTowards( known->GetEntity(), IBody::CRITICAL, 1.0f, NULL, "Aiming at a visible threat" );
  2143. return;
  2144. }
  2145. /* apparently sounds update last known position...
  2146. if ( known->WasEverVisible() && known->GetTimeSinceLastSeen() < 3.0f )
  2147. {
  2148. // I saw you just a moment ago...
  2149. GetBodyInterface()->AimHeadTowards( known->GetLastKnownPosition() + GetClassEyeHeight(), IBody::IMPORTANT, 1.0f, NULL, "Aiming at a last known threat position" );
  2150. return;
  2151. }
  2152. */
  2153. // known but not currently visible (I know you're around here somewhere)
  2154. // if there is unobstructed space between us, turn around
  2155. if ( IsLineOfSightClear( known->GetEntity(), IGNORE_ACTORS ) )
  2156. {
  2157. Vector toThreat = known->GetEntity()->GetAbsOrigin() - GetAbsOrigin();
  2158. float threatRange = toThreat.NormalizeInPlace();
  2159. float aimError = M_PI/6.0f;
  2160. float s, c;
  2161. FastSinCos( aimError, &s, &c );
  2162. float error = threatRange * s;
  2163. Vector imperfectAimSpot = known->GetEntity()->WorldSpaceCenter();
  2164. imperfectAimSpot.x += RandomFloat( -error, error );
  2165. imperfectAimSpot.y += RandomFloat( -error, error );
  2166. GetBodyInterface()->AimHeadTowards( imperfectAimSpot, IBody::IMPORTANT, 1.0f, NULL, "Turning around to find threat out of our FOV" );
  2167. return;
  2168. }
  2169. if ( !IsPlayerClass( TF_CLASS_SNIPER ) )
  2170. {
  2171. // look toward potentially visible area nearest the last known position
  2172. CTFNavArea *myArea = GetLastKnownArea();
  2173. if ( myArea )
  2174. {
  2175. const CTFNavArea *closeArea = NULL;
  2176. CFindClosestPotentiallyVisibleAreaToPos find( known->GetLastKnownPosition() );
  2177. myArea->ForAllPotentiallyVisibleAreas( find );
  2178. closeArea = find.m_closeArea;
  2179. if ( closeArea )
  2180. {
  2181. // try to not look directly at walls
  2182. const int retryCount = 10.0f;
  2183. for( int r=0; r<retryCount; ++r )
  2184. {
  2185. Vector gazeSpot = closeArea->GetRandomPoint() + Vector( 0, 0, 0.75f * HumanHeight );
  2186. if ( GetVisionInterface()->IsLineOfSightClear( gazeSpot ) )
  2187. {
  2188. // use maxLookInterval so these looks override body aiming from path following
  2189. GetBodyInterface()->AimHeadTowards( gazeSpot, IBody::IMPORTANT, maxLookInterval, NULL, "Looking toward potentially visible area near known but hidden threat" );
  2190. return;
  2191. }
  2192. }
  2193. // can't find a clear line to look along
  2194. if ( IsDebugging( NEXTBOT_VISION | NEXTBOT_ERRORS ) )
  2195. {
  2196. ConColorMsg( Color( 255, 255, 0, 255 ), "%3.2f: %s can't find clear line to look at potentially visible near known but hidden entity %s(#%d)\n",
  2197. gpGlobals->curtime,
  2198. GetDebugIdentifier(),
  2199. known->GetEntity()->GetClassname(),
  2200. known->GetEntity()->entindex() );
  2201. }
  2202. }
  2203. else if ( IsDebugging( NEXTBOT_VISION | NEXTBOT_ERRORS ) )
  2204. {
  2205. ConColorMsg( Color( 255, 255, 0, 255 ), "%3.2f: %s no potentially visible area to look toward known but hidden entity %s(#%d)\n",
  2206. gpGlobals->curtime,
  2207. GetDebugIdentifier(),
  2208. known->GetEntity()->GetClassname(),
  2209. known->GetEntity()->entindex() );
  2210. }
  2211. }
  2212. return;
  2213. }
  2214. }
  2215. // no known threat - look toward where enemies will come from
  2216. UpdateLookingAroundForIncomingPlayers( LOOK_FOR_ENEMIES );
  2217. }
  2218. //---------------------------------------------------------------------------------------------
  2219. class CFindVantagePoint : public ISearchSurroundingAreasFunctor
  2220. {
  2221. public:
  2222. CFindVantagePoint( int enemyTeamIndex )
  2223. {
  2224. m_enemyTeamIndex = enemyTeamIndex;
  2225. m_vantageArea = NULL;
  2226. }
  2227. virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
  2228. {
  2229. CTFNavArea *area = (CTFNavArea *)baseArea;
  2230. CTeam *enemyTeam = GetGlobalTeam( m_enemyTeamIndex );
  2231. for( int i=0; i<enemyTeam->GetNumPlayers(); ++i )
  2232. {
  2233. CTFPlayer *enemy = (CTFPlayer *)enemyTeam->GetPlayer(i);
  2234. if ( !enemy->IsAlive() || !enemy->GetLastKnownArea() )
  2235. continue;
  2236. CTFNavArea *enemyArea = (CTFNavArea *)enemy->GetLastKnownArea();
  2237. if ( enemyArea->IsCompletelyVisible( area ) )
  2238. {
  2239. // nearby area from which we can see the enemy team
  2240. m_vantageArea = area;
  2241. return false;
  2242. }
  2243. }
  2244. return true;
  2245. }
  2246. int m_enemyTeamIndex;
  2247. CTFNavArea *m_vantageArea;
  2248. };
  2249. //-----------------------------------------------------------------------------------------------------
  2250. // Return a nearby area where we can see a member of the enemy team
  2251. CTFNavArea *CTFBot::FindVantagePoint( float maxTravelDistance ) const
  2252. {
  2253. CFindVantagePoint find( GetTeamNumber() == TF_TEAM_BLUE ? TF_TEAM_RED : TF_TEAM_BLUE );
  2254. SearchSurroundingAreas( GetLastKnownArea(), find, maxTravelDistance );
  2255. return find.m_vantageArea;
  2256. }
  2257. //-----------------------------------------------------------------------------------------------------
  2258. /**
  2259. * Return perceived danger of threat (0=none, 1=immediate deadly danger)
  2260. * @todo: Move this to contextual query
  2261. * @todo: Differentiate between potential threats (that sentry up ahead along our route) and immediate threats (the sentry I'm in range of)
  2262. */
  2263. float CTFBot::GetThreatDanger( CBaseCombatCharacter *who ) const
  2264. {
  2265. if ( who == NULL )
  2266. return 0.0f;
  2267. if ( IsPlayerClass( TF_CLASS_SNIPER ) )
  2268. {
  2269. if ( IsRangeGreaterThan( who, tf_bot_sniper_personal_space_range.GetFloat() ) )
  2270. {
  2271. // far away enemies are no threat to a Sniper
  2272. return 0.0f;
  2273. }
  2274. }
  2275. if ( who->IsPlayer() )
  2276. {
  2277. CTFPlayer *player = ToTFPlayer( who );
  2278. // ubers are scary
  2279. if ( player->m_Shared.IsInvulnerable() )
  2280. return 1.0f;
  2281. switch( player->GetPlayerClass()->GetClassIndex() )
  2282. {
  2283. case TF_CLASS_MEDIC:
  2284. return 0.2f; // 1/5
  2285. case TF_CLASS_ENGINEER:
  2286. case TF_CLASS_SNIPER:
  2287. return 0.4f; // 2/5
  2288. case TF_CLASS_SCOUT:
  2289. case TF_CLASS_SPY:
  2290. case TF_CLASS_DEMOMAN:
  2291. return 0.6f; // 3/5
  2292. case TF_CLASS_SOLDIER:
  2293. case TF_CLASS_HEAVYWEAPONS:
  2294. return 0.8f; // 4/5
  2295. case TF_CLASS_PYRO:
  2296. return 1.0f; // 5/5
  2297. }
  2298. }
  2299. else
  2300. {
  2301. // sentry gun
  2302. CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( who );
  2303. if ( sentry )
  2304. {
  2305. if ( !sentry->IsAlive() || sentry->IsPlacing() || sentry->HasSapper() || sentry->IsPlasmaDisabled() || sentry->IsUpgrading() || sentry->IsBuilding() )
  2306. return 0.0f;
  2307. switch( sentry->GetUpgradeLevel() )
  2308. {
  2309. case 3: return 1.0f;
  2310. case 2: return 0.8f;
  2311. default: return 0.6f;
  2312. }
  2313. }
  2314. }
  2315. return 0.0f;
  2316. }
  2317. //-----------------------------------------------------------------------------------------------------
  2318. /**
  2319. * Return the max range at which we can effectively attack
  2320. */
  2321. float CTFBot::GetMaxAttackRange( void ) const
  2322. {
  2323. CTFWeaponBase *myWeapon = m_Shared.GetActiveTFWeapon();
  2324. if ( !myWeapon )
  2325. return 0.0f;
  2326. if ( myWeapon->IsMeleeWeapon() )
  2327. {
  2328. return 100.0f;
  2329. }
  2330. if ( myWeapon->IsWeapon( TF_WEAPON_FLAMETHROWER ) )
  2331. {
  2332. if ( TFGameRules()->IsMannVsMachineMode() )
  2333. {
  2334. const float flameRange = 350.0f;
  2335. static CSchemaItemDefHandle pItemDef_GiantFlamethrower( "MVM Giant Flamethrower" );
  2336. if ( IsActiveTFWeapon( pItemDef_GiantFlamethrower ) )
  2337. {
  2338. return 2.5f * flameRange;
  2339. }
  2340. return flameRange;
  2341. }
  2342. return 250.0f;
  2343. }
  2344. if ( WeaponID_IsSniperRifle( myWeapon->GetWeaponID() ) )
  2345. {
  2346. // infinite
  2347. return FLT_MAX;
  2348. }
  2349. if ( myWeapon->IsWeapon( TF_WEAPON_ROCKETLAUNCHER ) )
  2350. {
  2351. return 3000.0f;
  2352. }
  2353. // bullet spray weapons, grenades, etc
  2354. // for now, default to infinite so bot always returns fire and doesn't look dumb
  2355. return FLT_MAX;
  2356. }
  2357. //-----------------------------------------------------------------------------------------------------
  2358. /**
  2359. * Return the ideal range at which we can effectively attack
  2360. */
  2361. float CTFBot::GetDesiredAttackRange( void ) const
  2362. {
  2363. CTFWeaponBase *myWeapon = m_Shared.GetActiveTFWeapon();
  2364. if ( !myWeapon )
  2365. return 0.0f;
  2366. if ( myWeapon->IsWeapon( TF_WEAPON_KNIFE ) )
  2367. {
  2368. // get very close and stab
  2369. return 70.0f; // 60
  2370. }
  2371. if ( myWeapon->IsMeleeWeapon() )
  2372. {
  2373. return 100.0f;
  2374. }
  2375. if ( myWeapon->IsWeapon( TF_WEAPON_FLAMETHROWER ) )
  2376. {
  2377. return 100.0f;
  2378. }
  2379. if ( WeaponID_IsSniperRifle( myWeapon->GetWeaponID() ) )
  2380. {
  2381. // infinite
  2382. return FLT_MAX;
  2383. }
  2384. if ( myWeapon->IsWeapon( TF_WEAPON_ROCKETLAUNCHER ) && !TFGameRules()->IsMannVsMachineMode() )
  2385. {
  2386. return 1250.0f;
  2387. }
  2388. // bullet spray weapons, grenades, etc
  2389. return 500.0f;
  2390. }
  2391. //-----------------------------------------------------------------------------------------------------
  2392. // If we're required to equip a specific weapon, do it.
  2393. bool CTFBot::EquipRequiredWeapon( void )
  2394. {
  2395. // if we have a required weapon on our stack, it takes precedence (items, etc)
  2396. if ( m_requiredWeaponStack.Count() )
  2397. {
  2398. CBaseCombatWeapon *pWeapon = m_requiredWeaponStack.Top().Get();
  2399. return Weapon_Switch( pWeapon );
  2400. }
  2401. if ( TheTFBots().IsMeleeOnly() || TFGameRules()->IsInMedievalMode() || HasWeaponRestriction( MELEE_ONLY ) )
  2402. {
  2403. // force use of melee weapons
  2404. Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_MELEE ) );
  2405. return true;
  2406. }
  2407. if ( HasWeaponRestriction( PRIMARY_ONLY ) )
  2408. {
  2409. Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_PRIMARY ) );
  2410. return true;
  2411. }
  2412. if ( HasWeaponRestriction( SECONDARY_ONLY ) )
  2413. {
  2414. Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
  2415. return true;
  2416. }
  2417. return false;
  2418. }
  2419. //-----------------------------------------------------------------------------------------------------
  2420. // Equip the best weapon we have to attack the given threat
  2421. void CTFBot::EquipBestWeaponForThreat( const CKnownEntity *threat )
  2422. {
  2423. if ( EquipRequiredWeapon() )
  2424. return;
  2425. #ifdef TF_RAID_MODE
  2426. if ( TFGameRules()->IsRaidMode() )
  2427. {
  2428. if ( HasAttribute( CTFBot::AGGRESSIVE ) )
  2429. {
  2430. // mobs never equip other weapons
  2431. return;
  2432. }
  2433. if ( GetPlayerClass()->GetClassIndex() == TF_CLASS_DEMOMAN && !IsInASquad() )
  2434. {
  2435. // wandering demomen use stickies only
  2436. Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
  2437. return;
  2438. }
  2439. }
  2440. #endif // TF_RAID_MODE
  2441. CTFWeaponBase *primary = dynamic_cast< CTFWeaponBase *>( Weapon_GetSlot( TF_WPN_TYPE_PRIMARY ) );
  2442. if ( !IsCombatWeapon( primary ) )
  2443. {
  2444. primary = NULL;
  2445. }
  2446. CTFWeaponBase *secondary = dynamic_cast< CTFWeaponBase *>( Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
  2447. if ( !IsCombatWeapon( secondary ) )
  2448. {
  2449. secondary = NULL;
  2450. }
  2451. // no secondary weapons in MvM
  2452. if ( TFGameRules()->IsMannVsMachineMode() )
  2453. {
  2454. if ( IsPlayerClass( TF_CLASS_MEDIC ) && IsInASquad() && GetSquad() && !GetSquad()->IsLeader( this ) )
  2455. {
  2456. // always try to heal leader
  2457. Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
  2458. return;
  2459. }
  2460. secondary = NULL;
  2461. }
  2462. CTFWeaponBase *melee = dynamic_cast< CTFWeaponBase *>( Weapon_GetSlot( TF_WPN_TYPE_MELEE ) );
  2463. if ( !IsCombatWeapon( melee ) )
  2464. {
  2465. melee = NULL;
  2466. }
  2467. CTFWeaponBase *gun = NULL;
  2468. if ( primary )
  2469. {
  2470. gun = primary;
  2471. }
  2472. else if ( secondary )
  2473. {
  2474. gun = secondary;
  2475. }
  2476. else
  2477. {
  2478. gun = melee;
  2479. }
  2480. if ( IsDifficulty( CTFBot::EASY ) )
  2481. {
  2482. // easy bots always use their primary weapon if they have one
  2483. if ( gun )
  2484. {
  2485. Weapon_Switch( gun );
  2486. }
  2487. return;
  2488. }
  2489. if ( !threat || !threat->WasEverVisible() || threat->GetTimeSinceLastSeen() > 5.0f )
  2490. {
  2491. // no threat - go back to primary weapon so it has a chance to reload
  2492. if ( gun )
  2493. {
  2494. Weapon_Switch( gun );
  2495. }
  2496. return;
  2497. }
  2498. // now filter weapons by available ammo
  2499. if ( GetAmmoCount( TF_AMMO_PRIMARY ) <= 0 )
  2500. {
  2501. primary = NULL;
  2502. }
  2503. if ( GetAmmoCount( TF_WPN_TYPE_SECONDARY ) <= 0 )
  2504. {
  2505. secondary = NULL;
  2506. }
  2507. // modify our gun choice based on threat situation (range, etc)
  2508. switch( GetPlayerClass()->GetClassIndex() )
  2509. {
  2510. case TF_CLASS_DEMOMAN:
  2511. case TF_CLASS_HEAVYWEAPONS:
  2512. case TF_CLASS_SPY:
  2513. case TF_CLASS_MEDIC:
  2514. case TF_CLASS_ENGINEER:
  2515. // primary
  2516. break;
  2517. case TF_CLASS_SCOUT:
  2518. {
  2519. if ( secondary )
  2520. {
  2521. if ( gun && !gun->Clip1() )
  2522. {
  2523. gun = secondary;
  2524. }
  2525. }
  2526. }
  2527. break;
  2528. case TF_CLASS_SOLDIER:
  2529. {
  2530. // if we've emptied our rocket launcher clip and are fighting a nearby threat, switch to our secondary if it is ready to fire
  2531. if ( gun && !gun->Clip1() )
  2532. {
  2533. if ( secondary && secondary->Clip1() )
  2534. {
  2535. const float closeSoldierRange = 500.0f;
  2536. if ( IsRangeLessThan( threat->GetLastKnownPosition(), closeSoldierRange ) )
  2537. {
  2538. gun = secondary;
  2539. }
  2540. }
  2541. }
  2542. }
  2543. break;
  2544. case TF_CLASS_SNIPER:
  2545. {
  2546. const float closeSniperRange = 750.0f;
  2547. if ( secondary && IsRangeLessThan( threat->GetLastKnownPosition(), closeSniperRange ) )
  2548. gun = secondary;
  2549. }
  2550. break;
  2551. case TF_CLASS_PYRO:
  2552. {
  2553. const float flameRange = 750.0f;
  2554. if ( secondary && IsRangeGreaterThan( threat->GetLastKnownPosition(), flameRange ) )
  2555. {
  2556. gun = secondary;
  2557. }
  2558. // keep flamethrower out to reflect projectiles
  2559. if ( threat->GetEntity() && threat->GetEntity()->IsPlayer() )
  2560. {
  2561. CTFPlayer *enemy = ToTFPlayer( threat->GetEntity() );
  2562. if ( enemy->IsPlayerClass( TF_CLASS_SOLDIER ) || enemy->IsPlayerClass( TF_CLASS_DEMOMAN ) )
  2563. {
  2564. gun = primary;
  2565. }
  2566. }
  2567. }
  2568. break;
  2569. }
  2570. if ( gun )
  2571. {
  2572. Weapon_Switch( gun );
  2573. }
  2574. }
  2575. //-----------------------------------------------------------------------------------------------------
  2576. // NOTE: This assumes default weapon loadouts
  2577. bool CTFBot::EquipLongRangeWeapon( void )
  2578. {
  2579. // no secondary weapons in MvM
  2580. if ( TFGameRules()->IsMannVsMachineMode() )
  2581. return false;
  2582. if ( IsPlayerClass( TF_CLASS_SOLDIER ) ||
  2583. IsPlayerClass( TF_CLASS_DEMOMAN ) ||
  2584. IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ||
  2585. IsPlayerClass( TF_CLASS_SNIPER ) )
  2586. {
  2587. CBaseCombatWeapon *primary = Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
  2588. if ( primary )
  2589. {
  2590. if ( GetAmmoCount( TF_AMMO_PRIMARY ) > 0 )
  2591. {
  2592. Weapon_Switch( primary );
  2593. return true;
  2594. }
  2595. }
  2596. }
  2597. // fall back to our secondary (or go right to it if its the only thing we have that has reach)
  2598. CBaseCombatWeapon *secondary = Weapon_GetSlot( TF_WPN_TYPE_SECONDARY );
  2599. if ( secondary )
  2600. {
  2601. if ( GetAmmoCount( TF_AMMO_SECONDARY ) > 0 )
  2602. {
  2603. Weapon_Switch( secondary );
  2604. return true;
  2605. }
  2606. }
  2607. return false;
  2608. }
  2609. //-----------------------------------------------------------------------------------------------------
  2610. // Force us to equip and use this weapon until popped off the required stack
  2611. void CTFBot::PushRequiredWeapon( CTFWeaponBase *weapon )
  2612. {
  2613. m_requiredWeaponStack.Push( weapon );
  2614. }
  2615. //-----------------------------------------------------------------------------------------------------
  2616. // Pop top required weapon off of stack and discard
  2617. void CTFBot::PopRequiredWeapon( void )
  2618. {
  2619. m_requiredWeaponStack.Pop();
  2620. }
  2621. //-----------------------------------------------------------------------------------------------------
  2622. // return true if given weapon can be used to attack
  2623. bool CTFBot::IsCombatWeapon( CTFWeaponBase *weapon ) const
  2624. {
  2625. if ( weapon == MY_CURRENT_GUN ) // MY_CURRENT_GUN == NULL
  2626. {
  2627. weapon = m_Shared.GetActiveTFWeapon();
  2628. }
  2629. if ( weapon )
  2630. {
  2631. switch ( weapon->GetWeaponID() )
  2632. {
  2633. case TF_WEAPON_MEDIGUN:
  2634. case TF_WEAPON_PDA:
  2635. case TF_WEAPON_PDA_ENGINEER_BUILD:
  2636. case TF_WEAPON_PDA_ENGINEER_DESTROY:
  2637. case TF_WEAPON_PDA_SPY:
  2638. case TF_WEAPON_BUILDER:
  2639. case TF_WEAPON_DISPENSER:
  2640. case TF_WEAPON_INVIS:
  2641. case TF_WEAPON_LUNCHBOX:
  2642. case TF_WEAPON_BUFF_ITEM:
  2643. case TF_WEAPON_PUMPKIN_BOMB:
  2644. return false;
  2645. };
  2646. }
  2647. return true;
  2648. }
  2649. //-----------------------------------------------------------------------------------------------------
  2650. // return true if given weapon is a "hitscan" weapon
  2651. bool CTFBot::IsHitScanWeapon( CTFWeaponBase *weapon ) const
  2652. {
  2653. if ( weapon == MY_CURRENT_GUN ) // MY_CURRENT_GUN == NULL
  2654. {
  2655. weapon = m_Shared.GetActiveTFWeapon();
  2656. }
  2657. if ( weapon )
  2658. {
  2659. switch ( weapon->GetWeaponID() )
  2660. {
  2661. case TF_WEAPON_SHOTGUN_PRIMARY:
  2662. case TF_WEAPON_SHOTGUN_SOLDIER:
  2663. case TF_WEAPON_SHOTGUN_HWG:
  2664. case TF_WEAPON_SHOTGUN_PYRO:
  2665. case TF_WEAPON_SCATTERGUN:
  2666. case TF_WEAPON_SNIPERRIFLE:
  2667. case TF_WEAPON_MINIGUN:
  2668. case TF_WEAPON_SMG:
  2669. case TF_WEAPON_CHARGED_SMG:
  2670. case TF_WEAPON_PISTOL:
  2671. case TF_WEAPON_PISTOL_SCOUT:
  2672. case TF_WEAPON_REVOLVER:
  2673. case TF_WEAPON_SENTRY_BULLET:
  2674. case TF_WEAPON_SENTRY_ROCKET:
  2675. case TF_WEAPON_SENTRY_REVENGE:
  2676. case TF_WEAPON_HANDGUN_SCOUT_PRIMARY:
  2677. case TF_WEAPON_HANDGUN_SCOUT_SECONDARY:
  2678. case TF_WEAPON_SODA_POPPER:
  2679. case TF_WEAPON_SNIPERRIFLE_DECAP:
  2680. case TF_WEAPON_PEP_BRAWLER_BLASTER:
  2681. case TF_WEAPON_SNIPERRIFLE_CLASSIC:
  2682. return true;
  2683. };
  2684. }
  2685. return false;
  2686. }
  2687. //-----------------------------------------------------------------------------------------------------
  2688. // return true if given weapon "sprays" bullets/fire/etc continuously (ie: not individual rockets/etc)
  2689. bool CTFBot::IsContinuousFireWeapon( CTFWeaponBase *weapon ) const
  2690. {
  2691. if ( weapon == MY_CURRENT_GUN )
  2692. {
  2693. weapon = m_Shared.GetActiveTFWeapon();
  2694. }
  2695. if ( !IsCombatWeapon( weapon ) )
  2696. return false;
  2697. if ( weapon )
  2698. {
  2699. switch ( weapon->GetWeaponID() )
  2700. {
  2701. case TF_WEAPON_ROCKETLAUNCHER:
  2702. case TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT:
  2703. case TF_WEAPON_GRENADELAUNCHER:
  2704. case TF_WEAPON_PIPEBOMBLAUNCHER:
  2705. case TF_WEAPON_PISTOL:
  2706. case TF_WEAPON_PISTOL_SCOUT:
  2707. case TF_WEAPON_FLAREGUN:
  2708. case TF_WEAPON_JAR:
  2709. case TF_WEAPON_COMPOUND_BOW:
  2710. return false;
  2711. };
  2712. }
  2713. return true;
  2714. }
  2715. //-----------------------------------------------------------------------------------------------------
  2716. // return true if given weapon launches explosive projectiles with splash damage
  2717. bool CTFBot::IsExplosiveProjectileWeapon( CTFWeaponBase *weapon ) const
  2718. {
  2719. if ( weapon == MY_CURRENT_GUN )
  2720. {
  2721. weapon = m_Shared.GetActiveTFWeapon();
  2722. }
  2723. if ( weapon )
  2724. {
  2725. switch ( weapon->GetWeaponID() )
  2726. {
  2727. case TF_WEAPON_ROCKETLAUNCHER:
  2728. case TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT:
  2729. case TF_WEAPON_GRENADELAUNCHER:
  2730. case TF_WEAPON_PIPEBOMBLAUNCHER:
  2731. case TF_WEAPON_JAR:
  2732. return true;
  2733. };
  2734. }
  2735. return false;
  2736. }
  2737. //-----------------------------------------------------------------------------------------------------
  2738. // return true if given weapon has small clip and long reload cost (ie: rocket launcher, etc)
  2739. bool CTFBot::IsBarrageAndReloadWeapon( CTFWeaponBase *weapon ) const
  2740. {
  2741. if ( weapon == MY_CURRENT_GUN )
  2742. {
  2743. weapon = m_Shared.GetActiveTFWeapon();
  2744. }
  2745. if ( weapon )
  2746. {
  2747. switch ( weapon->GetWeaponID() )
  2748. {
  2749. case TF_WEAPON_ROCKETLAUNCHER:
  2750. case TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT:
  2751. case TF_WEAPON_GRENADELAUNCHER:
  2752. case TF_WEAPON_PIPEBOMBLAUNCHER:
  2753. case TF_WEAPON_SCATTERGUN:
  2754. return true;
  2755. };
  2756. }
  2757. return false;
  2758. }
  2759. //-----------------------------------------------------------------------------------------------------
  2760. // Return true if given weapon doesn't make much sound when used (ie: spy knife, etc)
  2761. bool CTFBot::IsQuietWeapon( CTFWeaponBase *weapon ) const
  2762. {
  2763. if ( weapon == MY_CURRENT_GUN )
  2764. {
  2765. weapon = m_Shared.GetActiveTFWeapon();
  2766. }
  2767. if ( weapon )
  2768. {
  2769. switch ( weapon->GetWeaponID() )
  2770. {
  2771. case TF_WEAPON_KNIFE:
  2772. case TF_WEAPON_FISTS:
  2773. case TF_WEAPON_PDA:
  2774. case TF_WEAPON_PDA_ENGINEER_BUILD:
  2775. case TF_WEAPON_PDA_ENGINEER_DESTROY:
  2776. case TF_WEAPON_PDA_SPY:
  2777. case TF_WEAPON_BUILDER:
  2778. case TF_WEAPON_MEDIGUN:
  2779. case TF_WEAPON_DISPENSER:
  2780. case TF_WEAPON_INVIS:
  2781. case TF_WEAPON_FLAREGUN:
  2782. case TF_WEAPON_LUNCHBOX:
  2783. case TF_WEAPON_JAR:
  2784. case TF_WEAPON_COMPOUND_BOW:
  2785. case TF_WEAPON_SWORD:
  2786. case TF_WEAPON_CROSSBOW:
  2787. return true;
  2788. };
  2789. }
  2790. return false;
  2791. }
  2792. //-----------------------------------------------------------------------------------------------------
  2793. // Return true if a weapon has no obstructions along the line between the given points
  2794. bool CTFBot::IsLineOfFireClear( const Vector &from, const Vector &to ) const
  2795. {
  2796. trace_t trace;
  2797. NextBotTraceFilterIgnoreActors botFilter( NULL, COLLISION_GROUP_NONE );
  2798. CTraceFilterIgnoreFriendlyCombatItems ignoreFriendlyCombatFilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
  2799. CTraceFilterChain filter( &botFilter, &ignoreFriendlyCombatFilter );
  2800. UTIL_TraceLine( from, to, MASK_SOLID_BRUSHONLY, &filter, &trace );
  2801. return !trace.DidHit();
  2802. }
  2803. //-----------------------------------------------------------------------------------------------------
  2804. // Return true if a weapon has no obstructions along the line from our eye to the given position
  2805. bool CTFBot::IsLineOfFireClear( const Vector &where ) const
  2806. {
  2807. return IsLineOfFireClear( const_cast< CTFBot * >( this )->EyePosition(), where );
  2808. }
  2809. //-----------------------------------------------------------------------------------------------------
  2810. // Return true if a weapon has no obstructions along the line between the given point and entity
  2811. bool CTFBot::IsLineOfFireClear( const Vector &from, CBaseEntity *who ) const
  2812. {
  2813. trace_t trace;
  2814. NextBotTraceFilterIgnoreActors botFilter( NULL, COLLISION_GROUP_NONE );
  2815. CTraceFilterIgnoreFriendlyCombatItems ignoreFriendlyCombatFilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
  2816. CTraceFilterChain filter( &botFilter, &ignoreFriendlyCombatFilter );
  2817. UTIL_TraceLine( from, who->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &filter, &trace );
  2818. return !trace.DidHit() || trace.m_pEnt == who;
  2819. }
  2820. //-----------------------------------------------------------------------------------------------------
  2821. // Return true if a weapon has no obstructions along the line from our eye to the given entity
  2822. bool CTFBot::IsLineOfFireClear( CBaseEntity *who ) const
  2823. {
  2824. return IsLineOfFireClear( const_cast< CTFBot * >( this )->EyePosition(), who );
  2825. }
  2826. //-----------------------------------------------------------------------------------------------------
  2827. bool CTFBot::IsEntityBetweenTargetAndSelf( CBaseEntity *other, CBaseEntity *target )
  2828. {
  2829. Vector toTarget = target->GetAbsOrigin() - GetAbsOrigin();
  2830. float rangeToTarget = toTarget.NormalizeInPlace();
  2831. Vector toOther = other->GetAbsOrigin() - GetAbsOrigin();
  2832. float rangeToOther = toOther.NormalizeInPlace();
  2833. return rangeToOther < rangeToTarget && DotProduct( toTarget, toOther ) > 0.7071f;
  2834. }
  2835. //-----------------------------------------------------------------------------------------------------
  2836. // Return true if we are sure this player actually is an enemy spy
  2837. bool CTFBot::IsKnownSpy( CTFPlayer *player ) const
  2838. {
  2839. for( int i=0; i<m_knownSpyVector.Count(); ++i )
  2840. {
  2841. CTFPlayer *spy = m_knownSpyVector[i];
  2842. if ( spy && player->entindex() == spy->entindex() )
  2843. {
  2844. return true;
  2845. }
  2846. }
  2847. return false;
  2848. }
  2849. //-----------------------------------------------------------------------------------------------------
  2850. // Return true if we suspect this player might be an enemy spy
  2851. CTFBot::SuspectedSpyInfo_t* CTFBot::IsSuspectedSpy( CTFPlayer *pPlayer )
  2852. {
  2853. for( int i=0; i<m_suspectedSpyVector.Count(); ++i )
  2854. {
  2855. SuspectedSpyInfo_t* pSpyInfo = m_suspectedSpyVector[i];
  2856. CTFPlayer* pSpy = pSpyInfo->m_suspectedSpy;
  2857. if ( pSpy && pPlayer->entindex() == pSpy->entindex() )
  2858. {
  2859. return pSpyInfo;
  2860. }
  2861. }
  2862. return NULL;
  2863. }
  2864. //-----------------------------------------------------------------------------------------------------
  2865. // Note that this player might be a spy
  2866. void CTFBot::SuspectSpy( CTFPlayer *pPlayer )
  2867. {
  2868. SuspectedSpyInfo_t* pSpyInfo = IsSuspectedSpy( pPlayer );
  2869. // Start suspecting this spy if we're not aware of them until now
  2870. if( pSpyInfo == NULL )
  2871. {
  2872. // add to head for LRU effect
  2873. pSpyInfo = new SuspectedSpyInfo_t;
  2874. pSpyInfo->m_suspectedSpy = pPlayer;
  2875. m_suspectedSpyVector.AddToHead( pSpyInfo );
  2876. }
  2877. // Suspicious!
  2878. pSpyInfo->Suspect();
  2879. // Too suspicious?
  2880. if( pSpyInfo->TestForRealizing() )
  2881. {
  2882. RealizeSpy( pPlayer );
  2883. }
  2884. }
  2885. void CTFBot::SuspectedSpyInfo_t::Suspect()
  2886. {
  2887. int nCurTime = floor(gpGlobals->curtime);
  2888. // Add our new entry
  2889. m_touchTimes.AddToHead( nCurTime );
  2890. }
  2891. bool CTFBot::SuspectedSpyInfo_t::TestForRealizing()
  2892. {
  2893. // Remove any old entries
  2894. int nCurTime = floor(gpGlobals->curtime);
  2895. int nCutoffTime = nCurTime - tf_bot_suspect_spy_touch_interval.GetInt();
  2896. FOR_EACH_VEC_BACK( m_touchTimes, i )
  2897. {
  2898. if( m_touchTimes[i] <= nCutoffTime )
  2899. m_touchTimes.Remove( i );
  2900. }
  2901. // Add our new entry
  2902. m_touchTimes.AddToHead( nCurTime );
  2903. // Setup an array of bools representing the past few seconds that we want
  2904. // to look for suspicious activity
  2905. CUtlVector<bool> vecSeconds;
  2906. vecSeconds.SetSize( tf_bot_suspect_spy_touch_interval.GetInt() );
  2907. FOR_EACH_VEC( vecSeconds, i )
  2908. {
  2909. vecSeconds[i] = false;
  2910. }
  2911. // Go through each time chunk and mark if there was suspicious activity
  2912. FOR_EACH_VEC( m_touchTimes, i )
  2913. {
  2914. int nTouchTime = m_touchTimes[i];
  2915. int nTimeSlot = nCurTime - nTouchTime;
  2916. if( nTimeSlot >= 0 && nTimeSlot < vecSeconds.Count() )
  2917. {
  2918. vecSeconds[nTimeSlot] = true;
  2919. }
  2920. }
  2921. // If all are true, then the spy has been suspicious enough to warrant being realized
  2922. FOR_EACH_VEC( vecSeconds, i )
  2923. {
  2924. if( vecSeconds[i] == false )
  2925. {
  2926. return false;
  2927. }
  2928. }
  2929. return true;
  2930. }
  2931. bool CTFBot::SuspectedSpyInfo_t::IsCurrentlySuspected()
  2932. {
  2933. float flCutoffTime = gpGlobals->curtime - tf_bot_suspect_spy_forget_cooldown.GetFloat();
  2934. if( m_touchTimes.Count() && m_touchTimes.Head() > flCutoffTime )
  2935. {
  2936. return true;
  2937. }
  2938. return false;
  2939. }
  2940. //-----------------------------------------------------------------------------------------------------
  2941. // Note that this player *IS* a spy
  2942. void CTFBot::RealizeSpy( CTFPlayer *pPlayer )
  2943. {
  2944. // We already know about this spy
  2945. if ( IsKnownSpy( pPlayer ) )
  2946. return;
  2947. // add to head for LRU effect
  2948. m_knownSpyVector.AddToHead( pPlayer );
  2949. // inform my teammates
  2950. SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_CLOAKEDSPY );
  2951. // If I am suspicious of this spy, make everyone around me know that
  2952. // they should be suspicious too
  2953. SuspectedSpyInfo_t* pSuspectInfo = IsSuspectedSpy( pPlayer );
  2954. if( pSuspectInfo && pSuspectInfo->IsCurrentlySuspected() )
  2955. {
  2956. // Tell others around us we've realized there's a spy
  2957. CUtlVector< CTFPlayer * > playerVector;
  2958. CollectPlayers( &playerVector, GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
  2959. FOR_EACH_VEC( playerVector, i )
  2960. {
  2961. CTFPlayer* pOther = playerVector[i];
  2962. if( !pOther->IsBot() )
  2963. continue;
  2964. //Make sure they're close by
  2965. Vector vecBetween = EyePosition() - pOther->EyePosition();
  2966. if( vecBetween.IsLengthLessThan( 512.f ) )
  2967. {
  2968. // If they dont know about this spy
  2969. CTFBot* pOtherBot = static_cast<CTFBot*>( pOther );
  2970. if( !pOtherBot->IsKnownSpy( pPlayer ) )
  2971. {
  2972. // I was suspicious that they were a spy, make my friend suspicious as well.
  2973. // This will cause them to attack a disguised spy in MvM for a bit.
  2974. pOtherBot->SuspectSpy( pPlayer );
  2975. // Tell them about it
  2976. pOtherBot->RealizeSpy( pPlayer );
  2977. }
  2978. }
  2979. }
  2980. }
  2981. }
  2982. //-----------------------------------------------------------------------------------------------------
  2983. // Remove player from spy suspect system
  2984. void CTFBot::ForgetSpy( CTFPlayer *pPlayer )
  2985. {
  2986. StopSuspectingSpy( pPlayer );
  2987. m_knownSpyVector.FindAndFastRemove( pPlayer );
  2988. }
  2989. void CTFBot::StopSuspectingSpy( CTFPlayer *pPlayer )
  2990. {
  2991. // Find the entry matching this spy
  2992. for( int i=0; i<m_suspectedSpyVector.Count(); ++i )
  2993. {
  2994. SuspectedSpyInfo_t* pSpyInfo = m_suspectedSpyVector[i];
  2995. CTFPlayer* pSpy = pSpyInfo->m_suspectedSpy;
  2996. if ( pSpy && pPlayer->entindex() == pSpy->entindex() )
  2997. {
  2998. delete pSpyInfo;
  2999. m_suspectedSpyVector.Remove(i);
  3000. break;
  3001. }
  3002. }
  3003. }
  3004. //-----------------------------------------------------------------------------------------------------
  3005. // Return the nearest human player on the given team who is looking directly at me
  3006. CTFPlayer *CTFBot::GetClosestHumanLookingAtMe( int team ) const
  3007. {
  3008. CUtlVector< CTFPlayer * > otherVector;
  3009. CollectPlayers( &otherVector, team, COLLECT_ONLY_LIVING_PLAYERS );
  3010. float closeRange = FLT_MAX;
  3011. CTFPlayer *close = NULL;
  3012. for( int i=0; i<otherVector.Count(); ++i )
  3013. {
  3014. CTFPlayer *other = otherVector[i];
  3015. if ( other->IsBot() )
  3016. continue;
  3017. Vector otherEye, otherForward;
  3018. other->EyePositionAndVectors( &otherEye, &otherForward, NULL, NULL );
  3019. Vector toMe = const_cast< CTFBot * >( this )->EyePosition() - otherEye;
  3020. float range = toMe.NormalizeInPlace();
  3021. if ( range < closeRange )
  3022. {
  3023. const float cosTolerance = 0.98f;
  3024. if ( DotProduct( toMe, otherForward ) > cosTolerance )
  3025. {
  3026. // a human is looking toward me - check LOS
  3027. if ( IsLineOfSightClear( otherEye, IGNORE_NOTHING, other ) )
  3028. {
  3029. close = other;
  3030. closeRange = range;
  3031. }
  3032. }
  3033. }
  3034. }
  3035. return close;
  3036. }
  3037. //-----------------------------------------------------------------------------------------------------
  3038. // become a member of the given squad
  3039. void CTFBot::JoinSquad( CTFBotSquad *squad )
  3040. {
  3041. if ( squad )
  3042. {
  3043. squad->Join( this );
  3044. m_squad = squad;
  3045. }
  3046. }
  3047. //-----------------------------------------------------------------------------------------------------
  3048. // leave our current squad
  3049. void CTFBot::LeaveSquad( void )
  3050. {
  3051. if ( m_squad )
  3052. {
  3053. m_squad->Leave( this );
  3054. m_squad = NULL;
  3055. }
  3056. }
  3057. //-----------------------------------------------------------------------------------------------------
  3058. // leave our current squad
  3059. void CTFBot::DeleteSquad( void )
  3060. {
  3061. if ( m_squad )
  3062. {
  3063. m_squad = NULL;
  3064. }
  3065. }
  3066. //---------------------------------------------------------------------------------------------
  3067. bool CTFBot::IsWeaponRestricted( CTFWeaponBase *weapon ) const
  3068. {
  3069. if ( !weapon )
  3070. {
  3071. return false;
  3072. }
  3073. // Get the weapon's loadout slot
  3074. CEconItemView *pEconItemView = weapon->GetAttributeContainer()->GetItem();
  3075. if ( !pEconItemView )
  3076. return false;
  3077. CTFItemDefinition *pItemDef = pEconItemView->GetStaticData();
  3078. if ( !pItemDef )
  3079. return false;
  3080. int iLoadoutSlot = pItemDef->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() );
  3081. if ( HasWeaponRestriction( MELEE_ONLY ) )
  3082. {
  3083. return (iLoadoutSlot != LOADOUT_POSITION_MELEE);
  3084. }
  3085. if ( HasWeaponRestriction( PRIMARY_ONLY ) )
  3086. {
  3087. return (iLoadoutSlot != LOADOUT_POSITION_PRIMARY);
  3088. }
  3089. if ( HasWeaponRestriction( SECONDARY_ONLY ) )
  3090. {
  3091. return (iLoadoutSlot != LOADOUT_POSITION_SECONDARY);
  3092. }
  3093. return false;
  3094. }
  3095. //---------------------------------------------------------------------------------------------
  3096. //
  3097. // Return true if there is something we want to reflect directly ahead of us
  3098. //
  3099. bool CTFBot::ShouldFireCompressionBlast( void )
  3100. {
  3101. if ( TFGameRules()->IsInTraining() )
  3102. {
  3103. // no reflection in training mode
  3104. return false;
  3105. }
  3106. if ( !tf_bot_pyro_always_reflect.GetBool() )
  3107. {
  3108. if ( IsDifficulty( CTFBot::EASY ) )
  3109. {
  3110. // easy bots can't reflect at all
  3111. return false;
  3112. }
  3113. if ( IsDifficulty( CTFBot::NORMAL ) )
  3114. {
  3115. // normal bots reflect some of the time
  3116. if ( TransientlyConsistentRandomValue( 1.0f ) < 0.5f )
  3117. {
  3118. return false;
  3119. }
  3120. }
  3121. if ( IsDifficulty( CTFBot::HARD ) )
  3122. {
  3123. // hard bots reflect most of the time
  3124. if ( TransientlyConsistentRandomValue( 1.0f ) < 0.1f )
  3125. {
  3126. return false;
  3127. }
  3128. }
  3129. }
  3130. bool shouldPushPlayers = !TFGameRules()->IsMannVsMachineMode();
  3131. if ( shouldPushPlayers )
  3132. {
  3133. const CKnownEntity *threat = GetVisionInterface()->GetPrimaryKnownThreat( true );
  3134. if ( threat && threat->GetEntity() && threat->GetEntity()->IsPlayer() )
  3135. {
  3136. CTFPlayer *pushVictim = ToTFPlayer( threat->GetEntity() );
  3137. if ( IsRangeLessThan( pushVictim, tf_bot_pyro_shove_away_range.GetFloat() ) )
  3138. {
  3139. // our threat is very close - shove them!
  3140. // always shove ubers
  3141. if ( pushVictim && pushVictim->m_Shared.IsInvulnerable() )
  3142. {
  3143. return true;
  3144. }
  3145. if ( pushVictim->GetGroundEntity() == NULL )
  3146. {
  3147. // they are in the air - juggle them some of the time
  3148. return ( TransientlyConsistentRandomValue( 0.5f ) < 0.5f );
  3149. }
  3150. if ( pushVictim->IsCapturingPoint() )
  3151. {
  3152. // push them off the point!
  3153. return true;
  3154. }
  3155. // be pushy sometimes
  3156. if ( TransientlyConsistentRandomValue( 3.0f ) < 0.5f )
  3157. {
  3158. return true;
  3159. }
  3160. }
  3161. }
  3162. }
  3163. Vector vecEye = EyePosition();
  3164. Vector vecForward, vecRight, vecUp;
  3165. AngleVectors( EyeAngles(), &vecForward, &vecRight, &vecUp );
  3166. Vector vecCenter = vecEye + vecForward * 128;
  3167. Vector vecSize = Vector( 128, 128, 64 );
  3168. const int maxCollectedEntities = 128;
  3169. CBaseEntity *pObjects[ maxCollectedEntities ];
  3170. int count = UTIL_EntitiesInBox( pObjects, maxCollectedEntities, vecCenter - vecSize, vecCenter + vecSize, FL_CLIENT | FL_GRENADE );
  3171. for ( int i = 0; i < count; i++ )
  3172. {
  3173. CBaseEntity *pObject = pObjects[i];
  3174. if ( pObject == this )
  3175. continue;
  3176. if ( pObject->GetTeamNumber() == GetTeamNumber() )
  3177. continue;
  3178. // should air blast player logic is already done before this loop
  3179. if ( pObject->IsPlayer() )
  3180. continue;
  3181. // is this something I want to deflect?
  3182. if ( !pObject->IsDeflectable() )
  3183. continue;
  3184. if ( FClassnameIs( pObject, "tf_projectile_rocket" ) || FClassnameIs( pObject, "tf_projectile_energy_ball" ) )
  3185. {
  3186. // is it headed right for me?
  3187. Vector vecThemUnitVel = pObject->GetAbsVelocity();
  3188. vecThemUnitVel.z = 0.0f;
  3189. vecThemUnitVel.NormalizeInPlace();
  3190. Vector horzForward( vecForward.x, vecForward.y, 0.0f );
  3191. horzForward.NormalizeInPlace();
  3192. if ( DotProduct( horzForward, vecThemUnitVel ) > -tf_bot_pyro_deflect_tolerance.GetFloat() )
  3193. continue;
  3194. }
  3195. // can I see it?
  3196. if ( !GetVisionInterface()->IsLineOfSightClear( pObject->WorldSpaceCenter() ) )
  3197. continue;
  3198. // bounce it!
  3199. return true;
  3200. }
  3201. return false;
  3202. }
  3203. //---------------------------------------------------------------------------------------------
  3204. // Compute a pseudo random value (0-1) that stays consistent for the
  3205. // given period of time, but changes unpredictably each period.
  3206. float CTFBot::TransientlyConsistentRandomValue( float period, int seedValue ) const
  3207. {
  3208. CNavArea *area = GetLastKnownArea();
  3209. if ( !area )
  3210. {
  3211. return 0.0f;
  3212. }
  3213. // this term stays stable for 'period' seconds, then changes in an unpredictable way
  3214. int timeMod = (int)( gpGlobals->curtime / period ) + 1;
  3215. return fabs( FastCos( (float)( seedValue + ( entindex() * area->GetID() * timeMod ) ) ) );
  3216. }
  3217. //---------------------------------------------------------------------------------------------
  3218. // Given a target entity, find a target within 'maxSplashRadius' that has clear line of fire
  3219. // to both the target entity and to me.
  3220. bool CTFBot::FindSplashTarget( CBaseEntity *target, float maxSplashRadius, Vector *splashTarget ) const
  3221. {
  3222. if ( !target || !splashTarget )
  3223. return false;
  3224. *splashTarget = target->WorldSpaceCenter();
  3225. const int retryCount = 50;
  3226. for( int i=0; i<retryCount; ++i )
  3227. {
  3228. Vector probe = target->WorldSpaceCenter() + RandomVector( -maxSplashRadius, maxSplashRadius );
  3229. trace_t trace;
  3230. NextBotTraceFilterIgnoreActors filter( NULL, COLLISION_GROUP_NONE );
  3231. UTIL_TraceLine( target->WorldSpaceCenter(), probe, MASK_SOLID_BRUSHONLY, &filter, &trace );
  3232. if ( trace.DidHitWorld() )
  3233. {
  3234. // can we shoot this spot?
  3235. if ( IsLineOfFireClear( trace.endpos ) )
  3236. {
  3237. // yes, found a corner-sticky target
  3238. *splashTarget = trace.endpos;
  3239. NDebugOverlay::Line( target->WorldSpaceCenter(), trace.endpos, 255, 0, 0, true, 60.0f );
  3240. NDebugOverlay::Cross3D( trace.endpos, 5.0f, 255, 255, 0, true, 60.0f );
  3241. return true;
  3242. }
  3243. }
  3244. }
  3245. return false;
  3246. }
  3247. //---------------------------------------------------------------------------------------------
  3248. // Restrict bot's attention to only this entity (or radius around this entity) to the exclusion of everything else
  3249. void CTFBot::SetAttentionFocus( CBaseEntity *focusOn )
  3250. {
  3251. m_attentionFocusEntity = focusOn;
  3252. }
  3253. //---------------------------------------------------------------------------------------------
  3254. // Remove attention focus restrictions
  3255. void CTFBot::ClearAttentionFocus( void )
  3256. {
  3257. m_attentionFocusEntity = NULL;
  3258. }
  3259. //---------------------------------------------------------------------------------------------
  3260. bool CTFBot::IsAttentionFocused( void ) const
  3261. {
  3262. return m_attentionFocusEntity != NULL;
  3263. }
  3264. //---------------------------------------------------------------------------------------------
  3265. bool CTFBot::IsAttentionFocusedOn( CBaseEntity *who ) const
  3266. {
  3267. if ( m_attentionFocusEntity == NULL || who == NULL )
  3268. {
  3269. return false;
  3270. }
  3271. if ( m_attentionFocusEntity->entindex() == who->entindex() )
  3272. {
  3273. // specifically focused on this entity
  3274. return true;
  3275. }
  3276. CTFBotActionPoint *actionPoint = dynamic_cast< CTFBotActionPoint * >( m_attentionFocusEntity.Get() );
  3277. if ( actionPoint )
  3278. {
  3279. // we attend to everything within the action point's radius
  3280. return actionPoint->IsWithinRange( who );
  3281. }
  3282. return false;
  3283. }
  3284. //---------------------------------------------------------------------------------------------
  3285. // Notice the given threat after the given number of seconds have elapsed
  3286. void CTFBot::DelayedThreatNotice( CHandle< CBaseEntity > who, float noticeDelay )
  3287. {
  3288. float when = gpGlobals->curtime + noticeDelay;
  3289. // if we already have a delayed notice for this threat, ignore the new one unless the delay is less
  3290. for( int i=0; i<m_delayedNoticeVector.Count(); ++i )
  3291. {
  3292. if ( m_delayedNoticeVector[i].m_who == who )
  3293. {
  3294. if ( m_delayedNoticeVector[i].m_when > when )
  3295. {
  3296. // update delay to shorter time
  3297. m_delayedNoticeVector[i].m_when = when;
  3298. }
  3299. return;
  3300. }
  3301. }
  3302. // new notice
  3303. DelayedNoticeInfo delay;
  3304. delay.m_who = who;
  3305. delay.m_when = when;
  3306. m_delayedNoticeVector.AddToTail( delay );
  3307. }
  3308. //---------------------------------------------------------------------------------------------
  3309. void CTFBot::UpdateDelayedThreatNotices( void )
  3310. {
  3311. for( int i=0; i<m_delayedNoticeVector.Count(); ++i )
  3312. {
  3313. if ( m_delayedNoticeVector[i].m_when <= gpGlobals->curtime )
  3314. {
  3315. // delay is up - notice this threat
  3316. CBaseEntity *who = m_delayedNoticeVector[i].m_who;
  3317. if ( who )
  3318. {
  3319. if ( who->IsPlayer() )
  3320. {
  3321. CTFPlayer *player = ToTFPlayer( who );
  3322. if ( player->IsPlayerClass( TF_CLASS_SPY ) )
  3323. {
  3324. RealizeSpy( player );
  3325. }
  3326. }
  3327. GetVisionInterface()->AddKnownEntity( who );
  3328. }
  3329. m_delayedNoticeVector.Remove( i );
  3330. --i;
  3331. }
  3332. }
  3333. }
  3334. //---------------------------------------------------------------------------------------------
  3335. void CTFBot::GiveRandomItem( loadout_positions_t loadoutPosition )
  3336. {
  3337. CUtlVector< const CEconItemDefinition * > itemVector;
  3338. const CEconItemSchema::ItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetItemDefinitionMap();
  3339. FOR_EACH_MAP_FAST( mapItemDefs, i )
  3340. {
  3341. const CTFItemDefinition *pItemDef = dynamic_cast< const CTFItemDefinition * >( mapItemDefs[i] );
  3342. if ( pItemDef && pItemDef->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() ) == loadoutPosition )
  3343. {
  3344. itemVector.AddToTail( pItemDef );
  3345. }
  3346. }
  3347. if ( itemVector.Count() > 0 )
  3348. {
  3349. int which = RandomInt( 0, itemVector.Count()-1 );
  3350. /*
  3351. CBaseCombatWeapon *myMelee = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
  3352. me->Weapon_Detach( myMelee );
  3353. UTIL_Remove( myMelee );
  3354. */
  3355. const char *itemName = itemVector[ which ]->GetDefinitionName();
  3356. BotGenerateAndWearItem( this, itemName );
  3357. }
  3358. }
  3359. //---------------------------------------------------------------------------------------------
  3360. bool CTFBot::IsSquadmate( CTFPlayer *who ) const
  3361. {
  3362. if ( !m_squad || !who || !who->IsBotOfType( TF_BOT_TYPE ) )
  3363. return false;
  3364. return GetSquad() == ToTFBot( who )->GetSquad();
  3365. }
  3366. //---------------------------------------------------------------------------------------------
  3367. // Set Spy disguise to be a class that someone on the enemy team is actually using
  3368. void CTFBot::DisguiseAsMemberOfEnemyTeam( void )
  3369. {
  3370. CUtlVector< CTFPlayer * > enemyVector;
  3371. CollectPlayers( &enemyVector, GetEnemyTeam( GetTeamNumber() ) );
  3372. int disguise = RandomInt( TF_FIRST_NORMAL_CLASS, TF_LAST_NORMAL_CLASS-1 );
  3373. if ( enemyVector.Count() > 0 )
  3374. {
  3375. disguise = enemyVector[ RandomInt( 0, enemyVector.Count()-1 ) ]->GetPlayerClass()->GetClassIndex();
  3376. }
  3377. m_Shared.Disguise( GetEnemyTeam( GetTeamNumber() ), disguise );
  3378. }
  3379. //---------------------------------------------------------------------------------------------
  3380. void CTFBot::ClearTags( void )
  3381. {
  3382. m_tags.RemoveAll();
  3383. }
  3384. //---------------------------------------------------------------------------------------------
  3385. void CTFBot::AddTag( const char *tag )
  3386. {
  3387. if ( !HasTag( tag ) )
  3388. {
  3389. m_tags.AddToTail( CFmtStr( "%s", tag ) );
  3390. }
  3391. }
  3392. //---------------------------------------------------------------------------------------------
  3393. void CTFBot::RemoveTag( const char *tag )
  3394. {
  3395. for ( int i=0; i<m_tags.Count(); ++i )
  3396. {
  3397. if ( FStrEq( tag, m_tags[i] ) )
  3398. {
  3399. m_tags.Remove(i);
  3400. return;
  3401. }
  3402. }
  3403. }
  3404. //---------------------------------------------------------------------------------------------
  3405. // TODO: Make this an efficient lookup/match
  3406. bool CTFBot::HasTag( const char *tag )
  3407. {
  3408. for( int i=0; i<m_tags.Count(); ++i )
  3409. {
  3410. if ( FStrEq( tag, m_tags[i] ) )
  3411. {
  3412. return true;
  3413. }
  3414. }
  3415. return false;
  3416. }
  3417. //---------------------------------------------------------------------------------------------
  3418. CBaseObject *CTFBot::GetNearestKnownSappableTarget( void )
  3419. {
  3420. CUtlVector< CKnownEntity > knownVector;
  3421. GetVisionInterface()->CollectKnownEntities( &knownVector );
  3422. CBaseObject *closeObject = NULL;
  3423. float closeObjectRangeSq = 500.0f * 500.0f;
  3424. for( int i=0; i<knownVector.Count(); ++i )
  3425. {
  3426. CBaseObject *enemyObject = dynamic_cast< CBaseObject * >( knownVector[i].GetEntity() );
  3427. if ( enemyObject && !enemyObject->HasSapper() && IsEnemy( enemyObject ) )
  3428. {
  3429. float rangeSq = GetRangeSquaredTo( enemyObject );
  3430. if ( rangeSq < closeObjectRangeSq )
  3431. {
  3432. closeObjectRangeSq = rangeSq;
  3433. closeObject = enemyObject;
  3434. }
  3435. }
  3436. }
  3437. return closeObject;
  3438. }
  3439. //-----------------------------------------------------------------------------------------
  3440. Action< CTFBot > *CTFBot::OpportunisticallyUseWeaponAbilities( void )
  3441. {
  3442. if ( !m_opportunisticTimer.IsElapsed() )
  3443. {
  3444. return NULL;
  3445. }
  3446. m_opportunisticTimer.Start( RandomFloat( 0.1f, 0.2f ) );
  3447. // if I'm wearing a charge shield, use it!
  3448. if ( IsPlayerClass( TF_CLASS_DEMOMAN ) && m_Shared.IsShieldEquipped() )
  3449. {
  3450. Vector forward;
  3451. EyeVectors( &forward );
  3452. bool bShouldCharge = GetLocomotionInterface()->IsPotentiallyTraversable( GetAbsOrigin(), GetAbsOrigin() + 100.0f * forward, ILocomotion::IMMEDIATELY );
  3453. if ( HasAttribute( CTFBot::AIR_CHARGE_ONLY ) && ( GetGroundEntity() || GetAbsVelocity().z > 0 ) )
  3454. {
  3455. bShouldCharge = false;
  3456. }
  3457. if ( bShouldCharge )
  3458. {
  3459. PressAltFireButton();
  3460. }
  3461. }
  3462. // if I'm wearing parachute, check if I should activate my parachute
  3463. else if ( m_Shared.IsParachuteEquipped() )
  3464. {
  3465. bool bIsBurning = m_Shared.InCond( TF_COND_BURNING );
  3466. float flHealthPercent = (float)GetHealth() / GetMaxHealth();
  3467. const float flHealthThreshold = 0.5f;
  3468. // should I activate parachute?
  3469. if ( !m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) )
  3470. {
  3471. float flMinParachuteGroundDistance = 300.f;
  3472. // check if I'm falling, high enough off the ground to deploy parachute, and not burning
  3473. if ( flHealthPercent >= flHealthThreshold && !bIsBurning && GetAbsVelocity().z < 0 && GetLocomotionInterface()->IsPotentiallyTraversable( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -flMinParachuteGroundDistance ), ILocomotion::IMMEDIATELY ) )
  3474. {
  3475. PressJumpButton();
  3476. }
  3477. }
  3478. // should I deactivate parachute?
  3479. else
  3480. {
  3481. float flCancelParachuteDistance = 150.f;
  3482. // if I'm burning or close enough to landing, deactivate the parachute or health less than some threshold
  3483. if ( flHealthPercent < flHealthThreshold || bIsBurning || !GetLocomotionInterface()->IsPotentiallyTraversable( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -flCancelParachuteDistance ), ILocomotion::IMMEDIATELY ) )
  3484. {
  3485. PressJumpButton();
  3486. }
  3487. }
  3488. }
  3489. // don't use items if we have the flag, since most of them are unusable (unless we're a bomb carrier in MvM)
  3490. if ( HasTheFlag() && !TFGameRules()->IsMannVsMachineMode() )
  3491. {
  3492. return NULL;
  3493. }
  3494. for ( int w=0; w<MAX_WEAPONS; ++w )
  3495. {
  3496. CTFWeaponBase *weapon = ( CTFWeaponBase * )GetWeapon( w );
  3497. if ( !weapon )
  3498. continue;
  3499. // if I have some kind of buff banner - use it!
  3500. if ( weapon->GetWeaponID() == TF_WEAPON_BUFF_ITEM )
  3501. {
  3502. CTFBuffItem *buff = (CTFBuffItem *)weapon;
  3503. if ( buff->IsFull() )
  3504. {
  3505. return new CTFBotUseItem( buff );
  3506. }
  3507. }
  3508. else if ( weapon->GetWeaponID() == TF_WEAPON_LUNCHBOX )
  3509. {
  3510. // if we have an eatable (drink, sandvich, etc) - eat it!
  3511. CTFLunchBox *lunchbox = (CTFLunchBox *)weapon;
  3512. if ( lunchbox->HasAmmo() )
  3513. {
  3514. // scout lunchboxes are also gated by their energy drink meter
  3515. if ( !IsPlayerClass( TF_CLASS_SCOUT ) || m_Shared.GetScoutEnergyDrinkMeter() >= 100 )
  3516. {
  3517. return new CTFBotUseItem( lunchbox );
  3518. }
  3519. }
  3520. }
  3521. else if ( weapon->GetWeaponID() == TF_WEAPON_BAT_WOOD )
  3522. {
  3523. // sandman
  3524. if ( GetAmmoCount( TF_AMMO_GRENADES1 ) > 0 )
  3525. {
  3526. const CKnownEntity *threat = GetVisionInterface()->GetPrimaryKnownThreat();
  3527. if ( threat && threat->IsVisibleInFOVNow() )
  3528. {
  3529. // hit a stunball
  3530. PressAltFireButton();
  3531. }
  3532. }
  3533. }
  3534. }
  3535. return NULL;
  3536. }
  3537. //-----------------------------------------------------------------------------------------
  3538. // mostly for MvM - pick a random enemy player that is not in their spawn room
  3539. CTFPlayer *CTFBot::SelectRandomReachableEnemy( void )
  3540. {
  3541. CUtlVector< CTFPlayer * > livePlayerVector;
  3542. CollectPlayers( &livePlayerVector, GetEnemyTeam( GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
  3543. // only consider players who have left their spawn
  3544. CUtlVector< CTFPlayer * > playerVector;
  3545. for( int i=0; i<livePlayerVector.Count(); ++i )
  3546. {
  3547. CTFPlayer *player = livePlayerVector[i];
  3548. if ( !PointInRespawnRoom( player, player->WorldSpaceCenter() ) )
  3549. {
  3550. playerVector.AddToTail( player );
  3551. }
  3552. }
  3553. if ( playerVector.Count() > 0 )
  3554. {
  3555. return playerVector[ RandomInt( 0, playerVector.Count()-1 ) ];
  3556. }
  3557. return NULL;
  3558. }
  3559. //-----------------------------------------------------------------------------------------
  3560. // Different sized bots used different lookahead distances
  3561. float CTFBot::GetDesiredPathLookAheadRange( void ) const
  3562. {
  3563. return tf_bot_path_lookahead_range.GetFloat() * GetModelScale();
  3564. }
  3565. //-----------------------------------------------------------------------------------------
  3566. // Hack to apply idle loop sounds in MvM
  3567. void CTFBot::StartIdleSound( void )
  3568. {
  3569. StopIdleSound();
  3570. if ( TFGameRules() && !TFGameRules()->IsMannVsMachineMode() )
  3571. return;
  3572. // SHIELD YOUR EYES MIKEB!!!
  3573. if ( IsMiniBoss() )
  3574. {
  3575. const char *pszSoundName = NULL;
  3576. int iClass = GetPlayerClass()->GetClassIndex();
  3577. switch ( iClass )
  3578. {
  3579. case TF_CLASS_HEAVYWEAPONS:
  3580. {
  3581. pszSoundName = "MVM.GiantHeavyLoop";
  3582. break;
  3583. }
  3584. case TF_CLASS_SOLDIER:
  3585. {
  3586. pszSoundName = "MVM.GiantSoldierLoop";
  3587. break;
  3588. }
  3589. case TF_CLASS_DEMOMAN:
  3590. {
  3591. if ( m_mission == MISSION_DESTROY_SENTRIES )
  3592. {
  3593. pszSoundName = "MVM.SentryBusterLoop";
  3594. }
  3595. else
  3596. {
  3597. pszSoundName = "MVM.GiantDemomanLoop";
  3598. }
  3599. break;
  3600. }
  3601. case TF_CLASS_SCOUT:
  3602. {
  3603. pszSoundName = "MVM.GiantScoutLoop";
  3604. break;
  3605. }
  3606. case TF_CLASS_PYRO:
  3607. {
  3608. pszSoundName = "MVM.GiantPyroLoop";
  3609. break;
  3610. }
  3611. }
  3612. if ( pszSoundName )
  3613. {
  3614. CReliableBroadcastRecipientFilter filter;
  3615. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  3616. m_pIdleSound = controller.SoundCreate( filter, entindex(), pszSoundName );
  3617. controller.Play( m_pIdleSound, 1.0, 100 );
  3618. }
  3619. }
  3620. }
  3621. //-----------------------------------------------------------------------------------------
  3622. void CTFBot::StopIdleSound( void )
  3623. {
  3624. if ( m_pIdleSound )
  3625. {
  3626. CSoundEnvelopeController::GetController().SoundDestroy( m_pIdleSound );
  3627. m_pIdleSound = NULL;
  3628. }
  3629. }
  3630. bool CTFBot::ShouldAutoJump()
  3631. {
  3632. if ( !HasAttribute( CTFBot::AUTO_JUMP ) )
  3633. return false;
  3634. if ( !m_autoJumpTimer.HasStarted() )
  3635. {
  3636. m_autoJumpTimer.Start( RandomFloat( m_flAutoJumpMin, m_flAutoJumpMax ) );
  3637. return true;
  3638. }
  3639. else if ( m_autoJumpTimer.IsElapsed() )
  3640. {
  3641. m_autoJumpTimer.Start( RandomFloat( m_flAutoJumpMin, m_flAutoJumpMax ) );
  3642. return true;
  3643. }
  3644. return false;
  3645. }
  3646. void CTFBot::SetFlagTarget( CCaptureFlag* pFlag )
  3647. {
  3648. if ( m_hFollowingFlagTarget != pFlag )
  3649. {
  3650. if ( m_hFollowingFlagTarget )
  3651. {
  3652. m_hFollowingFlagTarget->RemoveFollower( this );
  3653. }
  3654. m_hFollowingFlagTarget = pFlag;
  3655. if ( m_hFollowingFlagTarget )
  3656. {
  3657. m_hFollowingFlagTarget->AddFollower( this );
  3658. }
  3659. }
  3660. }
  3661. int CTFBot::DrawDebugTextOverlays(void)
  3662. {
  3663. int offset = tf_bot_debug_tags.GetBool() ? 1 : BaseClass::DrawDebugTextOverlays();
  3664. CUtlString strTags = "Tags : ";
  3665. for( int i=0; i<m_tags.Count(); ++i )
  3666. {
  3667. strTags.Append( m_tags[i] );
  3668. strTags.Append( " " );
  3669. }
  3670. EntityText( offset, strTags.Get(), 0 );
  3671. offset++;
  3672. return offset;
  3673. }
  3674. void CTFBot::AddEventChangeAttributes( const CTFBot::EventChangeAttributes_t* newEvent )
  3675. {
  3676. m_eventChangeAttributes.AddToTail( newEvent );
  3677. }
  3678. const CTFBot::EventChangeAttributes_t* CTFBot::GetEventChangeAttributes( const char* pszEventName ) const
  3679. {
  3680. for ( int i=0; i<m_eventChangeAttributes.Count(); ++i )
  3681. {
  3682. if ( FStrEq( m_eventChangeAttributes[i]->m_eventName, pszEventName ) )
  3683. {
  3684. return m_eventChangeAttributes[i];
  3685. }
  3686. }
  3687. return NULL;
  3688. }
  3689. void CTFBot::OnEventChangeAttributes( const CTFBot::EventChangeAttributes_t* pEvent )
  3690. {
  3691. if ( pEvent )
  3692. {
  3693. SetDifficulty( pEvent->m_skill );
  3694. ClearWeaponRestrictions();
  3695. SetWeaponRestriction( pEvent->m_weaponRestriction );
  3696. SetMission( pEvent->m_mission );
  3697. ClearAllAttributes();
  3698. SetAttribute( pEvent->m_attributeFlags );
  3699. SetMaxVisionRangeOverride( pEvent->m_maxVisionRange );
  3700. if ( TFGameRules()->IsMannVsMachineMode() )
  3701. {
  3702. SetAttribute( CTFBot::BECOME_SPECTATOR_ON_DEATH );
  3703. SetAttribute( CTFBot::RETAIN_BUILDINGS );
  3704. }
  3705. // cache off health value before we clear attribute because ModifyMaxHealth adds new attribute and reset the health
  3706. int nHealth = GetHealth();
  3707. int nMaxHealth = GetMaxHealth();
  3708. // remove any player attributes
  3709. RemovePlayerAttributes( false );
  3710. // and add ones that we want specifically
  3711. FOR_EACH_VEC( pEvent->m_characterAttributes, i )
  3712. {
  3713. const CEconItemAttributeDefinition *pDef = pEvent->m_characterAttributes[i].GetAttributeDefinition();
  3714. if ( pDef )
  3715. {
  3716. Assert( GetAttributeList() );
  3717. GetAttributeList()->SetRuntimeAttributeValue( pDef, pEvent->m_characterAttributes[i].m_value.asFloat );
  3718. }
  3719. }
  3720. NetworkStateChanged();
  3721. // set health back to what it was before we clear bot's attributes
  3722. ModifyMaxHealth( nMaxHealth );
  3723. SetHealth( nHealth );
  3724. // give items to bot before apply attribute changes
  3725. FOR_EACH_VEC( pEvent->m_items, i )
  3726. {
  3727. AddItem( pEvent->m_items[i] );
  3728. }
  3729. // add attributes to equipped items
  3730. FOR_EACH_VEC( pEvent->m_itemsAttributes, i )
  3731. {
  3732. const CTFBot::EventChangeAttributes_t::item_attributes_t& itemAttributes = pEvent->m_itemsAttributes[i];
  3733. CSchemaItemDefHandle itemDef( itemAttributes.m_itemName );
  3734. if ( !itemDef )
  3735. {
  3736. Warning( "Unable to find item %s to update attribute.\n", itemAttributes.m_itemName.Get() );
  3737. }
  3738. for ( int iItemSlot = LOADOUT_POSITION_PRIMARY ; iItemSlot < CLASS_LOADOUT_POSITION_COUNT ; iItemSlot++ )
  3739. {
  3740. CEconEntity* pEntity = NULL;
  3741. CEconItemView *pCurItemData = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( this, iItemSlot, &pEntity );
  3742. if ( pCurItemData && itemDef && ( pCurItemData->GetItemDefIndex() == itemDef->GetDefinitionIndex() ) )
  3743. {
  3744. for ( int iAtt=0; iAtt<itemAttributes.m_attributes.Count(); ++iAtt )
  3745. {
  3746. const static_attrib_t& attrib = itemAttributes.m_attributes[iAtt];
  3747. CAttributeList *pAttribList = pCurItemData->GetAttributeList();
  3748. if ( pAttribList )
  3749. {
  3750. pAttribList->SetRuntimeAttributeValue( attrib.GetAttributeDefinition(), attrib.m_value.asFloat );
  3751. }
  3752. }
  3753. if ( pEntity )
  3754. {
  3755. // update model incase we change style
  3756. pEntity->UpdateModelToClass();
  3757. }
  3758. // move on to the next set of attributes
  3759. break;
  3760. }
  3761. } // for each slot
  3762. } // for each set of attributes
  3763. // tags
  3764. ClearTags();
  3765. for( int g=0; g<pEvent->m_tags.Count(); ++g )
  3766. {
  3767. AddTag( pEvent->m_tags[g] );
  3768. }
  3769. }
  3770. }
  3771. void CTFBot::AddItem( const char* pszItemName )
  3772. {
  3773. CItemSelectionCriteria criteria;
  3774. criteria.SetQuality( AE_USE_SCRIPT_VALUE );
  3775. criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemName, true );
  3776. CBaseEntity *pItem = ItemGeneration()->GenerateRandomItem( &criteria, WorldSpaceCenter(), vec3_angle );
  3777. if ( pItem )
  3778. {
  3779. CEconItemView *pScriptItem = static_cast< CBaseCombatWeapon * >( pItem )->GetAttributeContainer()->GetItem();
  3780. // If we already have an item in that slot, remove it
  3781. int iClass = GetPlayerClass()->GetClassIndex();
  3782. int iSlot = pScriptItem->GetStaticData()->GetLoadoutSlot( iClass );
  3783. equip_region_mask_t unNewItemRegionMask = pScriptItem->GetItemDefinition() ? pScriptItem->GetItemDefinition()->GetEquipRegionConflictMask() : 0;
  3784. if ( IsWearableSlot( iSlot ) )
  3785. {
  3786. // Remove any wearable that has a conflicting equip_region
  3787. for ( int wbl = 0; wbl < GetNumWearables(); wbl++ )
  3788. {
  3789. CEconWearable *pWearable = GetWearable( wbl );
  3790. if ( !pWearable )
  3791. continue;
  3792. equip_region_mask_t unWearableRegionMask = 0;
  3793. if ( pWearable->GetAttributeContainer()->GetItem() )
  3794. {
  3795. unWearableRegionMask = pWearable->GetAttributeContainer()->GetItem()->GetItemDefinition()->GetEquipRegionConflictMask();
  3796. }
  3797. if ( unWearableRegionMask & unNewItemRegionMask )
  3798. {
  3799. RemoveWearable( pWearable );
  3800. }
  3801. }
  3802. }
  3803. else
  3804. {
  3805. CBaseEntity *pEntity = GetEntityForLoadoutSlot( iSlot );
  3806. if ( pEntity )
  3807. {
  3808. CBaseCombatWeapon *pWpn = dynamic_cast< CBaseCombatWeapon * >( pEntity );
  3809. Weapon_Detach( pWpn );
  3810. UTIL_Remove( pEntity );
  3811. }
  3812. }
  3813. // Fake global id
  3814. pScriptItem->SetItemID( 1 );
  3815. DispatchSpawn( pItem );
  3816. CEconEntity *pNewItem = assert_cast<CEconEntity*>( pItem );
  3817. if ( pNewItem )
  3818. {
  3819. pNewItem->GiveTo( this );
  3820. }
  3821. PostInventoryApplication();
  3822. }
  3823. else
  3824. {
  3825. if ( pszItemName && pszItemName[0] )
  3826. {
  3827. DevMsg( "CTFBotSpawner::AddItemToBot: Invalid item %s.\n", pszItemName );
  3828. }
  3829. }
  3830. }
  3831. int CTFBot::GetUberHealthThreshold()
  3832. {
  3833. int iUberHealthThreshold = 0;
  3834. CALL_ATTRIB_HOOK_INT( iUberHealthThreshold, bot_medic_uber_health_threshold );
  3835. if ( iUberHealthThreshold > 0 )
  3836. {
  3837. return iUberHealthThreshold;
  3838. }
  3839. return 50;
  3840. }
  3841. float CTFBot::GetUberDeployDelayDuration()
  3842. {
  3843. float flDelayUberDuration = 0;
  3844. CALL_ATTRIB_HOOK_INT( flDelayUberDuration, bot_medic_uber_deploy_delay_duration );
  3845. if ( flDelayUberDuration > 0 )
  3846. {
  3847. return flDelayUberDuration;
  3848. }
  3849. return -1.f;
  3850. }