summaryrefslogtreecommitdiff
path: root/game/server/NextBot/NextBotVisionInterface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/NextBot/NextBotVisionInterface.cpp')
-rw-r--r--game/server/NextBot/NextBotVisionInterface.cpp802
1 files changed, 802 insertions, 0 deletions
diff --git a/game/server/NextBot/NextBotVisionInterface.cpp b/game/server/NextBot/NextBotVisionInterface.cpp
new file mode 100644
index 0000000..1c9d661
--- /dev/null
+++ b/game/server/NextBot/NextBotVisionInterface.cpp
@@ -0,0 +1,802 @@
+// NextBotVisionInterface.cpp
+// Implementation of common vision system
+// Author: Michael Booth, May 2006
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#include "cbase.h"
+
+#include "nav.h"
+#include "functorutils.h"
+
+#include "NextBot.h"
+#include "NextBotVisionInterface.h"
+#include "NextBotBodyInterface.h"
+#include "NextBotUtil.h"
+
+#ifdef TERROR
+#include "querycache.h"
+#endif
+
+#include "tier0/vprof.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+ConVar nb_blind( "nb_blind", "0", FCVAR_CHEAT, "Disable vision" );
+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" );
+
+
+//------------------------------------------------------------------------------------------
+IVision::IVision( INextBot *bot ) : INextBotComponent( bot )
+{
+ Reset();
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Reset to initial state
+ */
+void IVision::Reset( void )
+{
+ INextBotComponent::Reset();
+
+ m_knownEntityVector.RemoveAll();
+ m_lastVisionUpdateTimestamp = 0.0f;
+ m_primaryThreat = NULL;
+
+ m_FOV = GetDefaultFieldOfView();
+ m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
+
+ for( int i=0; i<MAX_TEAMS; ++i )
+ {
+ m_notVisibleTimer[i].Invalidate();
+ }
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Ask the current behavior to select the most dangerous threat from
+ * our set of currently known entities
+ * TODO: Find a semantically better place for this to live.
+ */
+const CKnownEntity *IVision::GetPrimaryKnownThreat( bool onlyVisibleThreats ) const
+{
+ if ( m_knownEntityVector.Count() == 0 )
+ return NULL;
+
+ const CKnownEntity *threat = NULL;
+ int i;
+
+ // find the first valid entity
+ for( i=0; i<m_knownEntityVector.Count(); ++i )
+ {
+ const CKnownEntity &firstThreat = m_knownEntityVector[i];
+
+ // check in case status changes between updates
+ if ( IsAwareOf( firstThreat ) && !firstThreat.IsObsolete() && !IsIgnored( firstThreat.GetEntity() ) && GetBot()->IsEnemy( firstThreat.GetEntity() ) )
+ {
+ if ( !onlyVisibleThreats || firstThreat.IsVisibleRecently() )
+ {
+ threat = &firstThreat;
+ break;
+ }
+ }
+ }
+
+ if ( threat == NULL )
+ {
+ m_primaryThreat = NULL;
+ return NULL;
+ }
+
+ for( ++i; i<m_knownEntityVector.Count(); ++i )
+ {
+ const CKnownEntity &newThreat = m_knownEntityVector[i];
+
+ // check in case status changes between updates
+ if ( IsAwareOf( newThreat ) && !newThreat.IsObsolete() && !IsIgnored( newThreat.GetEntity() ) && GetBot()->IsEnemy( newThreat.GetEntity() ) )
+ {
+ if ( !onlyVisibleThreats || newThreat.IsVisibleRecently() )
+ {
+ threat = GetBot()->GetIntentionInterface()->SelectMoreDangerousThreat( GetBot(), GetBot()->GetEntity(), threat, &newThreat );
+ }
+ }
+ }
+
+ // cache off threat
+ m_primaryThreat = threat ? threat->GetEntity() : NULL;
+
+ return threat;
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Return the closest recognized entity
+ */
+const CKnownEntity *IVision::GetClosestKnown( int team ) const
+{
+ const Vector &myPos = GetBot()->GetPosition();
+
+ const CKnownEntity *close = NULL;
+ float closeRange = 999999999.9f;
+
+ for( int i=0; i < m_knownEntityVector.Count(); ++i )
+ {
+ const CKnownEntity &known = m_knownEntityVector[i];
+
+ if ( !known.IsObsolete() && IsAwareOf( known ) )
+ {
+ if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
+ {
+ Vector to = known.GetLastKnownPosition() - myPos;
+ float rangeSq = to.LengthSqr();
+
+ if ( rangeSq < closeRange )
+ {
+ close = &known;
+ closeRange = rangeSq;
+ }
+ }
+ }
+ }
+
+ return close;
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Return the closest recognized entity that passes the given filter
+ */
+const CKnownEntity *IVision::GetClosestKnown( const INextBotEntityFilter &filter ) const
+{
+ const Vector &myPos = GetBot()->GetPosition();
+
+ const CKnownEntity *close = NULL;
+ float closeRange = 999999999.9f;
+
+ for( int i=0; i < m_knownEntityVector.Count(); ++i )
+ {
+ const CKnownEntity &known = m_knownEntityVector[i];
+
+ if ( !known.IsObsolete() && IsAwareOf( known ) )
+ {
+ if ( filter.IsAllowed( known.GetEntity() ) )
+ {
+ Vector to = known.GetLastKnownPosition() - myPos;
+ float rangeSq = to.LengthSqr();
+
+ if ( rangeSq < closeRange )
+ {
+ close = &known;
+ closeRange = rangeSq;
+ }
+ }
+ }
+ }
+
+ return close;
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Given an entity, return our known version of it (or NULL if we don't know of it)
+ */
+const CKnownEntity *IVision::GetKnown( const CBaseEntity *entity ) const
+{
+ if ( entity == NULL )
+ return NULL;
+
+ for( int i=0; i < m_knownEntityVector.Count(); ++i )
+ {
+ const CKnownEntity &known = m_knownEntityVector[i];
+
+ if ( known.GetEntity() && known.GetEntity()->entindex() == entity->entindex() && !known.IsObsolete() )
+ {
+ return &known;
+ }
+ }
+
+ return NULL;
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Introduce a known entity into the system. Its position is assumed to be known
+ * and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
+ * of known entities by being told about them, hearing them, etc.
+ */
+void IVision::AddKnownEntity( CBaseEntity *entity )
+{
+ if ( entity == NULL || entity->IsWorld() )
+ {
+ // the world is not an entity we can deal with
+ return;
+ }
+
+ CKnownEntity known( entity );
+
+ // only add it if we don't already know of it
+ if ( m_knownEntityVector.Find( known ) == m_knownEntityVector.InvalidIndex() )
+ {
+ m_knownEntityVector.AddToTail( known );
+ }
+}
+
+
+//------------------------------------------------------------------------------------------
+// Remove the given entity from our awareness (whether we know if it or not)
+// Useful if we've moved to where we last saw the entity, but it's not there any longer.
+void IVision::ForgetEntity( CBaseEntity *forgetMe )
+{
+ if ( !forgetMe )
+ return;
+
+ FOR_EACH_VEC( m_knownEntityVector, it )
+ {
+ const CKnownEntity &known = m_knownEntityVector[ it ];
+
+ if ( known.GetEntity() && known.GetEntity()->entindex() == forgetMe->entindex() )
+ {
+ m_knownEntityVector.FastRemove( it );
+ return;
+ }
+ }
+}
+
+
+//------------------------------------------------------------------------------------------
+void IVision::ForgetAllKnownEntities( void )
+{
+ m_knownEntityVector.RemoveAll();
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Return the number of entity on the given team known to us closer than rangeLimit
+ */
+int IVision::GetKnownCount( int team, bool onlyVisible, float rangeLimit ) const
+{
+ int count = 0;
+
+ FOR_EACH_VEC( m_knownEntityVector, it )
+ {
+ const CKnownEntity &known = m_knownEntityVector[ it ];
+
+ if ( !known.IsObsolete() && IsAwareOf( known ) )
+ {
+ if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
+ {
+ if ( !onlyVisible || known.IsVisibleRecently() )
+ {
+ if ( rangeLimit < 0.0f || GetBot()->IsRangeLessThan( known.GetLastKnownPosition(), rangeLimit ) )
+ {
+ ++count;
+ }
+ }
+ }
+ }
+ }
+
+ return count;
+}
+
+
+//------------------------------------------------------------------------------------------
+class PopulateVisibleVector
+{
+public:
+ PopulateVisibleVector( CUtlVector< CBaseEntity * > *potentiallyVisible )
+ {
+ m_potentiallyVisible = potentiallyVisible;
+ }
+
+ bool operator() ( CBaseEntity *actor )
+ {
+ m_potentiallyVisible->AddToTail( actor );
+ return true;
+ }
+
+ CUtlVector< CBaseEntity * > *m_potentiallyVisible;
+};
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Populate "potentiallyVisible" with the set of all entities we could potentially see.
+ * Entities in this set will be tested for visibility/recognition in IVision::Update()
+ */
+void IVision::CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible )
+{
+ potentiallyVisible->RemoveAll();
+
+ // by default, only consider players and other bots as potentially visible
+ PopulateVisibleVector populate( potentiallyVisible );
+ ForEachActor( populate );
+}
+
+
+//------------------------------------------------------------------------------------------
+class CollectVisible
+{
+public:
+ CollectVisible( IVision *vision )
+ {
+ m_vision = vision;
+ }
+
+ bool operator() ( CBaseEntity *entity )
+ {
+ if ( entity &&
+ !m_vision->IsIgnored( entity ) &&
+ entity->IsAlive() &&
+ entity != m_vision->GetBot()->GetEntity() &&
+ m_vision->IsAbleToSee( entity, IVision::USE_FOV ) )
+ {
+ m_recognized.AddToTail( entity );
+ }
+
+ return true;
+ }
+
+ bool Contains( CBaseEntity *entity ) const
+ {
+ for( int i=0; i < m_recognized.Count(); ++i )
+ {
+ if ( entity->entindex() == m_recognized[ i ]->entindex() )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ IVision *m_vision;
+ CUtlVector< CBaseEntity * > m_recognized;
+};
+
+
+//------------------------------------------------------------------------------------------
+void IVision::UpdateKnownEntities( void )
+{
+ VPROF_BUDGET( "IVision::UpdateKnownEntities", "NextBot" );
+
+ // construct set of potentially visible objects
+ CUtlVector< CBaseEntity * > potentiallyVisible;
+ CollectPotentiallyVisibleEntities( &potentiallyVisible );
+
+ // collect set of visible and recognized entities at this moment
+ CollectVisible visibleNow( this );
+ FOR_EACH_VEC( potentiallyVisible, pit )
+ {
+ VPROF_BUDGET( "IVision::UpdateKnownEntities( collect visible )", "NextBot" );
+
+ if ( visibleNow( potentiallyVisible[ pit ] ) == false )
+ break;
+ }
+
+ // update known set with new data
+ { VPROF_BUDGET( "IVision::UpdateKnownEntities( update status )", "NextBot" );
+
+ int i;
+ for( i=0; i < m_knownEntityVector.Count(); ++i )
+ {
+ CKnownEntity &known = m_knownEntityVector[i];
+
+ // clear out obsolete knowledge
+ if ( known.GetEntity() == NULL || known.IsObsolete() )
+ {
+ m_knownEntityVector.Remove( i );
+ --i;
+ continue;
+ }
+
+ if ( visibleNow.Contains( known.GetEntity() ) )
+ {
+ // this visible entity was already known (but perhaps not visible until now)
+ known.UpdatePosition();
+ known.UpdateVisibilityStatus( true );
+
+ // has our reaction time just elapsed?
+ if ( gpGlobals->curtime - known.GetTimeWhenBecameVisible() >= GetMinRecognizeTime() &&
+ m_lastVisionUpdateTimestamp - known.GetTimeWhenBecameVisible() < GetMinRecognizeTime() )
+ {
+ if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
+ {
+ ConColorMsg( Color( 0, 255, 0, 255 ), "%3.2f: %s caught sight of %s(#%d)\n",
+ gpGlobals->curtime,
+ GetBot()->GetDebugIdentifier(),
+ known.GetEntity()->GetClassname(),
+ known.GetEntity()->entindex() );
+
+ NDebugOverlay::Line( GetBot()->GetBodyInterface()->GetEyePosition(), known.GetLastKnownPosition(), 255, 255, 0, false, 0.2f );
+ }
+
+ GetBot()->OnSight( known.GetEntity() );
+ }
+
+ // restart 'not seen' timer
+ m_notVisibleTimer[ known.GetEntity()->GetTeamNumber() ].Start();
+ }
+ else // known entity is not currently visible
+ {
+ if ( known.IsVisibleInFOVNow() )
+ {
+ // previously known and visible entity is now no longer visible
+ known.UpdateVisibilityStatus( false );
+
+ // lost sight of this entity
+ if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
+ {
+ ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Lost sight of %s(#%d)\n",
+ gpGlobals->curtime,
+ GetBot()->GetDebugIdentifier(),
+ known.GetEntity()->GetClassname(),
+ known.GetEntity()->entindex() );
+ }
+
+ GetBot()->OnLostSight( known.GetEntity() );
+ }
+
+ if ( !known.HasLastKnownPositionBeenSeen() )
+ {
+ // can we see the entity's last know position?
+ if ( IsAbleToSee( known.GetLastKnownPosition(), IVision::USE_FOV ) )
+ {
+ known.MarkLastKnownPositionAsSeen();
+ }
+ }
+ }
+ }
+ }
+
+ // check for new recognizes that were not in the known set
+ { VPROF_BUDGET( "IVision::UpdateKnownEntities( new recognizes )", "NextBot" );
+
+ int i, j;
+ for( i=0; i < visibleNow.m_recognized.Count(); ++i )
+ {
+ for( j=0; j < m_knownEntityVector.Count(); ++j )
+ {
+ if ( visibleNow.m_recognized[i] == m_knownEntityVector[j].GetEntity() )
+ {
+ break;
+ }
+ }
+
+ if ( j == m_knownEntityVector.Count() )
+ {
+ // recognized a previously unknown entity (emit OnSight() event after reaction time has passed)
+ CKnownEntity known( visibleNow.m_recognized[i] );
+ known.UpdatePosition();
+ known.UpdateVisibilityStatus( true );
+ m_knownEntityVector.AddToTail( known );
+ }
+ }
+ }
+
+ // debugging
+ if ( nb_debug_known_entities.GetBool() )
+ {
+ CBasePlayer *watcher = UTIL_GetListenServerHost();
+ if ( watcher )
+ {
+ CBaseEntity *subject = watcher->GetObserverTarget();
+
+ if ( subject && GetBot()->IsSelf( subject ) )
+ {
+ CUtlVector< CKnownEntity > knownVector;
+ CollectKnownEntities( &knownVector );
+
+ for( int i=0; i < knownVector.Count(); ++i )
+ {
+ CKnownEntity &known = knownVector[i];
+
+ if ( GetBot()->IsFriend( known.GetEntity() ) )
+ {
+ if ( IsAwareOf( known ) )
+ {
+ if ( known.IsVisibleInFOVNow() )
+ NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 0, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ else
+ NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 0, 100, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ else
+ {
+ NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 1.0f, 0, 100, 0, 128, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ }
+ else
+ {
+ if ( IsAwareOf( known ) )
+ {
+ if ( known.IsVisibleInFOVNow() )
+ NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 255, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ else
+ NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 100, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ else
+ {
+ NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 1.0f, 100, 0, 0, 128, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Update internal state
+ */
+void IVision::Update( void )
+{
+ VPROF_BUDGET( "IVision::Update", "NextBotExpensive" );
+
+/* This adds significantly to bot's reaction times
+ // throttle update rate
+ if ( !m_scanTimer.IsElapsed() )
+ {
+ return;
+ }
+
+ m_scanTimer.Start( 0.5f * GetMinRecognizeTime() );
+*/
+
+ if ( nb_blind.GetBool() )
+ {
+ m_knownEntityVector.RemoveAll();
+ return;
+ }
+
+ UpdateKnownEntities();
+
+ m_lastVisionUpdateTimestamp = gpGlobals->curtime;
+}
+
+
+//------------------------------------------------------------------------------------------
+bool IVision::IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot ) const
+{
+ VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
+
+ if ( GetBot()->IsRangeGreaterThan( subject, GetMaxVisionRange() ) )
+ {
+ return false;
+ }
+
+
+ if ( GetBot()->GetEntity()->IsHiddenByFog( subject ) )
+ {
+ // lost in the fog
+ return false;
+ }
+
+ if ( checkFOV == USE_FOV && !IsInFieldOfView( subject ) )
+ {
+ return false;
+ }
+
+ CBaseCombatCharacter *combat = subject->MyCombatCharacterPointer();
+ if ( combat )
+ {
+ CNavArea *subjectArea = combat->GetLastKnownArea();
+ CNavArea *myArea = GetBot()->GetEntity()->GetLastKnownArea();
+ if ( myArea && subjectArea )
+ {
+ if ( !myArea->IsPotentiallyVisible( subjectArea ) )
+ {
+ // subject is not potentially visible, skip the expensive raycast
+ return false;
+ }
+ }
+ }
+
+ // do actual line-of-sight trace
+ if ( !IsLineOfSightClearToEntity( subject ) )
+ {
+ return false;
+ }
+
+ return IsVisibleEntityNoticed( subject );
+}
+
+
+//------------------------------------------------------------------------------------------
+bool IVision::IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const
+{
+ VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
+
+
+ if ( GetBot()->IsRangeGreaterThan( pos, GetMaxVisionRange() ) )
+ {
+ return false;
+ }
+
+ if ( GetBot()->GetEntity()->IsHiddenByFog( pos ) )
+ {
+ // lost in the fog
+ return false;
+ }
+
+ if ( checkFOV == USE_FOV && !IsInFieldOfView( pos ) )
+ {
+ return false;
+ }
+
+ // do actual line-of-sight trace
+ return IsLineOfSightClear( pos );
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Angle given in degrees
+ */
+void IVision::SetFieldOfView( float horizAngle )
+{
+ m_FOV = horizAngle;
+ m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
+}
+
+
+//------------------------------------------------------------------------------------------
+bool IVision::IsInFieldOfView( const Vector &pos ) const
+{
+#ifdef CHECK_OLD_CODE_AGAINST_NEW
+ bool bCheck = PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
+ Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
+ to.NormalizeInPlace();
+
+ float cosDiff = DotProduct( GetBot()->GetBodyInterface()->GetViewVector(), to );
+
+ if ( ( cosDiff > m_cosHalfFOV ) != bCheck )
+ {
+ Assert(0);
+ bool bCheck2 =
+ PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
+
+ }
+
+ return ( cosDiff > m_cosHalfFOV );
+#else
+ return PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
+#endif
+
+ return true;
+}
+
+
+//------------------------------------------------------------------------------------------
+bool IVision::IsInFieldOfView( CBaseEntity *subject ) const
+{
+ /// @todo check more points
+ if ( IsInFieldOfView( subject->WorldSpaceCenter() ) )
+ {
+ return true;
+ }
+
+ return IsInFieldOfView( subject->EyePosition() );
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Return true if the ray to the given point is unobstructed
+ */
+bool IVision::IsLineOfSightClear( const Vector &pos ) const
+{
+ VPROF_BUDGET( "IVision::IsLineOfSightClear", "NextBot" );
+ VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClear", 1 );
+
+ trace_t result;
+ NextBotVisionTraceFilter filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
+
+ UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), pos, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+
+ return ( result.fraction >= 1.0f && !result.startsolid );
+}
+
+
+//------------------------------------------------------------------------------------------
+bool IVision::IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot ) const
+{
+#ifdef TERROR
+ // TODO: Integration querycache & its dependencies
+
+ VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClearToEntity", 1 );
+ VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBotSpiky" );
+
+ bool bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetBodyInterface()->GetEntity(), EOFFSET_MODE_EYEPOSITION,
+ subject, EOFFSET_MODE_WORLDSPACE_CENTER,
+ subject, COLLISION_GROUP_NONE,
+ MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, VisionTraceFilterFunction, 1.0 );
+
+#ifdef USE_NON_CACHE_QUERY
+ trace_t result;
+ NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
+
+ UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+ Assert( result.DidHit() != bClear );
+ if ( subject->IsPlayer() && ! bClear )
+ {
+ UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+ bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetEntity(),
+ EOFFSET_MODE_EYEPOSITION,
+ subject, EOFFSET_MODE_EYEPOSITION,
+ subject, COLLISION_GROUP_NONE,
+ MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE,
+ IgnoreActorsTraceFilterFunction, 1.0 );
+
+ // this WILL assert - the query interface happens at a different time, and has hysteresis.
+ Assert( result.DidHit() != bClear );
+ }
+#endif
+
+ return bClear;
+
+#else
+
+ // TODO: Use plain-old traces until querycache/etc gets integrated
+ VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBot" );
+
+ trace_t result;
+ NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
+
+ UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+ if ( result.DidHit() )
+ {
+ UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+
+ if ( result.DidHit() )
+ {
+ UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->GetAbsOrigin(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+ }
+ }
+
+ if ( visibleSpot )
+ {
+ *visibleSpot = result.endpos;
+ }
+
+ return ( result.fraction >= 1.0f && !result.startsolid );
+
+#endif
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Are we looking directly at the given position
+ */
+bool IVision::IsLookingAt( const Vector &pos, float cosTolerance ) const
+{
+ Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
+ to.NormalizeInPlace();
+
+ Vector forward;
+ AngleVectors( GetBot()->GetEntity()->EyeAngles(), &forward );
+
+ return DotProduct( to, forward ) > cosTolerance;
+}
+
+
+//------------------------------------------------------------------------------------------
+/**
+ * Are we looking directly at the given actor
+ */
+bool IVision::IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance ) const
+{
+ return IsLookingAt( actor->EyePosition(), cosTolerance );
+}
+
+