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.

2390 lines
62 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // Author: Michael S. Booth ([email protected]), 2003
  8. #pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
  9. #include "cbase.h"
  10. #include "cs_bot.h"
  11. #include "nav_area.h"
  12. #include "cs_gamerules.h"
  13. #include "shared_util.h"
  14. #include "KeyValues.h"
  15. #include "tier0/icommandline.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. #ifdef _WIN32
  19. #pragma warning (disable:4701) // disable warning that variable *may* not be initialized
  20. #endif
  21. CBotManager *TheBots = NULL;
  22. bool CCSBotManager::m_isMapDataLoaded = false;
  23. int g_nClientPutInServerOverrides = 0;
  24. void DrawOccupyTime( void );
  25. ConVar bot_show_occupy_time( "bot_show_occupy_time", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show when each nav area can first be reached by each team." );
  26. void DrawBattlefront( void );
  27. ConVar bot_show_battlefront( "bot_show_battlefront", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show areas where rushing players will initially meet." );
  28. int UTIL_CSSBotsInGame( void );
  29. ConVar bot_join_delay( "bot_join_delay", "0", FCVAR_GAMEDLL, "Prevents bots from joining the server for this many seconds after a map change." );
  30. /**
  31. * Determine whether bots can be used or not
  32. */
  33. inline bool AreBotsAllowed()
  34. {
  35. // If they pass in -nobots, don't allow bots. This is for people who host servers, to
  36. // allow them to disallow bots to enforce CPU limits.
  37. const char *nobots = CommandLine()->CheckParm( "-nobots" );
  38. if ( nobots )
  39. {
  40. return false;
  41. }
  42. return true;
  43. }
  44. //--------------------------------------------------------------------------------------------------------------
  45. void InstallBotControl( void )
  46. {
  47. if ( TheBots != NULL )
  48. delete TheBots;
  49. TheBots = new CCSBotManager;
  50. }
  51. //--------------------------------------------------------------------------------------------------------------
  52. void RemoveBotControl( void )
  53. {
  54. if ( TheBots != NULL )
  55. delete TheBots;
  56. TheBots = NULL;
  57. }
  58. //--------------------------------------------------------------------------------------------------------------
  59. CBasePlayer* ClientPutInServerOverride_Bot( edict_t *pEdict, const char *playername )
  60. {
  61. CBasePlayer *pPlayer = TheBots->AllocateAndBindBotEntity( pEdict );
  62. if ( pPlayer )
  63. {
  64. pPlayer->SetPlayerName( playername );
  65. }
  66. ++g_nClientPutInServerOverrides;
  67. return pPlayer;
  68. }
  69. //--------------------------------------------------------------------------------------------------------------
  70. // Constructor
  71. CCSBotManager::CCSBotManager()
  72. {
  73. m_zoneCount = 0;
  74. SetLooseBomb( NULL );
  75. m_serverActive = false;
  76. m_isBombPlanted = false;
  77. m_bombDefuser = NULL;
  78. m_roundStartTimestamp = 0.0f;
  79. m_eventListenersEnabled = true;
  80. m_commonEventListeners.AddToTail( &m_PlayerFootstepEvent );
  81. m_commonEventListeners.AddToTail( &m_PlayerRadioEvent );
  82. m_commonEventListeners.AddToTail( &m_PlayerFallDamageEvent );
  83. m_commonEventListeners.AddToTail( &m_BombBeepEvent );
  84. m_commonEventListeners.AddToTail( &m_DoorMovingEvent );
  85. m_commonEventListeners.AddToTail( &m_BreakPropEvent );
  86. m_commonEventListeners.AddToTail( &m_BreakBreakableEvent );
  87. m_commonEventListeners.AddToTail( &m_WeaponFireEvent );
  88. m_commonEventListeners.AddToTail( &m_WeaponFireOnEmptyEvent );
  89. m_commonEventListeners.AddToTail( &m_WeaponReloadEvent );
  90. m_commonEventListeners.AddToTail( &m_WeaponZoomEvent );
  91. m_commonEventListeners.AddToTail( &m_BulletImpactEvent );
  92. m_commonEventListeners.AddToTail( &m_GrenadeBounceEvent );
  93. m_commonEventListeners.AddToTail( &m_NavBlockedEvent );
  94. TheBotPhrases = new BotPhraseManager;
  95. TheBotProfiles = new BotProfileManager;
  96. }
  97. //--------------------------------------------------------------------------------------------------------------
  98. /**
  99. * Invoked when a new round begins
  100. */
  101. void CCSBotManager::RestartRound( void )
  102. {
  103. // extend
  104. CBotManager::RestartRound();
  105. SetLooseBomb( NULL );
  106. m_isBombPlanted = false;
  107. m_earliestBombPlantTimestamp = gpGlobals->curtime + RandomFloat( 10.0f, 30.0f ); // 60
  108. m_bombDefuser = NULL;
  109. ResetRadioMessageTimestamps();
  110. m_lastSeenEnemyTimestamp = -9999.9f;
  111. m_roundStartTimestamp = gpGlobals->curtime + mp_freezetime.GetFloat();
  112. // randomly decide if defensive team wants to "rush" as a whole
  113. const float defenseRushChance = 33.3f; // 25.0f;
  114. m_isDefenseRushing = (RandomFloat( 0.0f, 100.0f ) <= defenseRushChance) ? true : false;
  115. TheBotPhrases->OnRoundRestart();
  116. m_isRoundOver = false;
  117. }
  118. //--------------------------------------------------------------------------------------------------------------
  119. void UTIL_DrawBox( Extent *extent, int lifetime, int red, int green, int blue )
  120. {
  121. int darkRed = red/2;
  122. int darkGreen = green/2;
  123. int darkBlue = blue/2;
  124. Vector v[8];
  125. v[0].x = extent->lo.x; v[0].y = extent->lo.y; v[0].z = extent->lo.z;
  126. v[1].x = extent->hi.x; v[1].y = extent->lo.y; v[1].z = extent->lo.z;
  127. v[2].x = extent->hi.x; v[2].y = extent->hi.y; v[2].z = extent->lo.z;
  128. v[3].x = extent->lo.x; v[3].y = extent->hi.y; v[3].z = extent->lo.z;
  129. v[4].x = extent->lo.x; v[4].y = extent->lo.y; v[4].z = extent->hi.z;
  130. v[5].x = extent->hi.x; v[5].y = extent->lo.y; v[5].z = extent->hi.z;
  131. v[6].x = extent->hi.x; v[6].y = extent->hi.y; v[6].z = extent->hi.z;
  132. v[7].x = extent->lo.x; v[7].y = extent->hi.y; v[7].z = extent->hi.z;
  133. static int edge[] =
  134. {
  135. 1, 2, 3, 4, -1,
  136. 5, 6, 7, 8, -5,
  137. 1, -5,
  138. 2, -6,
  139. 3, -7,
  140. 4, -8,
  141. 0
  142. };
  143. Vector from, to;
  144. bool restart = true;
  145. for( int i=0; edge[i] != 0; ++i )
  146. {
  147. if (restart)
  148. {
  149. to = v[ edge[i]-1 ];
  150. restart = false;
  151. continue;
  152. }
  153. from = to;
  154. int index = edge[i];
  155. if (index < 0)
  156. {
  157. restart = true;
  158. index = -index;
  159. }
  160. to = v[ index-1 ];
  161. NDebugOverlay::Line( from, to, darkRed, darkGreen, darkBlue, true, 0.1f );
  162. NDebugOverlay::Line( from, to, red, green, blue, false, 0.15f );
  163. }
  164. }
  165. //--------------------------------------------------------------------------------------------------------------
  166. void CCSBotManager::EnableEventListeners( bool enable )
  167. {
  168. if ( m_eventListenersEnabled == enable )
  169. {
  170. return;
  171. }
  172. m_eventListenersEnabled = enable;
  173. // enable/disable the most frequent event listeners, to improve performance when no bots are present.
  174. for ( int i=0; i<m_commonEventListeners.Count(); ++i )
  175. {
  176. if ( enable )
  177. {
  178. gameeventmanager->AddListener( m_commonEventListeners[i], m_commonEventListeners[i]->GetEventName(), true );
  179. }
  180. else
  181. {
  182. gameeventmanager->RemoveListener( m_commonEventListeners[i] );
  183. }
  184. }
  185. }
  186. //--------------------------------------------------------------------------------------------------------------
  187. /**
  188. * Called each frame
  189. */
  190. void CCSBotManager::StartFrame( void )
  191. {
  192. if ( !AreBotsAllowed() )
  193. {
  194. EnableEventListeners( false );
  195. return;
  196. }
  197. // EXTEND
  198. CBotManager::StartFrame();
  199. MaintainBotQuota();
  200. EnableEventListeners( UTIL_CSSBotsInGame() > 0 );
  201. // debug zone extent visualization
  202. if (cv_bot_debug.GetInt() == 5)
  203. {
  204. for( int z=0; z<m_zoneCount; ++z )
  205. {
  206. Zone *zone = &m_zone[z];
  207. if ( zone->m_isBlocked )
  208. {
  209. UTIL_DrawBox( &zone->m_extent, 1, 255, 0, 200 );
  210. }
  211. else
  212. {
  213. UTIL_DrawBox( &zone->m_extent, 1, 255, 100, 0 );
  214. }
  215. }
  216. }
  217. if (bot_show_occupy_time.GetBool())
  218. {
  219. DrawOccupyTime();
  220. }
  221. if (bot_show_battlefront.GetBool())
  222. {
  223. DrawBattlefront();
  224. }
  225. if ( m_checkTransientAreasTimer.IsElapsed() && !nav_edit.GetBool() )
  226. {
  227. CUtlVector< CNavArea * >& transientAreas = TheNavMesh->GetTransientAreas();
  228. for ( int i=0; i<transientAreas.Count(); ++i )
  229. {
  230. CNavArea *area = transientAreas[i];
  231. if ( area->GetAttributes() & NAV_MESH_TRANSIENT )
  232. {
  233. area->UpdateBlocked();
  234. }
  235. }
  236. m_checkTransientAreasTimer.Start( 2.0f );
  237. }
  238. }
  239. //--------------------------------------------------------------------------------------------------------------
  240. /**
  241. * Return true if the bot can use this weapon
  242. */
  243. bool CCSBotManager::IsWeaponUseable( const CWeaponCSBase *weapon ) const
  244. {
  245. if (weapon == NULL)
  246. return false;
  247. if (weapon->IsA( WEAPON_C4 ))
  248. return true;
  249. if ((!AllowShotguns() && weapon->IsKindOf( WEAPONTYPE_SHOTGUN )) ||
  250. (!AllowMachineGuns() && weapon->IsKindOf( WEAPONTYPE_MACHINEGUN )) ||
  251. (!AllowRifles() && weapon->IsKindOf( WEAPONTYPE_RIFLE )) ||
  252. (!AllowShotguns() && weapon->IsKindOf( WEAPONTYPE_SHOTGUN )) ||
  253. (!AllowSnipers() && weapon->IsKindOf( WEAPONTYPE_SNIPER_RIFLE )) ||
  254. (!AllowSubMachineGuns() && weapon->IsKindOf( WEAPONTYPE_SUBMACHINEGUN )) ||
  255. (!AllowPistols() && weapon->IsKindOf( WEAPONTYPE_PISTOL )) ||
  256. (!AllowGrenades() && weapon->IsKindOf( WEAPONTYPE_GRENADE )))
  257. {
  258. return false;
  259. }
  260. return true;
  261. }
  262. //--------------------------------------------------------------------------------------------------------------
  263. /**
  264. * Return true if this player is on "defense"
  265. */
  266. bool CCSBotManager::IsOnDefense( const CCSPlayer *player ) const
  267. {
  268. switch (GetScenario())
  269. {
  270. case SCENARIO_DEFUSE_BOMB:
  271. return (player->GetTeamNumber() == TEAM_CT);
  272. case SCENARIO_RESCUE_HOSTAGES:
  273. return (player->GetTeamNumber() == TEAM_TERRORIST);
  274. case SCENARIO_ESCORT_VIP:
  275. return (player->GetTeamNumber() == TEAM_TERRORIST);
  276. }
  277. return false;
  278. }
  279. //--------------------------------------------------------------------------------------------------------------
  280. /**
  281. * Return true if this player is on "offense"
  282. */
  283. bool CCSBotManager::IsOnOffense( const CCSPlayer *player ) const
  284. {
  285. return !IsOnDefense( player );
  286. }
  287. //--------------------------------------------------------------------------------------------------------------
  288. /**
  289. * Invoked when a map has just been loaded
  290. */
  291. void CCSBotManager::ServerActivate( void )
  292. {
  293. m_isMapDataLoaded = false;
  294. // load the database of bot radio chatter
  295. TheBotPhrases->Reset();
  296. TheBotPhrases->Initialize( "BotChatter.db", 0 );
  297. TheBotProfiles->Reset();
  298. TheBotProfiles->FindVoiceBankIndex( "BotChatter.db" ); // make sure default voice bank is first
  299. const char *filename;
  300. if ( false ) // g_engfuncs.pfnIsCareerMatch() )
  301. {
  302. filename = "MissionPacks/BotPackList.db";
  303. }
  304. else
  305. {
  306. filename = "BotPackList.db";
  307. }
  308. // read in the list of bot profile DBs
  309. FileHandle_t file = filesystem->Open( filename, "r" );
  310. if ( !file )
  311. {
  312. TheBotProfiles->Init( "BotProfile.db" );
  313. }
  314. else
  315. {
  316. int dataLength = filesystem->Size( filename );
  317. char *dataPointer = new char[ dataLength ];
  318. filesystem->Read( dataPointer, dataLength, file );
  319. filesystem->Close( file );
  320. const char *dataFile = SharedParse( dataPointer );
  321. const char *token;
  322. while ( dataFile )
  323. {
  324. token = SharedGetToken();
  325. char *clone = CloneString( token );
  326. TheBotProfiles->Init( clone );
  327. delete[] clone;
  328. dataFile = SharedParse( dataFile );
  329. }
  330. delete [] dataPointer;
  331. }
  332. // Now that we've parsed all the profiles, we have a list of the voice banks they're using.
  333. // Go back and parse the custom voice speakables.
  334. const BotProfileManager::VoiceBankList *voiceBanks = TheBotProfiles->GetVoiceBanks();
  335. for ( int i=1; i<voiceBanks->Count(); ++i )
  336. {
  337. TheBotPhrases->Initialize( (*voiceBanks)[i], i );
  338. }
  339. // tell the Navigation Mesh system what CS spawn points are named
  340. TheNavMesh->SetPlayerSpawnName( "info_player_terrorist" );
  341. ExtractScenarioData();
  342. RestartRound();
  343. TheBotPhrases->OnMapChange();
  344. m_serverActive = true;
  345. }
  346. void CCSBotManager::ServerDeactivate( void )
  347. {
  348. m_serverActive = false;
  349. }
  350. void CCSBotManager::ClientDisconnect( CBaseEntity *entity )
  351. {
  352. /*
  353. if ( FBitSet( entity->GetFlags(), FL_FAKECLIENT ) )
  354. {
  355. FREE_PRIVATE( entity );
  356. }
  357. */
  358. /*
  359. // make sure voice feedback is turned off
  360. CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pEntity );
  361. if ( pPlayer && pPlayer->IsBot() )
  362. {
  363. CCSBot *pBot = static_cast<CCSBot *>(pPlayer);
  364. if (pBot)
  365. {
  366. pBot->EndVoiceFeedback( true );
  367. }
  368. }
  369. */
  370. }
  371. //--------------------------------------------------------------------------------------------------------------
  372. /**
  373. * Parses out bot name/template/etc params from the current ConCommand
  374. */
  375. void BotArgumentsFromArgv( const CCommand &args, const char **name, CSWeaponType *weaponType, BotDifficultyType *difficulty, int *team = NULL, bool *all = NULL )
  376. {
  377. static char s_name[MAX_PLAYER_NAME_LENGTH];
  378. s_name[0] = 0;
  379. *name = s_name;
  380. *difficulty = NUM_DIFFICULTY_LEVELS;
  381. if ( team )
  382. {
  383. *team = TEAM_UNASSIGNED;
  384. }
  385. if ( all )
  386. {
  387. *all = false;
  388. }
  389. *weaponType = WEAPONTYPE_UNKNOWN;
  390. for ( int arg=1; arg<args.ArgC(); ++arg )
  391. {
  392. bool found = false;
  393. const char *token = args[arg];
  394. if ( all && FStrEq( token, "all" ) )
  395. {
  396. *all = true;
  397. found = true;
  398. }
  399. else if ( team && FStrEq( token, "t" ) )
  400. {
  401. *team = TEAM_TERRORIST;
  402. found = true;
  403. }
  404. else if ( team && FStrEq( token, "ct" ) )
  405. {
  406. *team = TEAM_CT;
  407. found = true;
  408. }
  409. for( int i=0; i<NUM_DIFFICULTY_LEVELS && !found; ++i )
  410. {
  411. if (!stricmp( BotDifficultyName[i], token ))
  412. {
  413. *difficulty = (BotDifficultyType)i;
  414. found = true;
  415. }
  416. }
  417. if ( !found )
  418. {
  419. *weaponType = WeaponClassFromString( token );
  420. if ( *weaponType != WEAPONTYPE_UNKNOWN )
  421. {
  422. found = true;
  423. }
  424. }
  425. if ( !found )
  426. {
  427. Q_strncpy( s_name, token, sizeof( s_name ) );
  428. }
  429. }
  430. }
  431. //--------------------------------------------------------------------------------------------------------------
  432. CON_COMMAND_F( bot_add, "bot_add <t|ct> <type> <difficulty> <name> - Adds a bot matching the given criteria.", FCVAR_GAMEDLL )
  433. {
  434. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  435. return;
  436. const char *name;
  437. BotDifficultyType difficulty;
  438. CSWeaponType weaponType;
  439. int team;
  440. BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team );
  441. TheCSBots()->BotAddCommand( team, FROM_CONSOLE, name, weaponType, difficulty );
  442. }
  443. //--------------------------------------------------------------------------------------------------------------
  444. CON_COMMAND_F( bot_add_t, "bot_add_t <type> <difficulty> <name> - Adds a terrorist bot matching the given criteria.", FCVAR_GAMEDLL )
  445. {
  446. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  447. return;
  448. const char *name;
  449. BotDifficultyType difficulty;
  450. CSWeaponType weaponType;
  451. BotArgumentsFromArgv( args, &name, &weaponType, &difficulty );
  452. TheCSBots()->BotAddCommand( TEAM_TERRORIST, FROM_CONSOLE, name, weaponType, difficulty );
  453. }
  454. //--------------------------------------------------------------------------------------------------------------
  455. CON_COMMAND_F( bot_add_ct, "bot_add_ct <type> <difficulty> <name> - Adds a Counter-Terrorist bot matching the given criteria.", FCVAR_GAMEDLL )
  456. {
  457. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  458. return;
  459. const char *name;
  460. BotDifficultyType difficulty;
  461. CSWeaponType weaponType;
  462. BotArgumentsFromArgv( args, &name, &weaponType, &difficulty );
  463. TheCSBots()->BotAddCommand( TEAM_CT, FROM_CONSOLE, name, weaponType, difficulty );
  464. }
  465. //--------------------------------------------------------------------------------------------------------------
  466. /**
  467. * Collects all bots matching the given criteria (player name, profile template name, difficulty, and team)
  468. */
  469. class CollectBots
  470. {
  471. public:
  472. CollectBots( const char *name, CSWeaponType weaponType, BotDifficultyType difficulty, int team )
  473. {
  474. m_name = name;
  475. m_difficulty = difficulty;
  476. m_team = team;
  477. m_weaponType = weaponType;
  478. }
  479. bool operator() ( CBasePlayer *player )
  480. {
  481. if ( !player->IsBot() )
  482. {
  483. return true;
  484. }
  485. CCSBot *bot = dynamic_cast< CCSBot * >(player);
  486. if ( !bot || !bot->GetProfile() )
  487. {
  488. return true;
  489. }
  490. if ( m_name && *m_name )
  491. {
  492. // accept based on name
  493. if ( FStrEq( m_name, bot->GetProfile()->GetName() ) )
  494. {
  495. m_bots.RemoveAll();
  496. m_bots.AddToTail( bot );
  497. return false;
  498. }
  499. // Reject based on profile template name
  500. if ( !bot->GetProfile()->InheritsFrom( m_name ) )
  501. {
  502. return true;
  503. }
  504. }
  505. // reject based on difficulty
  506. if ( m_difficulty != NUM_DIFFICULTY_LEVELS )
  507. {
  508. if ( !bot->GetProfile()->IsDifficulty( m_difficulty ) )
  509. {
  510. return true;
  511. }
  512. }
  513. // reject based on team
  514. if ( m_team == TEAM_CT || m_team == TEAM_TERRORIST )
  515. {
  516. if ( bot->GetTeamNumber() != m_team )
  517. {
  518. return true;
  519. }
  520. }
  521. // reject based on weapon preference
  522. if ( m_weaponType != WEAPONTYPE_UNKNOWN )
  523. {
  524. if ( !bot->GetProfile()->GetWeaponPreferenceCount() )
  525. {
  526. return true;
  527. }
  528. if ( m_weaponType != WeaponClassFromWeaponID( (CSWeaponID)bot->GetProfile()->GetWeaponPreference( 0 ) ) )
  529. {
  530. return true;
  531. }
  532. }
  533. // A match!
  534. m_bots.AddToTail( bot );
  535. return true;
  536. }
  537. CUtlVector< CCSBot * > m_bots;
  538. private:
  539. const char *m_name;
  540. CSWeaponType m_weaponType;
  541. BotDifficultyType m_difficulty;
  542. int m_team;
  543. };
  544. //--------------------------------------------------------------------------------------------------------------
  545. CON_COMMAND_F( bot_kill, "bot_kill <all> <t|ct> <type> <difficulty> <name> - Kills a specific bot, or all bots, matching the given criteria.", FCVAR_GAMEDLL )
  546. {
  547. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  548. return;
  549. const char *name;
  550. BotDifficultyType difficulty;
  551. CSWeaponType weaponType;
  552. int team;
  553. bool all;
  554. BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team, &all );
  555. if ( (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
  556. {
  557. all = true;
  558. }
  559. CollectBots collector( name, weaponType, difficulty, team );
  560. ForEachPlayer( collector );
  561. for ( int i=0; i<collector.m_bots.Count(); ++i )
  562. {
  563. CCSBot *bot = collector.m_bots[i];
  564. if ( !bot->IsAlive() )
  565. continue;
  566. bot->CommitSuicide();
  567. if ( !all )
  568. {
  569. return;
  570. }
  571. }
  572. }
  573. //--------------------------------------------------------------------------------------------------------------
  574. CON_COMMAND_F( bot_kick, "bot_kick <all> <t|ct> <type> <difficulty> <name> - Kicks a specific bot, or all bots, matching the given criteria.", FCVAR_GAMEDLL )
  575. {
  576. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  577. return;
  578. const char *name;
  579. BotDifficultyType difficulty;
  580. CSWeaponType weaponType;
  581. int team;
  582. bool all;
  583. BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team, &all );
  584. if ( (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
  585. {
  586. all = true;
  587. }
  588. CollectBots collector( name, weaponType, difficulty, team );
  589. ForEachPlayer( collector );
  590. for ( int i=0; i<collector.m_bots.Count(); ++i )
  591. {
  592. CCSBot *bot = collector.m_bots[i];
  593. engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", bot->GetPlayerName() ) );
  594. if ( !all )
  595. {
  596. // adjust bot quota so kicked bot is not immediately added back in
  597. int newQuota = cv_bot_quota.GetInt() - 1;
  598. cv_bot_quota.SetValue( clamp( newQuota, 0, cv_bot_quota.GetInt() ) );
  599. return;
  600. }
  601. }
  602. // adjust bot quota so kicked bot is not immediately added back in
  603. if ( all && (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
  604. {
  605. cv_bot_quota.SetValue( 0 );
  606. }
  607. else
  608. {
  609. int newQuota = cv_bot_quota.GetInt() - collector.m_bots.Count();
  610. cv_bot_quota.SetValue( clamp( newQuota, 0, cv_bot_quota.GetInt() ) );
  611. }
  612. }
  613. //--------------------------------------------------------------------------------------------------------------
  614. CON_COMMAND_F( bot_knives_only, "Restricts the bots to only using knives", FCVAR_GAMEDLL )
  615. {
  616. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  617. return;
  618. cv_bot_allow_pistols.SetValue( 0 );
  619. cv_bot_allow_shotguns.SetValue( 0 );
  620. cv_bot_allow_sub_machine_guns.SetValue( 0 );
  621. cv_bot_allow_rifles.SetValue( 0 );
  622. cv_bot_allow_machine_guns.SetValue( 0 );
  623. cv_bot_allow_grenades.SetValue( 0 );
  624. cv_bot_allow_snipers.SetValue( 0 );
  625. #ifdef CS_SHIELD_ENABLED
  626. cv_bot_allow_shield.SetValue( 0 );
  627. #endif // CS_SHIELD_ENABLED
  628. }
  629. //--------------------------------------------------------------------------------------------------------------
  630. CON_COMMAND_F( bot_pistols_only, "Restricts the bots to only using pistols", FCVAR_GAMEDLL )
  631. {
  632. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  633. return;
  634. cv_bot_allow_pistols.SetValue( 1 );
  635. cv_bot_allow_shotguns.SetValue( 0 );
  636. cv_bot_allow_sub_machine_guns.SetValue( 0 );
  637. cv_bot_allow_rifles.SetValue( 0 );
  638. cv_bot_allow_machine_guns.SetValue( 0 );
  639. cv_bot_allow_grenades.SetValue( 0 );
  640. cv_bot_allow_snipers.SetValue( 0 );
  641. #ifdef CS_SHIELD_ENABLED
  642. cv_bot_allow_shield.SetValue( 0 );
  643. #endif // CS_SHIELD_ENABLED
  644. }
  645. //--------------------------------------------------------------------------------------------------------------
  646. CON_COMMAND_F( bot_snipers_only, "Restricts the bots to only using sniper rifles", FCVAR_GAMEDLL )
  647. {
  648. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  649. return;
  650. cv_bot_allow_pistols.SetValue( 0 );
  651. cv_bot_allow_shotguns.SetValue( 0 );
  652. cv_bot_allow_sub_machine_guns.SetValue( 0 );
  653. cv_bot_allow_rifles.SetValue( 0 );
  654. cv_bot_allow_machine_guns.SetValue( 0 );
  655. cv_bot_allow_grenades.SetValue( 0 );
  656. cv_bot_allow_snipers.SetValue( 1 );
  657. #ifdef CS_SHIELD_ENABLED
  658. cv_bot_allow_shield.SetValue( 0 );
  659. #endif // CS_SHIELD_ENABLED
  660. }
  661. //--------------------------------------------------------------------------------------------------------------
  662. CON_COMMAND_F( bot_all_weapons, "Allows the bots to use all weapons", FCVAR_GAMEDLL )
  663. {
  664. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  665. return;
  666. cv_bot_allow_pistols.SetValue( 1 );
  667. cv_bot_allow_shotguns.SetValue( 1 );
  668. cv_bot_allow_sub_machine_guns.SetValue( 1 );
  669. cv_bot_allow_rifles.SetValue( 1 );
  670. cv_bot_allow_machine_guns.SetValue( 1 );
  671. cv_bot_allow_grenades.SetValue( 1 );
  672. cv_bot_allow_snipers.SetValue( 1 );
  673. #ifdef CS_SHIELD_ENABLED
  674. cv_bot_allow_shield.SetValue( 1 );
  675. #endif // CS_SHIELD_ENABLED
  676. }
  677. //--------------------------------------------------------------------------------------------------------------
  678. CON_COMMAND_F( bot_goto_mark, "Sends a bot to the selected nav area (useful for testing navigation meshes)", FCVAR_GAMEDLL | FCVAR_CHEAT )
  679. {
  680. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  681. return;
  682. // tell the first bot we find to go to our marked area
  683. CNavArea *area = TheNavMesh->GetMarkedArea();
  684. if (area)
  685. {
  686. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  687. {
  688. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  689. if (player == NULL)
  690. continue;
  691. if (player->IsBot())
  692. {
  693. CCSBot *bot = dynamic_cast<CCSBot *>( player );
  694. if ( bot )
  695. {
  696. bot->MoveTo( area->GetCenter(), FASTEST_ROUTE );
  697. }
  698. break;
  699. }
  700. }
  701. }
  702. }
  703. //--------------------------------------------------------------------------------------------------------------
  704. #if 0
  705. CON_COMMAND_F( bot_memory_usage, "Reports on the bots' memory usage", FCVAR_GAMEDLL )
  706. {
  707. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  708. return;
  709. Msg( "Memory usage:\n" );
  710. Msg( " %d bytes per bot\n", sizeof(CCSBot) );
  711. Msg( " %d Navigation Areas @ %d bytes each = %d bytes\n",
  712. TheNavMesh->GetNavAreaCount(),
  713. sizeof( CNavArea ),
  714. TheNavMesh->GetNavAreaCount() * sizeof( CNavArea ) );
  715. Msg( " %d Hiding Spots @ %d bytes each = %d bytes\n",
  716. TheHidingSpotList.Count(),
  717. sizeof( HidingSpot ),
  718. TheHidingSpotList.Count() * sizeof( HidingSpot ) );
  719. /*
  720. unsigned int encounterMem = 0;
  721. FOR_EACH_LL( TheNavAreaList, it )
  722. {
  723. CNavArea *area = TheNavAreaList[ it ];
  724. FOR_EACH_LL( area->m_spotEncounterList, it )
  725. {
  726. SpotEncounter *se = area->m_spotEncounterList[ it ];
  727. encounterMem += sizeof( SpotEncounter );
  728. encounterMem += se->spotList.Count() * sizeof( SpotOrder );
  729. }
  730. }
  731. Msg( " Encounter Spot data = %d bytes\n", encounterMem );
  732. */
  733. }
  734. #endif
  735. bool CCSBotManager::ServerCommand( const char *cmd )
  736. {
  737. return false;
  738. }
  739. bool CCSBotManager::ClientCommand( CBasePlayer *player, const CCommand &args )
  740. {
  741. return false;
  742. }
  743. /**
  744. * Process the "bot_add" console command
  745. */
  746. bool CCSBotManager::BotAddCommand( int team, bool isFromConsole, const char *profileName, CSWeaponType weaponType, BotDifficultyType difficulty )
  747. {
  748. if ( !TheNavMesh->IsLoaded() )
  749. {
  750. // If there isn't a Navigation Mesh in memory, create one
  751. if ( !TheNavMesh->IsGenerating() )
  752. {
  753. if ( !m_isMapDataLoaded )
  754. {
  755. TheNavMesh->BeginGeneration();
  756. m_isMapDataLoaded = true;
  757. }
  758. return false;
  759. }
  760. }
  761. // dont allow bots to join if the Navigation Mesh is being generated
  762. if (TheNavMesh->IsGenerating())
  763. return false;
  764. const BotProfile *profile = NULL;
  765. if ( !isFromConsole )
  766. {
  767. profileName = NULL;
  768. difficulty = GetDifficultyLevel();
  769. }
  770. else
  771. {
  772. if ( difficulty == NUM_DIFFICULTY_LEVELS )
  773. {
  774. difficulty = GetDifficultyLevel();
  775. }
  776. // if team not specified, check bot_join_team cvar for preference
  777. if (team == TEAM_UNASSIGNED)
  778. {
  779. if (!stricmp( cv_bot_join_team.GetString(), "T" ))
  780. team = TEAM_TERRORIST;
  781. else if (!stricmp( cv_bot_join_team.GetString(), "CT" ))
  782. team = TEAM_CT;
  783. else
  784. team = CSGameRules()->SelectDefaultTeam();
  785. }
  786. }
  787. if ( profileName && *profileName )
  788. {
  789. // in career, ignore humans, since we want to add anyway
  790. bool ignoreHumans = CSGameRules()->IsCareer();
  791. if (UTIL_IsNameTaken( profileName, ignoreHumans ))
  792. {
  793. if ( isFromConsole )
  794. {
  795. Msg( "Error - %s is already in the game.\n", profileName );
  796. }
  797. return true;
  798. }
  799. // try to add a bot by name
  800. profile = TheBotProfiles->GetProfile( profileName, team );
  801. if ( !profile )
  802. {
  803. // try to add a bot by template
  804. profile = TheBotProfiles->GetProfileMatchingTemplate( profileName, team, difficulty );
  805. if ( !profile )
  806. {
  807. if ( isFromConsole )
  808. {
  809. Msg( "Error - no profile for '%s' exists.\n", profileName );
  810. }
  811. return true;
  812. }
  813. }
  814. }
  815. else
  816. {
  817. // if team not specified, check bot_join_team cvar for preference
  818. if (team == TEAM_UNASSIGNED)
  819. {
  820. if (!stricmp( cv_bot_join_team.GetString(), "T" ))
  821. team = TEAM_TERRORIST;
  822. else if (!stricmp( cv_bot_join_team.GetString(), "CT" ))
  823. team = TEAM_CT;
  824. else
  825. team = CSGameRules()->SelectDefaultTeam();
  826. }
  827. profile = TheBotProfiles->GetRandomProfile( difficulty, team, weaponType );
  828. if (profile == NULL)
  829. {
  830. if ( isFromConsole )
  831. {
  832. Msg( "All bot profiles at this difficulty level are in use.\n" );
  833. }
  834. return true;
  835. }
  836. }
  837. if (team == TEAM_UNASSIGNED || team == TEAM_SPECTATOR)
  838. {
  839. if ( isFromConsole )
  840. {
  841. Msg( "Could not add bot to the game: The game is full\n" );
  842. }
  843. return false;
  844. }
  845. if (CSGameRules()->TeamFull( team ))
  846. {
  847. if ( isFromConsole )
  848. {
  849. Msg( "Could not add bot to the game: Team is full\n" );
  850. }
  851. return false;
  852. }
  853. if (CSGameRules()->TeamStacked( team, TEAM_UNASSIGNED ))
  854. {
  855. if ( isFromConsole )
  856. {
  857. Msg( "Could not add bot to the game: Team is stacked (to disable this check, set mp_autoteambalance to zero, increase mp_limitteams, and restart the round).\n" );
  858. }
  859. return false;
  860. }
  861. // create the actual bot
  862. CCSBot *bot = CreateBot<CCSBot>( profile, team );
  863. if (bot == NULL)
  864. {
  865. if ( isFromConsole )
  866. {
  867. Msg( "Error: CreateBot() failed.\n" );
  868. }
  869. return false;
  870. }
  871. if (isFromConsole)
  872. {
  873. // increase the bot quota to account for manually added bot
  874. cv_bot_quota.SetValue( cv_bot_quota.GetInt() + 1 );
  875. }
  876. return true;
  877. }
  878. int UTIL_CSSBotsInGame()
  879. {
  880. int count = 0;
  881. for (int i = 1; i <= gpGlobals->maxClients; ++i )
  882. {
  883. CCSBot *player = dynamic_cast<CCSBot *>(UTIL_PlayerByIndex( i ));
  884. if ( player == NULL )
  885. continue;
  886. count++;
  887. }
  888. return count;
  889. }
  890. bool UTIL_CSSKickBotFromTeam( int kickTeam )
  891. {
  892. int i;
  893. // try to kick a dead bot first
  894. for ( i = 1; i <= gpGlobals->maxClients; ++i )
  895. {
  896. CCSBot *player = dynamic_cast<CCSBot *>( UTIL_PlayerByIndex( i ) );
  897. if (player == NULL)
  898. continue;
  899. if (!player->IsAlive() && player->GetTeamNumber() == kickTeam)
  900. {
  901. // its a bot on the right team - kick it
  902. engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
  903. return true;
  904. }
  905. }
  906. // no dead bots, kick any bot on the given team
  907. for ( i = 1; i <= gpGlobals->maxClients; ++i )
  908. {
  909. CCSBot *player = dynamic_cast<CCSBot *>( UTIL_PlayerByIndex( i ) );
  910. if (player == NULL)
  911. continue;
  912. if (player->GetTeamNumber() == kickTeam)
  913. {
  914. // its a bot on the right team - kick it
  915. engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
  916. return true;
  917. }
  918. }
  919. return false;
  920. }
  921. //--------------------------------------------------------------------------------------------------------------
  922. /**
  923. * Keep a minimum quota of bots in the game
  924. */
  925. void CCSBotManager::MaintainBotQuota( void )
  926. {
  927. if ( !AreBotsAllowed() )
  928. return;
  929. if (TheNavMesh->IsGenerating())
  930. return;
  931. int totalHumansInGame = UTIL_HumansInGame();
  932. int humanPlayersInGame = UTIL_HumansInGame( IGNORE_SPECTATORS );
  933. // don't add bots until local player has been registered, to make sure he's player ID #1
  934. if (!engine->IsDedicatedServer() && totalHumansInGame == 0)
  935. return;
  936. // new players can't spawn immediately after the round has been going for some time
  937. if ( !CSGameRules() || !TheCSBots() )
  938. {
  939. return;
  940. }
  941. int desiredBotCount = cv_bot_quota.GetInt();
  942. int botsInGame = UTIL_CSSBotsInGame();
  943. /// isRoundInProgress is true if the round has progressed far enough that new players will join as dead.
  944. bool isRoundInProgress = CSGameRules()->m_bFirstConnected &&
  945. !TheCSBots()->IsRoundOver() &&
  946. ( CSGameRules()->GetRoundElapsedTime() >= 20.0f );
  947. if ( FStrEq( cv_bot_quota_mode.GetString(), "fill" ) )
  948. {
  949. // If bot_quota_mode is 'fill', we want the number of bots and humans together to equal bot_quota
  950. // unless the round is already in progress, in which case we play with what we've been dealt
  951. if ( !isRoundInProgress )
  952. {
  953. desiredBotCount = MAX( 0, desiredBotCount - humanPlayersInGame );
  954. }
  955. else
  956. {
  957. desiredBotCount = botsInGame;
  958. }
  959. }
  960. else if ( FStrEq( cv_bot_quota_mode.GetString(), "match" ) )
  961. {
  962. // If bot_quota_mode is 'match', we want the number of bots to be bot_quota * total humans
  963. // unless the round is already in progress, in which case we play with what we've been dealt
  964. if ( !isRoundInProgress )
  965. {
  966. desiredBotCount = (int)MAX( 0, cv_bot_quota.GetFloat() * humanPlayersInGame );
  967. }
  968. else
  969. {
  970. desiredBotCount = botsInGame;
  971. }
  972. }
  973. // wait for a player to join, if necessary
  974. if (cv_bot_join_after_player.GetBool())
  975. {
  976. if (humanPlayersInGame == 0)
  977. desiredBotCount = 0;
  978. }
  979. // wait until the map has been loaded for a bit, to allow players to transition across
  980. // the transition without missing the pistol round
  981. if ( bot_join_delay.GetInt() > CSGameRules()->GetMapElapsedTime() )
  982. {
  983. desiredBotCount = 0;
  984. }
  985. // if bots will auto-vacate, we need to keep one slot open to allow players to join
  986. if (cv_bot_auto_vacate.GetBool())
  987. desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - (humanPlayersInGame + 1) );
  988. else
  989. desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - humanPlayersInGame );
  990. // Try to balance teams, if we are in the first 20 seconds of a round and bots can join either team.
  991. if ( botsInGame > 0 && desiredBotCount == botsInGame && CSGameRules()->m_bFirstConnected )
  992. {
  993. if ( CSGameRules()->GetRoundElapsedTime() < 20.0f ) // new bots can still spawn during this time
  994. {
  995. if ( mp_autoteambalance.GetBool() )
  996. {
  997. int numAliveTerrorist;
  998. int numAliveCT;
  999. int numDeadTerrorist;
  1000. int numDeadCT;
  1001. CSGameRules()->InitializePlayerCounts( numAliveTerrorist, numAliveCT, numDeadTerrorist, numDeadCT );
  1002. if ( !FStrEq( cv_bot_join_team.GetString(), "T" ) &&
  1003. !FStrEq( cv_bot_join_team.GetString(), "CT" ) )
  1004. {
  1005. if ( numAliveTerrorist > CSGameRules()->m_iNumCT + 1 )
  1006. {
  1007. if ( UTIL_KickBotFromTeam( TEAM_TERRORIST ) )
  1008. return;
  1009. }
  1010. else if ( numAliveCT > CSGameRules()->m_iNumTerrorist + 1 )
  1011. {
  1012. if ( UTIL_KickBotFromTeam( TEAM_CT ) )
  1013. return;
  1014. }
  1015. }
  1016. }
  1017. }
  1018. }
  1019. // add bots if necessary
  1020. if (desiredBotCount > botsInGame)
  1021. {
  1022. // don't try to add a bot if all teams are full
  1023. if (!CSGameRules()->TeamFull( TEAM_TERRORIST ) || !CSGameRules()->TeamFull( TEAM_CT ))
  1024. TheCSBots()->BotAddCommand( TEAM_UNASSIGNED );
  1025. }
  1026. else if (desiredBotCount < botsInGame)
  1027. {
  1028. // kick a bot to maintain quota
  1029. // first remove any unassigned bots
  1030. if (UTIL_CSSKickBotFromTeam( TEAM_UNASSIGNED ))
  1031. return;
  1032. int kickTeam;
  1033. // remove from the team that has more players
  1034. if (CSGameRules()->m_iNumTerrorist > CSGameRules()->m_iNumCT)
  1035. {
  1036. kickTeam = TEAM_TERRORIST;
  1037. }
  1038. else if (CSGameRules()->m_iNumTerrorist < CSGameRules()->m_iNumCT)
  1039. {
  1040. kickTeam = TEAM_CT;
  1041. }
  1042. // remove from the team that's winning
  1043. else if (CSGameRules()->m_iNumTerroristWins > CSGameRules()->m_iNumCTWins)
  1044. {
  1045. kickTeam = TEAM_TERRORIST;
  1046. }
  1047. else if (CSGameRules()->m_iNumCTWins > CSGameRules()->m_iNumTerroristWins)
  1048. {
  1049. kickTeam = TEAM_CT;
  1050. }
  1051. else
  1052. {
  1053. // teams and scores are equal, pick a team at random
  1054. kickTeam = (RandomInt( 0, 1 ) == 0) ? TEAM_CT : TEAM_TERRORIST;
  1055. }
  1056. // attempt to kick a bot from the given team
  1057. if (UTIL_CSSKickBotFromTeam( kickTeam ))
  1058. return;
  1059. // if there were no bots on the team, kick a bot from the other team
  1060. if (kickTeam == TEAM_TERRORIST)
  1061. UTIL_CSSKickBotFromTeam( TEAM_CT );
  1062. else
  1063. UTIL_CSSKickBotFromTeam( TEAM_TERRORIST );
  1064. }
  1065. }
  1066. //--------------------------------------------------------------------------------------------------------------
  1067. /**
  1068. * Collect all nav areas that overlap the given zone
  1069. */
  1070. class CollectOverlappingAreas
  1071. {
  1072. public:
  1073. CollectOverlappingAreas( CCSBotManager::Zone *zone )
  1074. {
  1075. m_zone = zone;
  1076. zone->m_areaCount = 0;
  1077. }
  1078. bool operator() ( CNavArea *area )
  1079. {
  1080. Extent areaExtent;
  1081. area->GetExtent(&areaExtent);
  1082. if (areaExtent.hi.x >= m_zone->m_extent.lo.x && areaExtent.lo.x <= m_zone->m_extent.hi.x &&
  1083. areaExtent.hi.y >= m_zone->m_extent.lo.y && areaExtent.lo.y <= m_zone->m_extent.hi.y &&
  1084. areaExtent.hi.z >= m_zone->m_extent.lo.z && areaExtent.lo.z <= m_zone->m_extent.hi.z)
  1085. {
  1086. // area overlaps m_zone
  1087. m_zone->m_area[ m_zone->m_areaCount++ ] = area;
  1088. if (m_zone->m_areaCount == CCSBotManager::MAX_ZONE_NAV_AREAS)
  1089. {
  1090. return false;
  1091. }
  1092. }
  1093. return true;
  1094. }
  1095. private:
  1096. CCSBotManager::Zone *m_zone;
  1097. };
  1098. //--------------------------------------------------------------------------------------------------------------
  1099. /**
  1100. * Search the map entities to determine the game scenario and define important zones.
  1101. */
  1102. void CCSBotManager::ExtractScenarioData( void )
  1103. {
  1104. if (!TheNavMesh->IsLoaded())
  1105. return;
  1106. m_zoneCount = 0;
  1107. m_gameScenario = SCENARIO_DEATHMATCH;
  1108. //
  1109. // Search all entities in the map and set the game type and
  1110. // store all zones (bomb target, etc).
  1111. //
  1112. CBaseEntity *entity;
  1113. int i;
  1114. for( i=1; i<gpGlobals->maxEntities; ++i )
  1115. {
  1116. entity = CBaseEntity::Instance( engine->PEntityOfEntIndex( i ) );
  1117. if (entity == NULL)
  1118. continue;
  1119. bool found = false;
  1120. bool isLegacy = false;
  1121. if (FClassnameIs( entity, "func_bomb_target" ))
  1122. {
  1123. m_gameScenario = SCENARIO_DEFUSE_BOMB;
  1124. found = true;
  1125. isLegacy = false;
  1126. }
  1127. else if (FClassnameIs( entity, "info_bomb_target" ))
  1128. {
  1129. m_gameScenario = SCENARIO_DEFUSE_BOMB;
  1130. found = true;
  1131. isLegacy = true;
  1132. }
  1133. else if (FClassnameIs( entity, "func_hostage_rescue" ))
  1134. {
  1135. m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
  1136. found = true;
  1137. isLegacy = false;
  1138. }
  1139. else if (FClassnameIs( entity, "info_hostage_rescue" ))
  1140. {
  1141. m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
  1142. found = true;
  1143. isLegacy = true;
  1144. }
  1145. else if (FClassnameIs( entity, "hostage_entity" ))
  1146. {
  1147. // some very old maps (ie: cs_assault) use info_player_start
  1148. // as rescue zones, so set the scenario if there are hostages
  1149. // in the map
  1150. m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
  1151. }
  1152. else if (FClassnameIs( entity, "func_vip_safetyzone" ))
  1153. {
  1154. m_gameScenario = SCENARIO_ESCORT_VIP;
  1155. found = true;
  1156. isLegacy = false;
  1157. }
  1158. if (found)
  1159. {
  1160. if (m_zoneCount < MAX_ZONES)
  1161. {
  1162. Vector absmin, absmax;
  1163. entity->CollisionProp()->WorldSpaceAABB( &absmin, &absmax );
  1164. m_zone[ m_zoneCount ].m_isBlocked = false;
  1165. m_zone[ m_zoneCount ].m_center = (isLegacy) ? entity->GetAbsOrigin() : (absmin + absmax)/2.0f;
  1166. m_zone[ m_zoneCount ].m_isLegacy = isLegacy;
  1167. m_zone[ m_zoneCount ].m_index = m_zoneCount;
  1168. m_zone[ m_zoneCount++ ].m_entity = entity;
  1169. }
  1170. else
  1171. Msg( "Warning: Too many zones, some will be ignored.\n" );
  1172. }
  1173. }
  1174. //
  1175. // If there are no zones and the scenario is hostage rescue,
  1176. // use the info_player_start entities as rescue zones.
  1177. //
  1178. if (m_zoneCount == 0 && m_gameScenario == SCENARIO_RESCUE_HOSTAGES)
  1179. {
  1180. for( entity = gEntList.FindEntityByClassname( NULL, "info_player_start" );
  1181. entity && !FNullEnt( entity->edict() );
  1182. entity = gEntList.FindEntityByClassname( entity, "info_player_start" ) )
  1183. {
  1184. if (m_zoneCount < MAX_ZONES)
  1185. {
  1186. m_zone[ m_zoneCount ].m_isBlocked = false;
  1187. m_zone[ m_zoneCount ].m_center = entity->GetAbsOrigin();
  1188. m_zone[ m_zoneCount ].m_isLegacy = true;
  1189. m_zone[ m_zoneCount ].m_index = m_zoneCount;
  1190. m_zone[ m_zoneCount++ ].m_entity = entity;
  1191. }
  1192. else
  1193. {
  1194. Msg( "Warning: Too many zones, some will be ignored.\n" );
  1195. }
  1196. }
  1197. }
  1198. //
  1199. // Collect nav areas that overlap each zone
  1200. //
  1201. for( i=0; i<m_zoneCount; ++i )
  1202. {
  1203. Zone *zone = &m_zone[i];
  1204. if (zone->m_isLegacy)
  1205. {
  1206. const float legacyRange = 256.0f;
  1207. zone->m_extent.lo.x = zone->m_center.x - legacyRange;
  1208. zone->m_extent.lo.y = zone->m_center.y - legacyRange;
  1209. zone->m_extent.lo.z = zone->m_center.z - legacyRange;
  1210. zone->m_extent.hi.x = zone->m_center.x + legacyRange;
  1211. zone->m_extent.hi.y = zone->m_center.y + legacyRange;
  1212. zone->m_extent.hi.z = zone->m_center.z + legacyRange;
  1213. }
  1214. else
  1215. {
  1216. Vector absmin, absmax;
  1217. zone->m_entity->CollisionProp()->WorldSpaceAABB( &absmin, &absmax );
  1218. zone->m_extent.lo = absmin;
  1219. zone->m_extent.hi = absmax;
  1220. }
  1221. // ensure Z overlap
  1222. const float zFudge = 50.0f;
  1223. zone->m_extent.lo.z -= zFudge;
  1224. zone->m_extent.hi.z += zFudge;
  1225. // build a list of nav areas that overlap this zone
  1226. CollectOverlappingAreas collector( zone );
  1227. TheNavMesh->ForAllAreas( collector );
  1228. }
  1229. }
  1230. //--------------------------------------------------------------------------------------------------------------
  1231. /**
  1232. * Return the zone that contains the given position
  1233. */
  1234. const CCSBotManager::Zone *CCSBotManager::GetZone( const Vector &pos ) const
  1235. {
  1236. for( int z=0; z<m_zoneCount; ++z )
  1237. {
  1238. if (m_zone[z].m_extent.Contains( pos ))
  1239. {
  1240. return &m_zone[z];
  1241. }
  1242. }
  1243. return NULL;
  1244. }
  1245. //--------------------------------------------------------------------------------------------------------------
  1246. /**
  1247. * Return the closest zone to the given position
  1248. */
  1249. const CCSBotManager::Zone *CCSBotManager::GetClosestZone( const Vector &pos ) const
  1250. {
  1251. const Zone *close = NULL;
  1252. float closeRangeSq = 999999999.9f;
  1253. for( int z=0; z<m_zoneCount; ++z )
  1254. {
  1255. if ( m_zone[z].m_isBlocked )
  1256. continue;
  1257. float rangeSq = (m_zone[z].m_center - pos).LengthSqr();
  1258. if (rangeSq < closeRangeSq)
  1259. {
  1260. closeRangeSq = rangeSq;
  1261. close = &m_zone[z];
  1262. }
  1263. }
  1264. return close;
  1265. }
  1266. //--------------------------------------------------------------------------------------------------------------
  1267. /**
  1268. * Return a random position inside the given zone
  1269. */
  1270. const Vector *CCSBotManager::GetRandomPositionInZone( const Zone *zone ) const
  1271. {
  1272. static Vector pos;
  1273. if (zone == NULL)
  1274. return NULL;
  1275. if (zone->m_areaCount == 0)
  1276. return NULL;
  1277. // pick a random overlapping area
  1278. CNavArea *area = GetRandomAreaInZone(zone);
  1279. // pick a location inside both the nav area and the zone
  1280. /// @todo Randomize this
  1281. if (zone->m_isLegacy)
  1282. {
  1283. /// @todo It is possible that the radius might not overlap this area at all...
  1284. area->GetClosestPointOnArea( zone->m_center, &pos );
  1285. }
  1286. else
  1287. {
  1288. Extent areaExtent;
  1289. area->GetExtent(&areaExtent);
  1290. Extent overlap;
  1291. overlap.lo.x = MAX( areaExtent.lo.x, zone->m_extent.lo.x );
  1292. overlap.lo.y = MAX( areaExtent.lo.y, zone->m_extent.lo.y );
  1293. overlap.hi.x = MIN( areaExtent.hi.x, zone->m_extent.hi.x );
  1294. overlap.hi.y = MIN( areaExtent.hi.y, zone->m_extent.hi.y );
  1295. pos.x = (overlap.lo.x + overlap.hi.x)/2.0f;
  1296. pos.y = (overlap.lo.y + overlap.hi.y)/2.0f;
  1297. pos.z = area->GetZ( pos );
  1298. }
  1299. return &pos;
  1300. }
  1301. //--------------------------------------------------------------------------------------------------------------
  1302. /**
  1303. * Return a random area inside the given zone
  1304. */
  1305. CNavArea *CCSBotManager::GetRandomAreaInZone( const Zone *zone ) const
  1306. {
  1307. int areaCount = zone->m_areaCount;
  1308. if( areaCount == 0 )
  1309. {
  1310. assert( false && "CCSBotManager::GetRandomAreaInZone: No areas for this zone" );
  1311. return NULL;
  1312. }
  1313. // Random, but weighted. Jump areas score zero, since you aren't ever meant to stop on one of those.
  1314. // Avoid areas score 1 to a normal area's 20 because pathfinding treats Avoid as a 20x penalty.
  1315. int totalWeight = 0;
  1316. for( int areaIndex = 0; areaIndex < areaCount; areaIndex++ )
  1317. {
  1318. CNavArea *currentArea = zone->m_area[areaIndex];
  1319. if( currentArea->GetAttributes() & NAV_MESH_JUMP )
  1320. totalWeight += 0;
  1321. else if( currentArea->GetAttributes() & NAV_MESH_AVOID )
  1322. totalWeight += 1;
  1323. else
  1324. totalWeight += 20;
  1325. }
  1326. if( totalWeight == 0 )
  1327. {
  1328. assert( false && "CCSBotManager::GetRandomAreaInZone: No real areas for this zone" );
  1329. return NULL;
  1330. }
  1331. int randomPick = RandomInt( 1, totalWeight );
  1332. for( int areaIndex = 0; areaIndex < areaCount; areaIndex++ )
  1333. {
  1334. CNavArea *currentArea = zone->m_area[areaIndex];
  1335. if( currentArea->GetAttributes() & NAV_MESH_JUMP )
  1336. randomPick -= 0;
  1337. else if( currentArea->GetAttributes() & NAV_MESH_AVOID )
  1338. randomPick -= 1;
  1339. else
  1340. randomPick -= 20;
  1341. if( randomPick <= 0 )
  1342. return currentArea;
  1343. }
  1344. // Won't ever get here, but the compiler will cry without it.
  1345. return zone->m_area[0];
  1346. }
  1347. //--------------------------------------------------------------------------------------------------------------
  1348. void CCSBotManager::OnServerShutdown( IGameEvent *event )
  1349. {
  1350. if ( !engine->IsDedicatedServer() )
  1351. {
  1352. // Since we're a listenserver, save some config info for the next time we start up
  1353. static const char *botVars[] =
  1354. {
  1355. "bot_quota",
  1356. "bot_difficulty",
  1357. "bot_chatter",
  1358. "bot_prefix",
  1359. "bot_join_team",
  1360. "bot_defer_to_human",
  1361. #ifdef CS_SHIELD_ENABLED
  1362. "bot_allow_shield",
  1363. #endif // CS_SHIELD_ENABLED
  1364. "bot_join_after_player",
  1365. "bot_allow_rogues",
  1366. "bot_allow_pistols",
  1367. "bot_allow_shotguns",
  1368. "bot_allow_sub_machine_guns",
  1369. "bot_allow_machine_guns",
  1370. "bot_allow_rifles",
  1371. "bot_allow_snipers",
  1372. "bot_allow_grenades"
  1373. };
  1374. KeyValues *data = new KeyValues( "ServerConfig" );
  1375. // load the config data
  1376. if (data)
  1377. {
  1378. data->LoadFromFile( filesystem, "ServerConfig.vdf", "GAME" );
  1379. for ( int i=0; i<sizeof(botVars)/sizeof(botVars[0]); ++i )
  1380. {
  1381. const char *varName = botVars[i];
  1382. if ( varName )
  1383. {
  1384. ConVar *var = cvar->FindVar( varName );
  1385. if ( var )
  1386. {
  1387. data->SetString( varName, var->GetString() );
  1388. }
  1389. }
  1390. }
  1391. data->SaveToFile( filesystem, "ServerConfig.vdf", "GAME" );
  1392. data->deleteThis();
  1393. }
  1394. return;
  1395. }
  1396. }
  1397. //--------------------------------------------------------------------------------------------------------------
  1398. void CCSBotManager::OnPlayerFootstep( IGameEvent *event )
  1399. {
  1400. CCSBOTMANAGER_ITERATE_BOTS( OnPlayerFootstep, event );
  1401. }
  1402. //--------------------------------------------------------------------------------------------------------------
  1403. void CCSBotManager::OnPlayerRadio( IGameEvent *event )
  1404. {
  1405. // if it's an Enemy Spotted radio, update our enemy spotted timestamp
  1406. if ( event->GetInt( "slot" ) == RADIO_ENEMY_SPOTTED )
  1407. {
  1408. // to have some idea of when a human Player has seen an enemy
  1409. SetLastSeenEnemyTimestamp();
  1410. }
  1411. CCSBOTMANAGER_ITERATE_BOTS( OnPlayerRadio, event );
  1412. }
  1413. //--------------------------------------------------------------------------------------------------------------
  1414. void CCSBotManager::OnPlayerDeath( IGameEvent *event )
  1415. {
  1416. CCSBOTMANAGER_ITERATE_BOTS( OnPlayerDeath, event );
  1417. }
  1418. //--------------------------------------------------------------------------------------------------------------
  1419. void CCSBotManager::OnPlayerFallDamage( IGameEvent *event )
  1420. {
  1421. CCSBOTMANAGER_ITERATE_BOTS( OnPlayerFallDamage, event );
  1422. }
  1423. //--------------------------------------------------------------------------------------------------------------
  1424. void CCSBotManager::OnBombPickedUp( IGameEvent *event )
  1425. {
  1426. // bomb no longer loose
  1427. SetLooseBomb( NULL );
  1428. CCSBOTMANAGER_ITERATE_BOTS( OnBombPickedUp, event );
  1429. }
  1430. //--------------------------------------------------------------------------------------------------------------
  1431. void CCSBotManager::OnBombPlanted( IGameEvent *event )
  1432. {
  1433. m_isBombPlanted = true;
  1434. m_bombPlantTimestamp = gpGlobals->curtime;
  1435. CCSBOTMANAGER_ITERATE_BOTS( OnBombPlanted, event );
  1436. }
  1437. //--------------------------------------------------------------------------------------------------------------
  1438. void CCSBotManager::OnBombBeep( IGameEvent *event )
  1439. {
  1440. CCSBOTMANAGER_ITERATE_BOTS( OnBombBeep, event );
  1441. }
  1442. //--------------------------------------------------------------------------------------------------------------
  1443. void CCSBotManager::OnBombDefuseBegin( IGameEvent *event )
  1444. {
  1445. m_bombDefuser = static_cast<CCSPlayer *>( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) );
  1446. CCSBOTMANAGER_ITERATE_BOTS( OnBombDefuseBegin, event );
  1447. }
  1448. //--------------------------------------------------------------------------------------------------------------
  1449. void CCSBotManager::OnBombDefused( IGameEvent *event )
  1450. {
  1451. m_isBombPlanted = false;
  1452. m_bombDefuser = NULL;
  1453. CCSBOTMANAGER_ITERATE_BOTS( OnBombDefused, event );
  1454. }
  1455. //--------------------------------------------------------------------------------------------------------------
  1456. void CCSBotManager::OnBombDefuseAbort( IGameEvent *event )
  1457. {
  1458. m_bombDefuser = NULL;
  1459. CCSBOTMANAGER_ITERATE_BOTS( OnBombDefuseAbort, event );
  1460. }
  1461. //--------------------------------------------------------------------------------------------------------------
  1462. void CCSBotManager::OnBombExploded( IGameEvent *event )
  1463. {
  1464. CCSBOTMANAGER_ITERATE_BOTS( OnBombExploded, event );
  1465. }
  1466. //--------------------------------------------------------------------------------------------------------------
  1467. void CCSBotManager::OnRoundEnd( IGameEvent *event )
  1468. {
  1469. m_isRoundOver = true;
  1470. CCSBOTMANAGER_ITERATE_BOTS( OnRoundEnd, event );
  1471. }
  1472. //--------------------------------------------------------------------------------------------------------------
  1473. void CCSBotManager::OnRoundStart( IGameEvent *event )
  1474. {
  1475. RestartRound();
  1476. CCSBOTMANAGER_ITERATE_BOTS( OnRoundStart, event );
  1477. }
  1478. //--------------------------------------------------------------------------------------------------------------
  1479. static CBaseEntity * SelectSpawnSpot( const char *pEntClassName )
  1480. {
  1481. CBaseEntity* pSpot = NULL;
  1482. // Find the next spawn spot.
  1483. pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
  1484. if ( pSpot == NULL ) // skip over the null point
  1485. pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
  1486. CBaseEntity *pFirstSpot = pSpot;
  1487. do
  1488. {
  1489. if ( pSpot )
  1490. {
  1491. // check if pSpot is valid
  1492. if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) )
  1493. {
  1494. pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
  1495. continue;
  1496. }
  1497. // if so, go to pSpot
  1498. return pSpot;
  1499. }
  1500. // increment pSpot
  1501. pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
  1502. } while ( pSpot != pFirstSpot ); // loop if we're not back to the start
  1503. return NULL;
  1504. }
  1505. //--------------------------------------------------------------------------------------------------------------
  1506. /**
  1507. * Pathfind from each zone to a spawn point to ensure it is valid. Assumes that every spawn can pathfind to
  1508. * every other spawn.
  1509. */
  1510. void CCSBotManager::CheckForBlockedZones( void )
  1511. {
  1512. CBaseEntity *pSpot = SelectSpawnSpot( "info_player_counterterrorist" );
  1513. if ( !pSpot )
  1514. pSpot = SelectSpawnSpot( "info_player_terrorist" );
  1515. if ( !pSpot )
  1516. return;
  1517. Vector spawnPos = pSpot->GetAbsOrigin();
  1518. CNavArea *spawnArea = TheNavMesh->GetNearestNavArea( spawnPos );
  1519. if ( !spawnArea )
  1520. return;
  1521. ShortestPathCost costFunc;
  1522. for( int i=0; i<m_zoneCount; ++i )
  1523. {
  1524. if (m_zone[i].m_areaCount == 0)
  1525. continue;
  1526. // just use the first overlapping nav area as a reasonable approximation
  1527. float dist = NavAreaTravelDistance( spawnArea, m_zone[i].m_area[0], costFunc );
  1528. m_zone[i].m_isBlocked = (dist < 0.0f );
  1529. if ( cv_bot_debug.GetInt() == 5 )
  1530. {
  1531. if ( m_zone[i].m_isBlocked )
  1532. DevMsg( "%.1f: Zone %d, area %d (%.0f %.0f %.0f) is blocked from spawn area %d (%.0f %.0f %.0f)\n",
  1533. gpGlobals->curtime, i, m_zone[i].m_area[0]->GetID(),
  1534. m_zone[i].m_area[0]->GetCenter().x, m_zone[i].m_area[0]->GetCenter().y, m_zone[i].m_area[0]->GetCenter().z,
  1535. spawnArea->GetID(),
  1536. spawnPos.x, spawnPos.y, spawnPos.z );
  1537. }
  1538. }
  1539. }
  1540. //--------------------------------------------------------------------------------------------------------------
  1541. void CCSBotManager::OnRoundFreezeEnd( IGameEvent *event )
  1542. {
  1543. bool reenableEvents = m_NavBlockedEvent.IsEnabled();
  1544. m_NavBlockedEvent.Enable( false ); // don't listen to nav_blocked events - there could be several, and we don't have bots pathing
  1545. CUtlVector< CNavArea * >& transientAreas = TheNavMesh->GetTransientAreas();
  1546. for ( int i=0; i<transientAreas.Count(); ++i )
  1547. {
  1548. CNavArea *area = transientAreas[i];
  1549. if ( area->GetAttributes() & NAV_MESH_TRANSIENT )
  1550. {
  1551. area->UpdateBlocked();
  1552. }
  1553. }
  1554. if ( reenableEvents )
  1555. {
  1556. m_NavBlockedEvent.Enable( true );
  1557. }
  1558. CheckForBlockedZones();
  1559. }
  1560. //--------------------------------------------------------------------------------------------------------------
  1561. void CCSBotManager::OnNavBlocked( IGameEvent *event )
  1562. {
  1563. CCSBOTMANAGER_ITERATE_BOTS( OnNavBlocked, event );
  1564. CheckForBlockedZones();
  1565. }
  1566. //--------------------------------------------------------------------------------------------------------------
  1567. void CCSBotManager::OnDoorMoving( IGameEvent *event )
  1568. {
  1569. CCSBOTMANAGER_ITERATE_BOTS( OnDoorMoving, event );
  1570. }
  1571. //--------------------------------------------------------------------------------------------------------------
  1572. /**
  1573. * Check all nav areas inside the breakable's extent to see if players would now fall through
  1574. */
  1575. class CheckAreasOverlappingBreakable
  1576. {
  1577. public:
  1578. CheckAreasOverlappingBreakable( CBaseEntity *breakable )
  1579. {
  1580. m_breakable = breakable;
  1581. ICollideable *collideable = breakable->GetCollideable();
  1582. collideable->WorldSpaceSurroundingBounds( &m_breakableExtent.lo, &m_breakableExtent.hi );
  1583. const float expand = 10.0f;
  1584. m_breakableExtent.lo += Vector( -expand, -expand, -expand );
  1585. m_breakableExtent.hi += Vector( expand, expand, expand );
  1586. }
  1587. bool operator() ( CNavArea *area )
  1588. {
  1589. Extent areaExtent;
  1590. area->GetExtent(&areaExtent);
  1591. if (areaExtent.hi.x >= m_breakableExtent.lo.x && areaExtent.lo.x <= m_breakableExtent.hi.x &&
  1592. areaExtent.hi.y >= m_breakableExtent.lo.y && areaExtent.lo.y <= m_breakableExtent.hi.y &&
  1593. areaExtent.hi.z >= m_breakableExtent.lo.z && areaExtent.lo.z <= m_breakableExtent.hi.z)
  1594. {
  1595. // area overlaps the breakable
  1596. area->CheckFloor( m_breakable );
  1597. }
  1598. return true;
  1599. }
  1600. private:
  1601. Extent m_breakableExtent;
  1602. CBaseEntity *m_breakable;
  1603. };
  1604. //--------------------------------------------------------------------------------------------------------------
  1605. void CCSBotManager::OnBreakBreakable( IGameEvent *event )
  1606. {
  1607. CheckAreasOverlappingBreakable collector( UTIL_EntityByIndex( event->GetInt( "entindex" ) ) );
  1608. TheNavMesh->ForAllAreas( collector );
  1609. CCSBOTMANAGER_ITERATE_BOTS( OnBreakBreakable, event );
  1610. }
  1611. //--------------------------------------------------------------------------------------------------------------
  1612. void CCSBotManager::OnBreakProp( IGameEvent *event )
  1613. {
  1614. CheckAreasOverlappingBreakable collector( UTIL_EntityByIndex( event->GetInt( "entindex" ) ) );
  1615. TheNavMesh->ForAllAreas( collector );
  1616. CCSBOTMANAGER_ITERATE_BOTS( OnBreakProp, event );
  1617. }
  1618. //--------------------------------------------------------------------------------------------------------------
  1619. void CCSBotManager::OnHostageFollows( IGameEvent *event )
  1620. {
  1621. CCSBOTMANAGER_ITERATE_BOTS( OnHostageFollows, event );
  1622. }
  1623. //--------------------------------------------------------------------------------------------------------------
  1624. void CCSBotManager::OnHostageRescuedAll( IGameEvent *event )
  1625. {
  1626. CCSBOTMANAGER_ITERATE_BOTS( OnHostageRescuedAll, event );
  1627. }
  1628. //--------------------------------------------------------------------------------------------------------------
  1629. void CCSBotManager::OnWeaponFire( IGameEvent *event )
  1630. {
  1631. CCSBOTMANAGER_ITERATE_BOTS( OnWeaponFire, event );
  1632. }
  1633. //--------------------------------------------------------------------------------------------------------------
  1634. void CCSBotManager::OnWeaponFireOnEmpty( IGameEvent *event )
  1635. {
  1636. CCSBOTMANAGER_ITERATE_BOTS( OnWeaponFireOnEmpty, event );
  1637. }
  1638. //--------------------------------------------------------------------------------------------------------------
  1639. void CCSBotManager::OnWeaponReload( IGameEvent *event )
  1640. {
  1641. CCSBOTMANAGER_ITERATE_BOTS( OnWeaponReload, event );
  1642. }
  1643. //--------------------------------------------------------------------------------------------------------------
  1644. void CCSBotManager::OnWeaponZoom( IGameEvent *event )
  1645. {
  1646. CCSBOTMANAGER_ITERATE_BOTS( OnWeaponZoom, event );
  1647. }
  1648. //--------------------------------------------------------------------------------------------------------------
  1649. void CCSBotManager::OnBulletImpact( IGameEvent *event )
  1650. {
  1651. CCSBOTMANAGER_ITERATE_BOTS( OnBulletImpact, event );
  1652. }
  1653. //--------------------------------------------------------------------------------------------------------------
  1654. void CCSBotManager::OnHEGrenadeDetonate( IGameEvent *event )
  1655. {
  1656. CCSBOTMANAGER_ITERATE_BOTS( OnHEGrenadeDetonate, event );
  1657. }
  1658. //--------------------------------------------------------------------------------------------------------------
  1659. void CCSBotManager::OnFlashbangDetonate( IGameEvent *event )
  1660. {
  1661. CCSBOTMANAGER_ITERATE_BOTS( OnFlashbangDetonate, event );
  1662. }
  1663. //--------------------------------------------------------------------------------------------------------------
  1664. void CCSBotManager::OnSmokeGrenadeDetonate( IGameEvent *event )
  1665. {
  1666. CCSBOTMANAGER_ITERATE_BOTS( OnSmokeGrenadeDetonate, event );
  1667. }
  1668. //--------------------------------------------------------------------------------------------------------------
  1669. void CCSBotManager::OnGrenadeBounce( IGameEvent *event )
  1670. {
  1671. CCSBOTMANAGER_ITERATE_BOTS( OnGrenadeBounce, event );
  1672. }
  1673. //--------------------------------------------------------------------------------------------------------------
  1674. /**
  1675. * Get the time remaining before the planted bomb explodes
  1676. */
  1677. float CCSBotManager::GetBombTimeLeft( void ) const
  1678. {
  1679. return (mp_c4timer.GetFloat() - (gpGlobals->curtime - m_bombPlantTimestamp));
  1680. }
  1681. //--------------------------------------------------------------------------------------------------------------
  1682. void CCSBotManager::SetLooseBomb( CBaseEntity *bomb )
  1683. {
  1684. m_looseBomb = bomb;
  1685. if (bomb)
  1686. {
  1687. m_looseBombArea = TheNavMesh->GetNearestNavArea( bomb->GetAbsOrigin() );
  1688. }
  1689. else
  1690. {
  1691. m_looseBombArea = NULL;
  1692. }
  1693. }
  1694. //--------------------------------------------------------------------------------------------------------------
  1695. /**
  1696. * Return true if player is important to scenario (VIP, bomb carrier, etc)
  1697. */
  1698. bool CCSBotManager::IsImportantPlayer( CCSPlayer *player ) const
  1699. {
  1700. switch (GetScenario())
  1701. {
  1702. case SCENARIO_DEFUSE_BOMB:
  1703. {
  1704. if (player->GetTeamNumber() == TEAM_TERRORIST && player->HasC4())
  1705. return true;
  1706. /// @todo TEAM_CT's defusing the bomb are important
  1707. return false;
  1708. }
  1709. case SCENARIO_ESCORT_VIP:
  1710. {
  1711. if (player->GetTeamNumber() == TEAM_CT && player->IsVIP())
  1712. return true;
  1713. return false;
  1714. }
  1715. case SCENARIO_RESCUE_HOSTAGES:
  1716. {
  1717. /// @todo TEAM_CT's escorting hostages are important
  1718. return false;
  1719. }
  1720. }
  1721. // everyone is equally important in a deathmatch
  1722. return false;
  1723. }
  1724. //--------------------------------------------------------------------------------------------------------------
  1725. /**
  1726. * Return priority of player (0 = max pri)
  1727. */
  1728. unsigned int CCSBotManager::GetPlayerPriority( CBasePlayer *player ) const
  1729. {
  1730. const unsigned int lowestPriority = 0xFFFFFFFF;
  1731. if (!player->IsPlayer())
  1732. return lowestPriority;
  1733. // human players have highest priority
  1734. if (!player->IsBot())
  1735. return 0;
  1736. CCSBot *bot = dynamic_cast<CCSBot *>( player );
  1737. if ( !bot )
  1738. return 0;
  1739. // bots doing something important for the current scenario have high priority
  1740. switch (GetScenario())
  1741. {
  1742. case SCENARIO_DEFUSE_BOMB:
  1743. {
  1744. // the bomb carrier has high priority
  1745. if (bot->GetTeamNumber() == TEAM_TERRORIST && bot->HasC4())
  1746. return 1;
  1747. break;
  1748. }
  1749. case SCENARIO_ESCORT_VIP:
  1750. {
  1751. // the VIP has high priority
  1752. if (bot->GetTeamNumber() == TEAM_CT && bot->m_bIsVIP)
  1753. return 1;
  1754. break;
  1755. }
  1756. case SCENARIO_RESCUE_HOSTAGES:
  1757. {
  1758. // TEAM_CT's rescuing hostages have high priority
  1759. if (bot->GetTeamNumber() == TEAM_CT && bot->GetHostageEscortCount())
  1760. return 1;
  1761. break;
  1762. }
  1763. }
  1764. // everyone else is ranked by their unique ID (which cannot be zero)
  1765. return 1 + bot->GetID();
  1766. }
  1767. //--------------------------------------------------------------------------------------------------------------
  1768. /**
  1769. * Returns a random spawn point for the given team (no arg means use both team spawnpoints)
  1770. */
  1771. CBaseEntity *CCSBotManager::GetRandomSpawn( int team ) const
  1772. {
  1773. CUtlVector< CBaseEntity * > spawnSet;
  1774. CBaseEntity *spot;
  1775. if (team == TEAM_TERRORIST || team == TEAM_MAXCOUNT)
  1776. {
  1777. // collect T spawns
  1778. for( spot = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
  1779. spot;
  1780. spot = gEntList.FindEntityByClassname( spot, "info_player_terrorist" ) )
  1781. {
  1782. spawnSet.AddToTail( spot );
  1783. }
  1784. }
  1785. if (team == TEAM_CT || team == TEAM_MAXCOUNT)
  1786. {
  1787. // collect CT spawns
  1788. for( spot = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
  1789. spot;
  1790. spot = gEntList.FindEntityByClassname( spot, "info_player_counterterrorist" ) )
  1791. {
  1792. spawnSet.AddToTail( spot );
  1793. }
  1794. }
  1795. if (spawnSet.Count() == 0)
  1796. {
  1797. return NULL;
  1798. }
  1799. // select one at random
  1800. int which = RandomInt( 0, spawnSet.Count()-1 );
  1801. return spawnSet[ which ];
  1802. }
  1803. //--------------------------------------------------------------------------------------------------------------
  1804. /**
  1805. * Return the last time the given radio message was sent for given team
  1806. * 'teamID' can be TEAM_CT or TEAM_TERRORIST
  1807. */
  1808. float CCSBotManager::GetRadioMessageTimestamp( RadioType event, int teamID ) const
  1809. {
  1810. int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
  1811. if (event > RADIO_START_1 && event < RADIO_END)
  1812. return m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ];
  1813. return 0.0f;
  1814. }
  1815. //--------------------------------------------------------------------------------------------------------------
  1816. /**
  1817. * Return the interval since the last time this message was sent
  1818. */
  1819. float CCSBotManager::GetRadioMessageInterval( RadioType event, int teamID ) const
  1820. {
  1821. int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
  1822. if (event > RADIO_START_1 && event < RADIO_END)
  1823. return gpGlobals->curtime - m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ];
  1824. return 99999999.9f;
  1825. }
  1826. //--------------------------------------------------------------------------------------------------------------
  1827. /**
  1828. * Set the given radio message timestamp.
  1829. * 'teamID' can be TEAM_CT or TEAM_TERRORIST
  1830. */
  1831. void CCSBotManager::SetRadioMessageTimestamp( RadioType event, int teamID )
  1832. {
  1833. int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
  1834. if (event > RADIO_START_1 && event < RADIO_END)
  1835. m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ] = gpGlobals->curtime;
  1836. }
  1837. //--------------------------------------------------------------------------------------------------------------
  1838. /**
  1839. * Reset all radio message timestamps
  1840. */
  1841. void CCSBotManager::ResetRadioMessageTimestamps( void )
  1842. {
  1843. for( int t=0; t<2; ++t )
  1844. {
  1845. for( int m=0; m<(RADIO_END - RADIO_START_1); ++m )
  1846. m_radioMsgTimestamp[ m ][ t ] = 0.0f;
  1847. }
  1848. }
  1849. //--------------------------------------------------------------------------------------------------------------
  1850. /**
  1851. * Display nav areas as they become reachable by each team
  1852. */
  1853. void DrawOccupyTime( void )
  1854. {
  1855. FOR_EACH_VEC( TheNavAreas, it )
  1856. {
  1857. CNavArea *area = TheNavAreas[ it ];
  1858. int r, g, b;
  1859. if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_TERRORIST ))
  1860. {
  1861. if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_CT ))
  1862. {
  1863. r = 255; g = 0; b = 255;
  1864. }
  1865. else
  1866. {
  1867. r = 255; g = 0; b = 0;
  1868. }
  1869. }
  1870. else if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_CT ))
  1871. {
  1872. r = 0; g = 0; b = 255;
  1873. }
  1874. else
  1875. {
  1876. continue;
  1877. }
  1878. const Vector &nw = area->GetCorner( NORTH_WEST );
  1879. const Vector &ne = area->GetCorner( NORTH_EAST );
  1880. const Vector &sw = area->GetCorner( SOUTH_WEST );
  1881. const Vector &se = area->GetCorner( SOUTH_EAST );
  1882. NDebugOverlay::Line( nw, ne, r, g, b, true, 0.1f );
  1883. NDebugOverlay::Line( nw, sw, r, g, b, true, 0.1f );
  1884. NDebugOverlay::Line( se, sw, r, g, b, true, 0.1f );
  1885. NDebugOverlay::Line( se, ne, r, g, b, true, 0.1f );
  1886. }
  1887. }
  1888. //--------------------------------------------------------------------------------------------------------------
  1889. /**
  1890. * Display areas where players will likely have initial battles
  1891. */
  1892. void DrawBattlefront( void )
  1893. {
  1894. const float epsilon = 1.0f;
  1895. int r = 255, g = 50, b = 0;
  1896. FOR_EACH_VEC( TheNavAreas, it )
  1897. {
  1898. CNavArea *area = TheNavAreas[ it ];
  1899. if ( fabs(area->GetEarliestOccupyTime( TEAM_TERRORIST ) - area->GetEarliestOccupyTime( TEAM_CT )) > epsilon )
  1900. {
  1901. continue;
  1902. }
  1903. const Vector &nw = area->GetCorner( NORTH_WEST );
  1904. const Vector &ne = area->GetCorner( NORTH_EAST );
  1905. const Vector &sw = area->GetCorner( SOUTH_WEST );
  1906. const Vector &se = area->GetCorner( SOUTH_EAST );
  1907. NDebugOverlay::Line( nw, ne, r, g, b, true, 0.1f );
  1908. NDebugOverlay::Line( nw, sw, r, g, b, true, 0.1f );
  1909. NDebugOverlay::Line( se, sw, r, g, b, true, 0.1f );
  1910. NDebugOverlay::Line( se, ne, r, g, b, true, 0.1f );
  1911. }
  1912. }
  1913. //--------------------------------------------------------------------------------------------------------------
  1914. static bool CheckAreaAgainstAllZoneAreas(CNavArea *queryArea)
  1915. {
  1916. // A marked area means they just want to double check this one spot
  1917. int goalZoneCount = TheCSBots()->GetZoneCount();
  1918. for( int zoneIndex = 0; zoneIndex < goalZoneCount; zoneIndex++ )
  1919. {
  1920. const CCSBotManager::Zone *currentZone = TheCSBots()->GetZone(zoneIndex);
  1921. int zoneAreaCount = currentZone->m_areaCount;
  1922. for( int areaIndex = 0; areaIndex < zoneAreaCount; areaIndex++ )
  1923. {
  1924. CNavArea *zoneArea = currentZone->m_area[areaIndex];
  1925. // We need to be connected to every area in the zone, since we don't know what other code might pick for an area
  1926. ShortestPathCost cost;
  1927. if( NavAreaTravelDistance(queryArea, zoneArea, cost) == -1.0f )
  1928. {
  1929. Msg( "Area #%d is disconnected from goal area #%d.\n",
  1930. queryArea->GetID(),
  1931. zoneArea->GetID()
  1932. );
  1933. return false;
  1934. }
  1935. }
  1936. }
  1937. return true;
  1938. }
  1939. CON_COMMAND_F( nav_check_connectivity, "Checks to be sure every (or just the marked) nav area can get to every goal area for the map (hostages or bomb site).", FCVAR_CHEAT )
  1940. {
  1941. //Nav command in here since very CS specific.
  1942. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  1943. return;
  1944. if ( TheNavMesh->GetMarkedArea() )
  1945. {
  1946. CNavArea *markedArea = TheNavMesh->GetMarkedArea();
  1947. bool fine = CheckAreaAgainstAllZoneAreas( markedArea );
  1948. if( fine )
  1949. {
  1950. Msg( "Area #%d is connected to all goal areas.\n", markedArea->GetID() );
  1951. }
  1952. }
  1953. else
  1954. {
  1955. // Otherwise, loop through every area, and make sure they can all get to the goal.
  1956. float start = engine->Time();
  1957. FOR_EACH_VEC( TheNavAreas, nit )
  1958. {
  1959. CheckAreaAgainstAllZoneAreas(TheNavAreas[ nit ]);
  1960. }
  1961. float end = engine->Time();
  1962. float time = (end - start) * 1000.0f;
  1963. Msg( "nav_check_connectivity took %2.2f ms\n", time );
  1964. }
  1965. }