Counter Strike : Global Offensive Source Code
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.

947 lines
24 KiB

  1. // NextBotManager.cpp
  2. // Author: Michael Booth, May 2006
  3. // Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
  4. #include "cbase.h"
  5. #include "NextBotManager.h"
  6. #include "NextBotInterface.h"
  7. #ifdef TERROR
  8. #include "ZombieBot/Infected/Infected.h"
  9. #include "ZombieBot/Witch/Witch.h"
  10. #include "ZombieManager.h"
  11. #endif
  12. #include "SharedFunctorUtils.h"
  13. //#include "../../common/blackbox_helper.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. extern ConVar ZombieMobMaxSize;
  17. ConVar nb_update_frequency( "nb_update_frequency", ".1", FCVAR_CHEAT );
  18. ConVar nb_update_framelimit( "nb_update_framelimit", ( IsDebug() ) ? "30" : "15", FCVAR_CHEAT );
  19. ConVar nb_update_maxslide( "nb_update_maxslide", "2", FCVAR_CHEAT );
  20. ConVar nb_update_debug( "nb_update_debug", "0", FCVAR_CHEAT );
  21. //---------------------------------------------------------------------------------------------
  22. //---------------------------------------------------------------------------------------------
  23. /**
  24. * Singleton accessor.
  25. * By returning a reference, we guarantee construction of the
  26. * instance before its first use.
  27. */
  28. NextBotManager &TheNextBots( void )
  29. {
  30. static NextBotManager manager;
  31. return manager;
  32. }
  33. //---------------------------------------------------------------------------------------------
  34. //---------------------------------------------------------------------------------------------
  35. static const char *debugTypeName[] =
  36. {
  37. "BEHAVIOR",
  38. "LOOK_AT",
  39. "PATH",
  40. "ANIMATION",
  41. "LOCOMOTION",
  42. "VISION",
  43. "HEARING",
  44. "EVENTS",
  45. "ERRORS",
  46. NULL
  47. };
  48. static void CC_SetDebug( const CCommand &args )
  49. {
  50. if ( args.ArgC() < 2 )
  51. {
  52. Msg( "Debugging stopped\n" );
  53. TheNextBots().SetDebugTypes( NEXTBOT_DEBUG_NONE );
  54. return;
  55. }
  56. int debugType = 0;
  57. for( int i=1; i<args.ArgC(); ++i )
  58. {
  59. int type;
  60. for( type = 0; debugTypeName[ type ]; ++type )
  61. {
  62. const char *token = args[i];
  63. // special token that means "all"
  64. if ( token[0] == '*' )
  65. {
  66. debugType = NEXTBOT_DEBUG_ALL;
  67. break;
  68. }
  69. if ( !Q_strnicmp( args[i], debugTypeName[ type ], Q_strlen( args[1] ) ) )
  70. {
  71. debugType |= ( 1 << type );
  72. break;
  73. }
  74. }
  75. if ( !debugTypeName[ type ] )
  76. {
  77. Msg( "Invalid debug type '%s'\n", args[i] );
  78. }
  79. }
  80. // enable debugging
  81. TheNextBots().SetDebugTypes( ( NextBotDebugType ) debugType );
  82. }
  83. static ConCommand SetDebug( "nb_debug", CC_SetDebug, "Debug NextBots. Categories are: BEHAVIOR, LOOK_AT, PATH, ANIMATION, LOCOMOTION, VISION, HEARING, EVENTS, ERRORS.", FCVAR_CHEAT );
  84. //---------------------------------------------------------------------------------------------
  85. static void CC_SetDebugFilter( const CCommand &args )
  86. {
  87. if ( args.ArgC() < 2 )
  88. {
  89. Msg( "Debug filter cleared.\n" );
  90. TheNextBots().DebugFilterClear();
  91. return;
  92. }
  93. for( int i=1; i<args.ArgC(); ++i )
  94. {
  95. int index = Q_atoi( args[i] );
  96. if ( index > 0 )
  97. {
  98. TheNextBots().DebugFilterAdd( index );
  99. }
  100. else
  101. {
  102. TheNextBots().DebugFilterAdd( args[i] );
  103. }
  104. }
  105. }
  106. static ConCommand SetDebugFilter( "nb_debug_filter", CC_SetDebugFilter, "Add items to the NextBot debug filter. Items can be entindexes or part of the indentifier of one or more bots.", FCVAR_CHEAT );
  107. //---------------------------------------------------------------------------------------------
  108. class Selector
  109. {
  110. public:
  111. Selector( CBasePlayer *player, bool useLOS )
  112. {
  113. m_player = player;
  114. player->EyeVectors( &m_forward );
  115. m_pick = NULL;
  116. m_pickRange = 99999999999999.9f;
  117. m_useLOS = useLOS;
  118. }
  119. bool operator() ( INextBot *bot )
  120. {
  121. CBaseCombatCharacter *botEntity = bot->GetEntity();
  122. if ( botEntity->IsAlive() )
  123. {
  124. Vector to = botEntity->WorldSpaceCenter() - m_player->EyePosition();
  125. float range = to.NormalizeInPlace();
  126. if ( DotProduct( m_forward, to ) > 0.98f && range < m_pickRange )
  127. {
  128. if ( !m_useLOS || m_player->IsAbleToSee( botEntity, CBaseCombatCharacter::DISREGARD_FOV ) )
  129. {
  130. m_pick = bot;
  131. m_pickRange = range;
  132. }
  133. }
  134. }
  135. return true;
  136. }
  137. CBasePlayer *m_player;
  138. Vector m_forward;
  139. INextBot *m_pick;
  140. float m_pickRange;
  141. bool m_useLOS;
  142. };
  143. static void CC_SelectBot( const CCommand &args )
  144. {
  145. CBasePlayer *player = UTIL_GetListenServerHost();
  146. if ( player )
  147. {
  148. Selector select( player, false );
  149. TheNextBots().ForEachBot( select );
  150. TheNextBots().Select( select.m_pick );
  151. if ( select.m_pick )
  152. {
  153. NDebugOverlay::Circle( select.m_pick->GetLocomotionInterface()->GetFeet() + Vector( 0, 0, 5 ), Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 25.0f, 0, 255, 0, 255, false, 1.0f );
  154. }
  155. }
  156. }
  157. static ConCommand SelectBot( "nb_select", CC_SelectBot, "Select the bot you are aiming at for further debug operations.", FCVAR_CHEAT );
  158. //---------------------------------------------------------------------------------------------
  159. static void CC_ForceLookAt( const CCommand &args )
  160. {
  161. CBasePlayer *player = UTIL_GetListenServerHost();
  162. INextBot *pick = TheNextBots().GetSelected();
  163. if ( player && pick )
  164. {
  165. pick->GetBodyInterface()->AimHeadTowards( player, IBody::CRITICAL, 9999999.9f, NULL, "Aim forced" );
  166. }
  167. }
  168. static ConCommand ForceLookAt( "nb_force_look_at", CC_ForceLookAt, "Force selected bot to look at the local player's position", FCVAR_CHEAT );
  169. //--------------------------------------------------------------------------------------------------------
  170. void CC_WarpSelectedHere( const CCommand &args )
  171. {
  172. CBasePlayer *me = dynamic_cast< CBasePlayer * >( UTIL_GetCommandClient() );
  173. INextBot *pick = TheNextBots().GetSelected();
  174. if ( me == NULL || pick == NULL )
  175. {
  176. return;
  177. }
  178. Vector forward;
  179. me->EyeVectors( &forward );
  180. trace_t result;
  181. UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, me, COLLISION_GROUP_NONE, &result );
  182. if ( result.DidHit() )
  183. {
  184. Vector spot = result.endpos + Vector( 0, 0, 10.0f );
  185. pick->GetEntity()->Teleport( &spot, &vec3_angle, &vec3_origin );
  186. }
  187. }
  188. static ConCommand WarpSelectedHere( "nb_warp_selected_here", CC_WarpSelectedHere, "Teleport the selected bot to your cursor position", FCVAR_CHEAT );
  189. //---------------------------------------------------------------------------------------------
  190. //---------------------------------------------------------------------------------------------
  191. NextBotManager::NextBotManager( void )
  192. {
  193. m_debugType = 0;
  194. m_selectedBot = NULL;
  195. m_iUpdateTickrate = 0;
  196. }
  197. //---------------------------------------------------------------------------------------------
  198. NextBotManager::~NextBotManager()
  199. {
  200. }
  201. //---------------------------------------------------------------------------------------------
  202. /**
  203. * Reset to initial state
  204. */
  205. void NextBotManager::Reset( void )
  206. {
  207. // remove the NextBots that should go away during a reset (they will unregister themselves as they go)
  208. int i = m_botList.Head();
  209. while ( i != m_botList.InvalidIndex() )
  210. {
  211. int iNext = m_botList.Next( i );
  212. if ( m_botList[i]->IsRemovedOnReset() )
  213. {
  214. UTIL_Remove( m_botList[i]->GetEntity() );
  215. //Assert( !m_botList.IsInList( i ) ); // UTIL_Remove() calls UpdateOnRemove, adds EFL_KILLME, but doesn't delete until the end of the frame
  216. }
  217. i = iNext;
  218. }
  219. m_selectedBot = NULL;
  220. }
  221. //---------------------------------------------------------------------------------------------
  222. inline bool IsDead( INextBot *pBot )
  223. {
  224. CBaseCombatCharacter *pEntity = pBot->GetEntity();
  225. if ( pEntity )
  226. {
  227. if ( pEntity->IsPlayer() && pEntity->m_lifeState == LIFE_DEAD )
  228. {
  229. return true;
  230. }
  231. if ( pEntity->IsMarkedForDeletion() )
  232. {
  233. return true;
  234. }
  235. if ( pEntity->m_pfnThink == &CBaseEntity::SUB_Remove )
  236. {
  237. return true;
  238. }
  239. }
  240. return false;
  241. }
  242. //---------------------------------------------------------------------------------------------
  243. // Debug stats for update balancing
  244. static int g_nRun;
  245. static int g_nSlid;
  246. static int g_nBlockedSlides;
  247. void NextBotManager::Update( void )
  248. {
  249. // do lightweight upkeep every tick
  250. for( int u=m_botList.Head(); u != m_botList.InvalidIndex(); u = m_botList.Next( u ) )
  251. {
  252. m_botList[ u ]->Upkeep();
  253. }
  254. // schedule full updates
  255. if ( m_botList.Count() )
  256. {
  257. static int iCurFrame = -1;
  258. if ( iCurFrame != gpGlobals->framecount )
  259. {
  260. iCurFrame = gpGlobals->framecount;
  261. m_SumFrameTime = 0;
  262. }
  263. else
  264. {
  265. // Don't run multiple ticks in a frame
  266. return;
  267. }
  268. int tickRate = TIME_TO_TICKS( nb_update_frequency.GetFloat() );
  269. if ( tickRate < 0 )
  270. {
  271. tickRate = 0;
  272. }
  273. if ( m_iUpdateTickrate != tickRate )
  274. {
  275. Msg( "NextBot tickrate changed from %d (%.3fms) to %d (%.3fms)\n", m_iUpdateTickrate, TICKS_TO_TIME( m_iUpdateTickrate ), tickRate, TICKS_TO_TIME( tickRate ) );
  276. m_iUpdateTickrate = tickRate;
  277. }
  278. int i = 0;
  279. int nScheduled = 0;
  280. int nNonResponsive = 0;
  281. int nDead = 0;
  282. if ( m_iUpdateTickrate > 0 )
  283. {
  284. INextBot *pBot;
  285. // Count dead bots, they won't update and balancing calculations should exclude them
  286. for( i = m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
  287. {
  288. if ( IsDead( m_botList[i] ) )
  289. {
  290. nDead++;
  291. }
  292. }
  293. int nTargetToRun = ceilf( (float)( m_botList.Count() - nDead ) / (float)m_iUpdateTickrate );
  294. int curtickcount = gpGlobals->tickcount;
  295. for( i = m_botList.Head(); nTargetToRun && i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
  296. {
  297. pBot = m_botList[i];
  298. if ( pBot->IsFlaggedForUpdate() )
  299. {
  300. // Was offered a run last tick but didn't take it, push it back
  301. // Leave the flag set so that bot will run right away later, but be ignored
  302. // until then
  303. nNonResponsive++;
  304. }
  305. else
  306. {
  307. if ( curtickcount - pBot->GetTickLastUpdate() < m_iUpdateTickrate )
  308. {
  309. break;
  310. }
  311. if ( !IsDead( pBot ) )
  312. {
  313. pBot->FlagForUpdate();
  314. nTargetToRun--;
  315. nScheduled++;
  316. }
  317. }
  318. }
  319. }
  320. else
  321. {
  322. nScheduled = m_botList.Count();
  323. }
  324. if ( nb_update_debug.GetBool() )
  325. {
  326. int nIntentionalSliders = 0;
  327. if ( m_iUpdateTickrate > 0 )
  328. {
  329. for( ; i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
  330. {
  331. if ( gpGlobals->tickcount - m_botList[i]->GetTickLastUpdate() >= m_iUpdateTickrate )
  332. {
  333. nIntentionalSliders++;
  334. }
  335. }
  336. }
  337. Msg( "Frame %8d/tick %8d: %3d run of %3d, %3d sliders, %3d blocked slides, scheduled %3d for next tick, %3d intentional sliders, %d nonresponsive, %d dead\n", gpGlobals->framecount - 1, gpGlobals->tickcount - 1, g_nRun, m_botList.Count() - nDead, g_nSlid, g_nBlockedSlides, nScheduled, nIntentionalSliders, nNonResponsive, nDead );
  338. g_nRun = g_nSlid = g_nBlockedSlides = 0;
  339. }
  340. }
  341. }
  342. //---------------------------------------------------------------------------------------------
  343. bool NextBotManager::ShouldUpdate( INextBot *bot )
  344. {
  345. if ( m_iUpdateTickrate < 1 )
  346. {
  347. return true;
  348. }
  349. float frameLimit = nb_update_framelimit.GetFloat();
  350. float sumFrameTime = 0;
  351. if ( bot->IsFlaggedForUpdate() )
  352. {
  353. bot->FlagForUpdate( false );
  354. sumFrameTime = m_SumFrameTime * 1000.0;
  355. if ( frameLimit > 0.0f )
  356. {
  357. if ( sumFrameTime < frameLimit )
  358. {
  359. return true;
  360. }
  361. else if ( nb_update_debug.GetBool() )
  362. {
  363. Msg( "Frame %8d/tick %8d: frame out of budget (%.2fms > %.2fms)\n", gpGlobals->framecount, gpGlobals->tickcount, sumFrameTime, frameLimit );
  364. }
  365. }
  366. }
  367. int nTicksSlid = ( gpGlobals->tickcount - bot->GetTickLastUpdate() ) - m_iUpdateTickrate;
  368. if ( nTicksSlid >= nb_update_maxslide.GetInt() )
  369. {
  370. if ( frameLimit == 0.0 || sumFrameTime < nb_update_framelimit.GetFloat() * 2.0 )
  371. {
  372. g_nBlockedSlides++;
  373. return true;
  374. }
  375. }
  376. if ( nb_update_debug.GetBool() )
  377. {
  378. if ( nTicksSlid > 0 )
  379. {
  380. g_nSlid++;
  381. }
  382. }
  383. return false;
  384. }
  385. //---------------------------------------------------------------------------------------------
  386. void NextBotManager::NotifyBeginUpdate( INextBot *bot )
  387. {
  388. if ( nb_update_debug.GetBool() )
  389. {
  390. g_nRun++;
  391. }
  392. m_botList.Unlink( bot->GetBotId() );
  393. m_botList.LinkToTail( bot->GetBotId() );
  394. bot->SetTickLastUpdate( gpGlobals->tickcount );
  395. m_CurUpdateStartTime = Plat_FloatTime();
  396. }
  397. //---------------------------------------------------------------------------------------------
  398. void NextBotManager::NotifyEndUpdate( INextBot *bot )
  399. {
  400. // This might be a good place to detect a particular bot had spiked [3/14/2008 tom]
  401. m_SumFrameTime += Plat_FloatTime() - m_CurUpdateStartTime;
  402. }
  403. //---------------------------------------------------------------------------------------------
  404. /**
  405. * When the server has changed maps
  406. */
  407. void NextBotManager::OnMapLoaded( void )
  408. {
  409. Reset();
  410. }
  411. //---------------------------------------------------------------------------------------------
  412. /**
  413. * When the scenario restarts
  414. */
  415. void NextBotManager::OnRoundRestart( void )
  416. {
  417. Reset();
  418. }
  419. //---------------------------------------------------------------------------------------------
  420. int NextBotManager::Register( INextBot *bot )
  421. {
  422. return m_botList.AddToHead( bot );
  423. }
  424. //---------------------------------------------------------------------------------------------
  425. void NextBotManager::UnRegister( INextBot *bot )
  426. {
  427. m_botList.Remove( bot->GetBotId() );
  428. if ( bot == m_selectedBot)
  429. {
  430. // we can't access virtual methods because this is called from a destructor, so just clear it
  431. m_selectedBot = NULL;
  432. }
  433. }
  434. //---------------------------------------------------------------------------------------------
  435. #ifdef TERROR
  436. extern ConVar ZombieNoticeItRange;
  437. class NextBotAttackTheIT
  438. {
  439. public:
  440. NextBotAttackTheIT( CTerrorPlayer *victim, float range )
  441. {
  442. m_victim = victim;
  443. m_chaseCount = 0;
  444. m_range = range;
  445. }
  446. bool operator() ( INextBot *bot )
  447. {
  448. if ( bot->GetEntity()->GetTeamNumber() == TEAM_ZOMBIE && bot->GetEntity()->IsAlive() )
  449. {
  450. if ( ( bot->GetPosition() - m_victim->GetAbsOrigin() ).IsLengthLessThan( m_range ) )
  451. {
  452. // IT does not aggro Witches
  453. Witch *witch = dynamic_cast< Witch * >( bot );
  454. if ( !witch )
  455. {
  456. // close enough to smell the blood - attack the "it"!
  457. bot->OnCommandAttack( m_victim );
  458. ++m_chaseCount;
  459. }
  460. }
  461. }
  462. return true;
  463. }
  464. CTerrorPlayer *m_victim;
  465. int m_chaseCount;
  466. float m_range;
  467. };
  468. //---------------------------------------------------------------------------------------------
  469. /**
  470. * When a Survivor has been hit by Boomer Vomit
  471. */
  472. void NextBotManager::OnSurvivorVomitedUpon( CTerrorPlayer *victim )
  473. {
  474. NextBotAttackTheIT attackIT( victim, ZombieNoticeItRange.GetFloat() );
  475. ForEachBot( attackIT );
  476. // make sure a mob chases the IT victim
  477. int desiredCount = ZombieMobMaxSize.GetFloat();
  478. UTIL_LogPrintf( "(MOB) %d wanderers grabbed for an IT mob of desired size %d.\n", attackIT.m_chaseCount, desiredCount );
  479. if ( attackIT.m_chaseCount < desiredCount )
  480. {
  481. // fill out the IT mob a bit
  482. TheZombieManager->SpawnITMob( desiredCount - attackIT.m_chaseCount );
  483. }
  484. // either via wanderers or an explicit mob, there was a rush
  485. TheDirector->OnMobRushStart();
  486. }
  487. //---------------------------------------------------------------------------------------------
  488. CON_COMMAND_F( nb_rush, "Causes all infected to rush the survivors.", FCVAR_CHEAT )
  489. {
  490. CTerrorPlayer *victim = TheDirector->GetRandomSurvivor();
  491. if ( !victim )
  492. return;
  493. const float RushRange = 10000.0f; // everyone, not just bots in IT range.
  494. NextBotAttackTheIT attackIT( victim, RushRange );
  495. TheNextBots().ForEachBot( attackIT );
  496. }
  497. #endif // TERROR
  498. //--------------------------------------------------------------------------------------------------------
  499. void NextBotManager::OnBeginChangeLevel( void )
  500. {
  501. }
  502. //----------------------------------------------------------------------------------------------------------
  503. class NextBotKilledNotifyScan
  504. {
  505. public:
  506. NextBotKilledNotifyScan( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
  507. {
  508. m_victim = victim;
  509. m_info = info;
  510. }
  511. bool operator() ( INextBot *bot )
  512. {
  513. if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_victim ) )
  514. {
  515. bot->OnOtherKilled( m_victim, m_info );
  516. }
  517. return true;
  518. }
  519. CBaseCombatCharacter *m_victim;
  520. CTakeDamageInfo m_info;
  521. };
  522. //---------------------------------------------------------------------------------------------
  523. /**
  524. * When an actor is killed. Propagate to all NextBots.
  525. */
  526. void NextBotManager::OnKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
  527. {
  528. NextBotKilledNotifyScan notify( victim, info );
  529. TheNextBots().ForEachBot( notify );
  530. }
  531. //----------------------------------------------------------------------------------------------------------
  532. class NextBotSoundNotifyScan
  533. {
  534. public:
  535. NextBotSoundNotifyScan( CBaseEntity *source, const Vector &pos, KeyValues *keys ) : m_source( source ), m_pos( pos ), m_keys( keys )
  536. {
  537. }
  538. bool operator() ( INextBot *bot )
  539. {
  540. if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_source ) )
  541. {
  542. bot->OnSound( m_source, m_pos, m_keys );
  543. }
  544. return true;
  545. }
  546. CBaseEntity *m_source;
  547. const Vector &m_pos;
  548. KeyValues *m_keys;
  549. };
  550. //---------------------------------------------------------------------------------------------
  551. /**
  552. * When an entity emits a sound
  553. */
  554. void NextBotManager::OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys )
  555. {
  556. NextBotSoundNotifyScan notify( source, pos, keys );
  557. TheNextBots().ForEachBot( notify );
  558. if ( source && IsDebugging( NEXTBOT_HEARING ) )
  559. {
  560. int r,g,b;
  561. switch( source->GetTeamNumber() )
  562. {
  563. case FIRST_GAME_TEAM: r = 0; g = 255; b = 0; break;
  564. case (FIRST_GAME_TEAM+1): r = 255; g = 0; b = 0; break;
  565. default: r = 255; g = 255; b = 0; break;
  566. }
  567. NDebugOverlay::Circle( pos, Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 5.0f, r, g, b, 255, true, 3.0f );
  568. }
  569. }
  570. //----------------------------------------------------------------------------------------------------------
  571. class NextBotResponseNotifyScan
  572. {
  573. public:
  574. NextBotResponseNotifyScan( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ) : m_who( who ), m_concept( concept ), m_response( response )
  575. {
  576. }
  577. bool operator() ( INextBot *bot )
  578. {
  579. if ( bot->GetEntity()->IsAlive() )
  580. {
  581. bot->OnSpokeConcept( m_who, m_concept, m_response );
  582. }
  583. return true;
  584. }
  585. CBaseCombatCharacter *m_who;
  586. AIConcept_t m_concept;
  587. AI_Response *m_response;
  588. };
  589. //---------------------------------------------------------------------------------------------
  590. /**
  591. * When an Actor speaks a concept
  592. */
  593. void NextBotManager::OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response )
  594. {
  595. NextBotResponseNotifyScan notify( who, concept, response );
  596. TheNextBots().ForEachBot( notify );
  597. if ( IsDebugging( NEXTBOT_HEARING ) )
  598. {
  599. // const char *who = response->GetCriteria()->GetValue( response->GetCriteria()->FindCriterionIndex( "Who" ) );
  600. // TODO: Need concept.GetStringConcept()
  601. DevMsg( "%3.2f: OnSpokeConcept( %s, %s )\n", gpGlobals->curtime, who->GetDebugName(), "concept.GetStringConcept()" );
  602. }
  603. }
  604. //----------------------------------------------------------------------------------------------------------
  605. class NextBotWeaponFiredNotifyScan
  606. {
  607. public:
  608. NextBotWeaponFiredNotifyScan( CBaseCombatCharacter *who, CBaseCombatWeapon *weapon ) : m_who( who ), m_weapon( weapon )
  609. {
  610. }
  611. bool operator() ( INextBot *bot )
  612. {
  613. if ( bot->GetEntity()->IsAlive() )
  614. {
  615. bot->OnWeaponFired( m_who, m_weapon );
  616. }
  617. return true;
  618. }
  619. CBaseCombatCharacter *m_who;
  620. CBaseCombatWeapon *m_weapon;
  621. };
  622. //---------------------------------------------------------------------------------------------
  623. /**
  624. * When someone fires a weapon
  625. */
  626. void NextBotManager::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
  627. {
  628. NextBotWeaponFiredNotifyScan notify( whoFired, weapon );
  629. TheNextBots().ForEachBot( notify );
  630. if ( IsDebugging( NEXTBOT_EVENTS ) )
  631. {
  632. DevMsg( "%3.2f: OnWeaponFired( %s, %s )\n", gpGlobals->curtime, whoFired->GetDebugName(), weapon->GetName() );
  633. }
  634. }
  635. //---------------------------------------------------------------------------------------------
  636. /**
  637. * Add given entindex to the debug filter
  638. */
  639. void NextBotManager::DebugFilterAdd( int index )
  640. {
  641. DebugFilter filter;
  642. filter.index = index;
  643. filter.name[0] = '\000';
  644. m_debugFilterList.AddToTail( filter );
  645. }
  646. //---------------------------------------------------------------------------------------------
  647. /**
  648. * Add given name to the debug filter
  649. */
  650. void NextBotManager::DebugFilterAdd( const char *name )
  651. {
  652. DebugFilter filter;
  653. filter.index = -1;
  654. Q_strncpy( filter.name, name, DebugFilter::MAX_DEBUG_NAME_SIZE );
  655. m_debugFilterList.AddToTail( filter );
  656. }
  657. //---------------------------------------------------------------------------------------------
  658. /**
  659. * Remove given entindex from the debug filter
  660. */
  661. void NextBotManager::DebugFilterRemove( int index )
  662. {
  663. for( int i=0; i<m_debugFilterList.Count(); ++i )
  664. {
  665. if ( m_debugFilterList[i].index == index )
  666. {
  667. m_debugFilterList.Remove( i );
  668. break;
  669. }
  670. }
  671. }
  672. //---------------------------------------------------------------------------------------------
  673. /**
  674. * Remove given name from the debug filter
  675. */
  676. void NextBotManager::DebugFilterRemove( const char *name )
  677. {
  678. for( int i=0; i<m_debugFilterList.Count(); ++i )
  679. {
  680. if ( m_debugFilterList[i].name[0] != '\000' &&
  681. !Q_strnicmp( name, m_debugFilterList[i].name, MIN( Q_strlen( name ), sizeof( m_debugFilterList[i].name ) ) ) )
  682. {
  683. m_debugFilterList.Remove( i );
  684. break;
  685. }
  686. }
  687. }
  688. //---------------------------------------------------------------------------------------------
  689. /**
  690. * Clear the debug filter (remove all entries)
  691. */
  692. void NextBotManager::DebugFilterClear( void )
  693. {
  694. m_debugFilterList.RemoveAll();
  695. }
  696. //---------------------------------------------------------------------------------------------
  697. /**
  698. * Return true if the given bot matches the debug filter
  699. */
  700. bool NextBotManager::IsDebugFilterMatch( const INextBot *bot ) const
  701. {
  702. // if the filter is empty, all bots match
  703. if ( m_debugFilterList.Count() == 0 )
  704. {
  705. return true;
  706. }
  707. for( int i=0; i<m_debugFilterList.Count(); ++i )
  708. {
  709. // compare entity index
  710. if ( m_debugFilterList[i].index == const_cast< INextBot * >( bot )->GetEntity()->entindex() )
  711. {
  712. return true;
  713. }
  714. // compare debug filter
  715. if ( m_debugFilterList[i].name[0] != '\000' && bot->IsDebugFilterMatch( m_debugFilterList[i].name ) )
  716. {
  717. return true;
  718. }
  719. // compare special keyword meaning local player is looking at them
  720. if ( !Q_strnicmp( m_debugFilterList[i].name, "lookat", Q_strlen( m_debugFilterList[i].name ) ) )
  721. {
  722. CBasePlayer *watcher = UTIL_GetListenServerHost();
  723. if ( watcher )
  724. {
  725. CBaseEntity *subject = watcher->GetObserverTarget();
  726. if ( subject && bot->IsSelf( subject ) )
  727. {
  728. return true;
  729. }
  730. }
  731. }
  732. // compare special keyword meaning NextBot is selected
  733. if ( !Q_strnicmp( m_debugFilterList[i].name, "selected", Q_strlen( m_debugFilterList[i].name ) ) )
  734. {
  735. INextBot *selected = GetSelected();
  736. if ( selected && bot->IsSelf( selected->GetEntity() ) )
  737. {
  738. return true;
  739. }
  740. }
  741. }
  742. return false;
  743. }
  744. //---------------------------------------------------------------------------------------------
  745. /**
  746. * Get the bot under the given player's crosshair
  747. */
  748. INextBot *NextBotManager::GetBotUnderCrosshair( CBasePlayer *picker )
  749. {
  750. if ( !picker )
  751. return NULL;
  752. const float MaxDot = 0.7f;
  753. const float MaxRange = 4000.0f;
  754. TargetScan< CBaseCombatCharacter > scan( picker, TEAM_ANY, 1.0f - MaxDot, MaxRange );
  755. ForEachCombatCharacter( scan );
  756. CBaseCombatCharacter *target = scan.GetTarget();
  757. if ( target && target->MyNextBotPointer() )
  758. return target->MyNextBotPointer();
  759. return NULL;
  760. }
  761. #ifdef NEED_BLACK_BOX
  762. //---------------------------------------------------------------------------------------------
  763. CON_COMMAND( nb_dump_debug_history, "Dumps debug history for the bot under the cursor to the blackbox" )
  764. {
  765. if ( !NextBotDebugHistory.GetBool() )
  766. {
  767. BlackBox_Record( "bot", "nb_debug_history 0" );
  768. return;
  769. }
  770. CBasePlayer *player = UTIL_GetCommandClient();
  771. if ( !player )
  772. {
  773. player = UTIL_GetListenServerHost();
  774. }
  775. INextBot *bot = TheNextBots().GetBotUnderCrosshair( player );
  776. if ( !bot )
  777. {
  778. BlackBox_Record( "bot", "no bot under crosshairs" );
  779. return;
  780. }
  781. CUtlVector< const INextBot::NextBotDebugLineType * > lines;
  782. bot->GetDebugHistory( (NEXTBOT_DEBUG_ALL & (~NEXTBOT_EVENTS)), &lines );
  783. for ( int i=0; i<lines.Count(); ++i )
  784. {
  785. if ( IsPC() )
  786. {
  787. BlackBox_Record( "bot", "%s", lines[i]->data );
  788. }
  789. }
  790. }
  791. #endif // NEED_BLACK_BOX