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.

729 lines
20 KiB

  1. // NextBotVisionInterface.cpp
  2. // Implementation of common vision system
  3. // Author: Michael Booth, May 2006
  4. // Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
  5. #include "cbase.h"
  6. #include "nav.h"
  7. #include "functorutils.h"
  8. #include "NextBot.h"
  9. #include "NextBotVisionInterface.h"
  10. #include "NextBotBodyInterface.h"
  11. #include "NextBotUtil.h"
  12. #ifdef TERROR
  13. #include "querycache.h"
  14. #endif
  15. #include "tier0/vprof.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. ConVar nb_blind( "nb_blind", "0", FCVAR_CHEAT, "Disable vision" );
  19. ConVar nb_debug_known_entities( "nb_debug_known_entities", "0", FCVAR_CHEAT, "Show the 'known entities' for the bot that is the current spectator target" );
  20. //------------------------------------------------------------------------------------------
  21. IVision::IVision( INextBot *bot ) : INextBotComponent( bot )
  22. {
  23. Reset();
  24. }
  25. //------------------------------------------------------------------------------------------
  26. /**
  27. * Reset to initial state
  28. */
  29. void IVision::Reset( void )
  30. {
  31. INextBotComponent::Reset();
  32. m_knownEntityVector.RemoveAll();
  33. m_lastVisionUpdateTimestamp = 0.0f;
  34. m_FOV = GetDefaultFieldOfView();
  35. m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
  36. for( int i=0; i<MAX_TEAMS; ++i )
  37. {
  38. m_notVisibleTimer[i].Invalidate();
  39. }
  40. }
  41. //------------------------------------------------------------------------------------------
  42. /**
  43. * Ask the current behavior to select the most dangerous threat from
  44. * our set of currently known entities
  45. * TODO: Find a semantically better place for this to live.
  46. */
  47. const CKnownEntity *IVision::GetPrimaryKnownThreat( bool onlyVisibleThreats ) const
  48. {
  49. if ( m_knownEntityVector.Count() == 0 )
  50. return NULL;
  51. const CKnownEntity *threat = NULL;
  52. int i;
  53. // find the first valid entity
  54. for( i=0; i<m_knownEntityVector.Count(); ++i )
  55. {
  56. const CKnownEntity &firstThreat = m_knownEntityVector[i];
  57. // check in case status changes between updates
  58. if ( IsAwareOf( firstThreat ) && !firstThreat.IsObsolete() && !IsIgnored( firstThreat.GetEntity() ) && GetBot()->IsEnemy( firstThreat.GetEntity() ) )
  59. {
  60. if ( !onlyVisibleThreats || firstThreat.IsVisible() )
  61. {
  62. threat = &firstThreat;
  63. break;
  64. }
  65. }
  66. }
  67. if ( threat == NULL )
  68. return NULL;
  69. for( ++i; i<m_knownEntityVector.Count(); ++i )
  70. {
  71. const CKnownEntity &newThreat = m_knownEntityVector[i];
  72. // check in case status changes between updates
  73. if ( IsAwareOf( newThreat ) && !newThreat.IsObsolete() && !IsIgnored( newThreat.GetEntity() ) && GetBot()->IsEnemy( newThreat.GetEntity() ) )
  74. {
  75. if ( !onlyVisibleThreats || newThreat.IsVisible() )
  76. {
  77. threat = GetBot()->GetIntentionInterface()->SelectMoreDangerousThreat( GetBot(), GetBot()->GetEntity(), threat, &newThreat );
  78. }
  79. }
  80. }
  81. return threat;
  82. }
  83. //------------------------------------------------------------------------------------------
  84. /**
  85. * Return the closest recognized entity
  86. */
  87. const CKnownEntity *IVision::GetClosestKnown( int team ) const
  88. {
  89. const Vector &myPos = GetBot()->GetPosition();
  90. const CKnownEntity *close = NULL;
  91. float closeRange = 999999999.9f;
  92. for( int i=0; i < m_knownEntityVector.Count(); ++i )
  93. {
  94. const CKnownEntity &known = m_knownEntityVector[i];
  95. if ( !known.IsObsolete() && IsAwareOf( known ) )
  96. {
  97. if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
  98. {
  99. Vector to = known.GetLastKnownPosition() - myPos;
  100. float rangeSq = to.LengthSqr();
  101. if ( rangeSq < closeRange )
  102. {
  103. close = &known;
  104. closeRange = rangeSq;
  105. }
  106. }
  107. }
  108. }
  109. return close;
  110. }
  111. //------------------------------------------------------------------------------------------
  112. /**
  113. * Return the closest recognized entity that passes the given filter
  114. */
  115. const CKnownEntity *IVision::GetClosestKnown( const INextBotEntityFilter &filter ) const
  116. {
  117. const Vector &myPos = GetBot()->GetPosition();
  118. const CKnownEntity *close = NULL;
  119. float closeRange = 999999999.9f;
  120. for( int i=0; i < m_knownEntityVector.Count(); ++i )
  121. {
  122. const CKnownEntity &known = m_knownEntityVector[i];
  123. if ( !known.IsObsolete() && IsAwareOf( known ) )
  124. {
  125. if ( filter.IsAllowed( known.GetEntity() ) )
  126. {
  127. Vector to = known.GetLastKnownPosition() - myPos;
  128. float rangeSq = to.LengthSqr();
  129. if ( rangeSq < closeRange )
  130. {
  131. close = &known;
  132. closeRange = rangeSq;
  133. }
  134. }
  135. }
  136. }
  137. return close;
  138. }
  139. //------------------------------------------------------------------------------------------
  140. /**
  141. * Given an entity, return our known version of it (or NULL if we don't know of it)
  142. */
  143. const CKnownEntity *IVision::GetKnown( const CBaseEntity *entity ) const
  144. {
  145. if ( entity == NULL )
  146. return NULL;
  147. for( int i=0; i < m_knownEntityVector.Count(); ++i )
  148. {
  149. const CKnownEntity &known = m_knownEntityVector[i];
  150. if ( known.GetEntity() && known.GetEntity()->entindex() == entity->entindex() )
  151. {
  152. return &known;
  153. }
  154. }
  155. return NULL;
  156. }
  157. //------------------------------------------------------------------------------------------
  158. /**
  159. * Introduce a known entity into the system. Its position is assumed to be known
  160. * and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
  161. * of known entities by being told about them, hearing them, etc.
  162. */
  163. void IVision::AddKnownEntity( CBaseEntity *entity )
  164. {
  165. CKnownEntity known( entity );
  166. // only add it if we don't already know of it
  167. if ( m_knownEntityVector.Find( known ) == m_knownEntityVector.InvalidIndex() )
  168. {
  169. m_knownEntityVector.AddToTail( known );
  170. }
  171. }
  172. //------------------------------------------------------------------------------------------
  173. /**
  174. * Return the number of entity on the given team known to us closer than rangeLimit
  175. */
  176. int IVision::GetKnownCount( int team, bool onlyVisible, float rangeLimit ) const
  177. {
  178. int count = 0;
  179. FOR_EACH_VEC( m_knownEntityVector, it )
  180. {
  181. const CKnownEntity &known = m_knownEntityVector[ it ];
  182. if ( !known.IsObsolete() && IsAwareOf( known ) )
  183. {
  184. if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
  185. {
  186. if ( !onlyVisible || known.IsVisible() )
  187. {
  188. if ( rangeLimit < 0.0f || GetBot()->IsRangeLessThan( known.GetLastKnownPosition(), rangeLimit ) )
  189. {
  190. ++count;
  191. }
  192. }
  193. }
  194. }
  195. }
  196. return count;
  197. }
  198. //------------------------------------------------------------------------------------------
  199. class PopulateVisibleVector
  200. {
  201. public:
  202. PopulateVisibleVector( CUtlVector< CBaseEntity * > *potentiallyVisible )
  203. {
  204. m_potentiallyVisible = potentiallyVisible;
  205. }
  206. bool operator() ( CBaseEntity *actor )
  207. {
  208. m_potentiallyVisible->AddToTail( actor );
  209. return true;
  210. }
  211. CUtlVector< CBaseEntity * > *m_potentiallyVisible;
  212. };
  213. //------------------------------------------------------------------------------------------
  214. /**
  215. * Populate "potentiallyVisible" with the set of all entities we could potentially see.
  216. * Entities in this set will be tested for visibility/recognition in IVision::Update()
  217. */
  218. void IVision::CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible )
  219. {
  220. potentiallyVisible->RemoveAll();
  221. // by default, only consider players and other bots as potentially visible
  222. PopulateVisibleVector populate( potentiallyVisible );
  223. ForEachActor( populate );
  224. }
  225. //------------------------------------------------------------------------------------------
  226. class CollectVisible
  227. {
  228. public:
  229. CollectVisible( IVision *vision )
  230. {
  231. m_vision = vision;
  232. }
  233. bool operator() ( CBaseEntity *entity )
  234. {
  235. if ( entity &&
  236. !m_vision->IsIgnored( entity ) &&
  237. entity->IsAlive() &&
  238. entity != m_vision->GetBot()->GetEntity() &&
  239. m_vision->IsAbleToSee( entity, IVision::USE_FOV ) )
  240. {
  241. m_recognized.AddToTail( entity );
  242. }
  243. return true;
  244. }
  245. bool Contains( CBaseEntity *entity ) const
  246. {
  247. for( int i=0; i < m_recognized.Count(); ++i )
  248. {
  249. if ( entity->entindex() == m_recognized[ i ]->entindex() )
  250. {
  251. return true;
  252. }
  253. }
  254. return false;
  255. }
  256. IVision *m_vision;
  257. CUtlVector< CBaseEntity * > m_recognized;
  258. };
  259. //------------------------------------------------------------------------------------------
  260. void IVision::UpdateKnownEntities( void )
  261. {
  262. VPROF_BUDGET( "IVision::UpdateKnownEntities", "NextBot" );
  263. // construct set of potentially visible objects
  264. CUtlVector< CBaseEntity * > potentiallyVisible;
  265. CollectPotentiallyVisibleEntities( &potentiallyVisible );
  266. // collect set of visible and recognized entities at this moment
  267. CollectVisible visibleNow( this );
  268. FOR_EACH_VEC( potentiallyVisible, pit )
  269. {
  270. VPROF_BUDGET( "IVision::UpdateKnownEntities( collect visible )", "NextBot" );
  271. if ( visibleNow( potentiallyVisible[ pit ] ) == false )
  272. break;
  273. }
  274. // update known set with new data
  275. { VPROF_BUDGET( "IVision::UpdateKnownEntities( update status )", "NextBot" );
  276. int i;
  277. for( i=0; i < m_knownEntityVector.Count(); ++i )
  278. {
  279. CKnownEntity &known = m_knownEntityVector[i];
  280. // clear out obsolete knowledge
  281. if ( known.GetEntity() == NULL || known.IsObsolete() )
  282. {
  283. m_knownEntityVector.Remove( i );
  284. --i;
  285. continue;
  286. }
  287. if ( visibleNow.Contains( known.GetEntity() ) )
  288. {
  289. // this visible entity was already known (but perhaps not visible until now)
  290. known.UpdatePosition();
  291. known.UpdateVisibilityStatus( true );
  292. // has our reaction time just elapsed?
  293. if ( gpGlobals->curtime - known.GetTimeWhenBecameVisible() >= GetMinRecognizeTime() &&
  294. m_lastVisionUpdateTimestamp - known.GetTimeWhenBecameVisible() < GetMinRecognizeTime() )
  295. {
  296. if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
  297. {
  298. ConColorMsg( Color( 0, 255, 0, 255 ), "%3.2f: %s caught sight of %s(#%d)\n",
  299. gpGlobals->curtime,
  300. GetBot()->GetDebugIdentifier(),
  301. known.GetEntity()->GetClassname(),
  302. known.GetEntity()->entindex() );
  303. NDebugOverlay::Line( GetBot()->GetBodyInterface()->GetEyePosition(), known.GetLastKnownPosition(), 255, 255, 0, false, 0.2f );
  304. }
  305. GetBot()->OnSight( known.GetEntity() );
  306. }
  307. // restart 'not seen' timer
  308. m_notVisibleTimer[ known.GetEntity()->GetTeamNumber() ].Start();
  309. }
  310. else // known entity is not currently visible
  311. {
  312. if ( known.IsVisible() )
  313. {
  314. // previously known and visible entity is now no longer visible
  315. known.UpdateVisibilityStatus( false );
  316. // lost sight of this entity
  317. if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
  318. {
  319. ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Lost sight of %s(#%d)\n",
  320. gpGlobals->curtime,
  321. GetBot()->GetDebugIdentifier(),
  322. known.GetEntity()->GetClassname(),
  323. known.GetEntity()->entindex() );
  324. }
  325. GetBot()->OnLostSight( known.GetEntity() );
  326. }
  327. if ( !known.HasLastKnownPositionBeenSeen() )
  328. {
  329. // can we see the entity's last know position?
  330. if ( IsAbleToSee( known.GetLastKnownPosition(), IVision::USE_FOV ) )
  331. {
  332. known.MarkLastKnownPositionAsSeen();
  333. }
  334. }
  335. }
  336. }
  337. }
  338. // check for new recognizes that were not in the known set
  339. { VPROF_BUDGET( "IVision::UpdateKnownEntities( new recognizes )", "NextBot" );
  340. int i, j;
  341. for( i=0; i < visibleNow.m_recognized.Count(); ++i )
  342. {
  343. for( j=0; j < m_knownEntityVector.Count(); ++j )
  344. {
  345. if ( visibleNow.m_recognized[i] == m_knownEntityVector[j].GetEntity() )
  346. {
  347. break;
  348. }
  349. }
  350. if ( j == m_knownEntityVector.Count() )
  351. {
  352. // recognized a previously unknown entity (emit OnSight() event after reaction time has passed)
  353. CKnownEntity known( visibleNow.m_recognized[i] );
  354. known.UpdatePosition();
  355. known.UpdateVisibilityStatus( true );
  356. m_knownEntityVector.AddToTail( known );
  357. }
  358. }
  359. }
  360. // debugging
  361. if ( nb_debug_known_entities.GetBool() )
  362. {
  363. CBasePlayer *watcher = UTIL_GetListenServerHost();
  364. if ( watcher )
  365. {
  366. CBaseEntity *subject = watcher->GetObserverTarget();
  367. if ( subject && GetBot()->IsSelf( subject ) )
  368. {
  369. for( int i=0; i < m_knownEntityVector.Count(); ++i )
  370. {
  371. CKnownEntity &known = m_knownEntityVector[i];
  372. if ( GetBot()->IsFriend( known.GetEntity() ) )
  373. {
  374. if ( known.IsVisible() )
  375. NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 0, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  376. else
  377. NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 0, 100, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  378. }
  379. else
  380. {
  381. if ( known.IsVisible() )
  382. NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 255, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  383. else
  384. NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 100, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  385. }
  386. }
  387. }
  388. }
  389. }
  390. }
  391. //------------------------------------------------------------------------------------------
  392. /**
  393. * Update internal state
  394. */
  395. void IVision::Update( void )
  396. {
  397. VPROF_BUDGET( "IVision::Update", "NextBotExpensive" );
  398. // throttle update rate
  399. if ( !m_scanTimer.IsElapsed() )
  400. {
  401. return;
  402. }
  403. m_scanTimer.Start( 0.5f * GetMinRecognizeTime() );
  404. if ( nb_blind.GetBool() )
  405. {
  406. m_knownEntityVector.RemoveAll();
  407. return;
  408. }
  409. UpdateKnownEntities();
  410. m_lastVisionUpdateTimestamp = gpGlobals->curtime;
  411. }
  412. //------------------------------------------------------------------------------------------
  413. bool IVision::IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot ) const
  414. {
  415. VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
  416. if ( GetBot()->IsRangeGreaterThan( subject, GetMaxVisionRange() ) )
  417. {
  418. return false;
  419. }
  420. if ( GetBot()->GetEntity()->IsHiddenByFog( subject ) )
  421. {
  422. // lost in the fog
  423. return false;
  424. }
  425. if ( checkFOV == USE_FOV && !IsInFieldOfView( subject ) )
  426. {
  427. return false;
  428. }
  429. if ( !IsNoticed( subject ) )
  430. {
  431. return false;
  432. }
  433. // do actual line-of-sight trace
  434. return IsLineOfSightClearToEntity( subject );
  435. }
  436. //------------------------------------------------------------------------------------------
  437. bool IVision::IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const
  438. {
  439. VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
  440. if ( GetBot()->IsRangeGreaterThan( pos, GetMaxVisionRange() ) )
  441. {
  442. return false;
  443. }
  444. if ( GetBot()->GetEntity()->IsHiddenByFog( pos ) )
  445. {
  446. // lost in the fog
  447. return false;
  448. }
  449. if ( checkFOV == USE_FOV && !IsInFieldOfView( pos ) )
  450. {
  451. return false;
  452. }
  453. // do actual line-of-sight trace
  454. return IsLineOfSightClear( pos );
  455. }
  456. //------------------------------------------------------------------------------------------
  457. /**
  458. * Angle given in degrees
  459. */
  460. void IVision::SetFieldOfView( float horizAngle )
  461. {
  462. m_FOV = horizAngle;
  463. m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
  464. }
  465. //------------------------------------------------------------------------------------------
  466. bool IVision::IsInFieldOfView( const Vector &pos ) const
  467. {
  468. #ifdef CHECK_OLD_CODE_AGAINST_NEW
  469. bool bCheck = PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
  470. Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
  471. to.NormalizeInPlace();
  472. float cosDiff = DotProduct( GetBot()->GetBodyInterface()->GetViewVector(), to );
  473. if ( ( cosDiff > m_cosHalfFOV ) != bCheck )
  474. {
  475. Assert(0);
  476. bool bCheck2 =
  477. PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
  478. }
  479. return ( cosDiff > m_cosHalfFOV );
  480. #else
  481. return PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
  482. #endif
  483. return true;
  484. }
  485. //------------------------------------------------------------------------------------------
  486. bool IVision::IsInFieldOfView( CBaseEntity *subject ) const
  487. {
  488. /// @todo check more points
  489. if ( IsInFieldOfView( subject->WorldSpaceCenter() ) )
  490. {
  491. return true;
  492. }
  493. return IsInFieldOfView( subject->EyePosition() );
  494. }
  495. //------------------------------------------------------------------------------------------
  496. /**
  497. * Return true if the ray to the given point is unobstructed
  498. */
  499. bool IVision::IsLineOfSightClear( const Vector &pos ) const
  500. {
  501. VPROF_BUDGET( "IVision::IsLineOfSightClear", "NextBot" );
  502. VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClear", 1 );
  503. trace_t result;
  504. NextBotVisionTraceFilter filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
  505. UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), pos, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
  506. return ( result.fraction >= 1.0f && !result.startsolid );
  507. }
  508. //------------------------------------------------------------------------------------------
  509. bool IVision::IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot ) const
  510. {
  511. #ifdef TERROR
  512. // TODO: Integration querycache & its dependencies
  513. VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClearToEntity", 1 );
  514. VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBotSpiky" );
  515. bool bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetBodyInterface()->GetEntity(), EOFFSET_MODE_EYEPOSITION,
  516. subject, EOFFSET_MODE_WORLDSPACE_CENTER,
  517. subject, COLLISION_GROUP_NONE,
  518. MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, VisionTraceFilterFunction, 1.0 );
  519. #ifdef USE_NON_CACHE_QUERY
  520. trace_t result;
  521. NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
  522. UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
  523. Assert( result.DidHit() != bClear );
  524. if ( subject->IsPlayer() && ! bClear )
  525. {
  526. UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
  527. bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetEntity(),
  528. EOFFSET_MODE_EYEPOSITION,
  529. subject, EOFFSET_MODE_EYEPOSITION,
  530. subject, COLLISION_GROUP_NONE,
  531. MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE,
  532. IgnoreActorsTraceFilterFunction, 1.0 );
  533. // this WILL assert - the query interface happens at a different time, and has hysteresis.
  534. Assert( result.DidHit() != bClear );
  535. }
  536. #endif
  537. return bClear;
  538. #else
  539. // TODO: Use plain-old traces until querycache/etc gets integrated
  540. VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBot" );
  541. trace_t result;
  542. NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
  543. UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
  544. if ( result.DidHit() )
  545. {
  546. UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
  547. if ( result.DidHit() )
  548. {
  549. UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->GetAbsOrigin(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
  550. }
  551. }
  552. if ( visibleSpot )
  553. {
  554. *visibleSpot = result.endpos;
  555. }
  556. return ( result.fraction >= 1.0f && !result.startsolid );
  557. #endif
  558. }
  559. //------------------------------------------------------------------------------------------
  560. /**
  561. * Are we looking directly at the given position
  562. */
  563. bool IVision::IsLookingAt( const Vector &pos, float cosTolerance ) const
  564. {
  565. Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
  566. to.NormalizeInPlace();
  567. Vector forward;
  568. AngleVectors( GetBot()->GetEntity()->EyeAngles(), &forward );
  569. return DotProduct( to, forward ) > cosTolerance;
  570. }
  571. //------------------------------------------------------------------------------------------
  572. /**
  573. * Are we looking directly at the given actor
  574. */
  575. bool IVision::IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance ) const
  576. {
  577. return IsLookingAt( actor->EyePosition(), cosTolerance );
  578. }