diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/ai_tacticalservices.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/ai_tacticalservices.cpp')
| -rw-r--r-- | mp/src/game/server/ai_tacticalservices.cpp | 794 |
1 files changed, 794 insertions, 0 deletions
diff --git a/mp/src/game/server/ai_tacticalservices.cpp b/mp/src/game/server/ai_tacticalservices.cpp new file mode 100644 index 00000000..08d32699 --- /dev/null +++ b/mp/src/game/server/ai_tacticalservices.cpp @@ -0,0 +1,794 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=============================================================================
+
+#include "cbase.h"
+
+#include "bitstring.h"
+
+#include "ai_tacticalservices.h"
+#include "ai_basenpc.h"
+#include "ai_node.h"
+#include "ai_network.h"
+#include "ai_link.h"
+#include "ai_moveprobe.h"
+#include "ai_pathfinder.h"
+#include "ai_navigator.h"
+#include "ai_networkmanager.h"
+#include "ai_hint.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar ai_find_lateral_cover( "ai_find_lateral_cover", "1" );
+ConVar ai_find_lateral_los( "ai_find_lateral_los", "1" );
+
+#ifdef _DEBUG
+ConVar ai_debug_cover( "ai_debug_cover", "0" );
+int g_AIDebugFindCoverNode = -1;
+#define DebugFindCover( node, from, to, r, g, b ) \
+ if ( !ai_debug_cover.GetBool() || \
+ (g_AIDebugFindCoverNode != -1 && g_AIDebugFindCoverNode != node) || \
+ !GetOuter()->m_bSelected ) \
+ ; \
+ else \
+ NDebugOverlay::Line( from, to, r, g, b, false, 1 )
+
+#define DebugFindCover2( node, from, to, r, g, b ) \
+ if ( ai_debug_cover.GetInt() < 2 || \
+ (g_AIDebugFindCoverNode != -1 && g_AIDebugFindCoverNode != node) || \
+ !GetOuter()->m_bSelected ) \
+ ; \
+ else \
+ NDebugOverlay::Line( from, to, r, g, b, false, 1 )
+
+ConVar ai_debug_tactical_los( "ai_debug_tactical_los", "0" );
+int g_AIDebugFindLosNode = -1;
+#define ShouldDebugLos( node ) ( ai_debug_tactical_los.GetBool() && ( g_AIDebugFindLosNode == -1 || g_AIDebugFindLosNode == ( node ) ) && GetOuter()->m_bSelected )
+#else
+#define DebugFindCover( node, from, to, r, g, b ) ((void)0)
+#define DebugFindCover2( node, from, to, r, g, b ) ((void)0)
+#define ShouldDebugLos( node ) false
+#endif
+
+//-----------------------------------------------------------------------------
+
+BEGIN_SIMPLE_DATADESC(CAI_TacticalServices)
+ // m_pNetwork (not saved)
+ // m_pPathfinder (not saved)
+ DEFINE_FIELD( m_bAllowFindLateralLos, FIELD_BOOLEAN ),
+
+END_DATADESC();
+
+//-------------------------------------
+
+void CAI_TacticalServices::Init( CAI_Network *pNetwork )
+{
+ Assert( pNetwork );
+ m_pNetwork = pNetwork;
+ m_pPathfinder = GetOuter()->GetPathfinder();
+ Assert( m_pPathfinder );
+}
+
+//-------------------------------------
+
+bool CAI_TacticalServices::FindLos(const Vector &threatPos, const Vector &threatEyePos, float minThreatDist, float maxThreatDist, float blockTime, FlankType_t eFlankType, const Vector &vecFlankRefPos, float flFlankParam, Vector *pResult)
+{
+ AI_PROFILE_SCOPE( CAI_TacticalServices_FindLos );
+
+ MARK_TASK_EXPENSIVE();
+
+ int node = FindLosNode( threatPos, threatEyePos,
+ minThreatDist, maxThreatDist,
+ blockTime, eFlankType, vecFlankRefPos, flFlankParam );
+
+ if (node == NO_NODE)
+ return false;
+
+ *pResult = GetNodePos( node );
+ return true;
+}
+
+//-------------------------------------
+
+bool CAI_TacticalServices::FindLos(const Vector &threatPos, const Vector &threatEyePos, float minThreatDist, float maxThreatDist, float blockTime, Vector *pResult)
+{
+ return FindLos( threatPos, threatEyePos, minThreatDist, maxThreatDist, blockTime, FLANKTYPE_NONE, vec3_origin, 0, pResult );
+}
+
+//-------------------------------------
+
+bool CAI_TacticalServices::FindBackAwayPos( const Vector &vecThreat, Vector *pResult )
+{
+ MARK_TASK_EXPENSIVE();
+
+ Vector vMoveAway = GetAbsOrigin() - vecThreat;
+ vMoveAway.NormalizeInPlace();
+
+ if ( GetOuter()->GetNavigator()->FindVectorGoal( pResult, vMoveAway, 10*12, 10*12, true ) )
+ return true;
+
+ int node = FindBackAwayNode( vecThreat );
+
+ if (node != NO_NODE)
+ {
+ *pResult = GetNodePos( node );
+ return true;
+ }
+
+ if ( GetOuter()->GetNavigator()->FindVectorGoal( pResult, vMoveAway, GetHullWidth() * 4, GetHullWidth() * 2, true ) )
+ return true;
+
+ return false;
+}
+
+//-------------------------------------
+
+bool CAI_TacticalServices::FindCoverPos( const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist, Vector *pResult )
+{
+ return FindCoverPos( GetLocalOrigin(), vThreatPos, vThreatEyePos, flMinDist, flMaxDist, pResult );
+}
+
+//-------------------------------------
+
+bool CAI_TacticalServices::FindCoverPos( const Vector &vNearPos, const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist, Vector *pResult )
+{
+ AI_PROFILE_SCOPE( CAI_TacticalServices_FindCoverPos );
+
+ MARK_TASK_EXPENSIVE();
+
+ int node = FindCoverNode( vNearPos, vThreatPos, vThreatEyePos, flMinDist, flMaxDist );
+
+ if (node == NO_NODE)
+ return false;
+
+ *pResult = GetNodePos( node );
+ return true;
+}
+
+//-------------------------------------
+// Checks lateral cover
+//-------------------------------------
+bool CAI_TacticalServices::TestLateralCover( const Vector &vecCheckStart, const Vector &vecCheckEnd, float flMinDist )
+{
+ trace_t tr;
+
+ if ( (vecCheckStart - vecCheckEnd).LengthSqr() > Square(flMinDist) )
+ {
+ if (GetOuter()->IsCoverPosition(vecCheckStart, vecCheckEnd + GetOuter()->GetViewOffset()))
+ {
+ if ( GetOuter()->IsValidCover ( vecCheckEnd, NULL ) )
+ {
+ AIMoveTrace_t moveTrace;
+ GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND, GetLocalOrigin(), vecCheckEnd, MASK_NPCSOLID, NULL, &moveTrace );
+ if (moveTrace.fStatus == AIMR_OK)
+ {
+ DebugFindCover( NO_NODE, vecCheckEnd + GetOuter()->GetViewOffset(), vecCheckStart, 0, 255, 0 );
+ return true;
+ }
+ }
+ }
+ }
+
+ DebugFindCover( NO_NODE, vecCheckEnd + GetOuter()->GetViewOffset(), vecCheckStart, 255, 0, 0 );
+
+ return false;
+}
+
+
+//-------------------------------------
+// FindLateralCover - attempts to locate a spot in the world
+// directly to the left or right of the caller that will
+// conceal them from view of pSightEnt
+//-------------------------------------
+
+#define COVER_CHECKS 5// how many checks are made
+#define COVER_DELTA 48// distance between checks
+bool CAI_TacticalServices::FindLateralCover( const Vector &vecThreat, float flMinDist, Vector *pResult )
+{
+ return FindLateralCover( vecThreat, flMinDist, COVER_CHECKS * COVER_DELTA, COVER_CHECKS, pResult );
+}
+
+bool CAI_TacticalServices::FindLateralCover( const Vector &vecThreat, float flMinDist, float distToCheck, int numChecksPerDir, Vector *pResult )
+{
+ return FindLateralCover( GetAbsOrigin(), vecThreat, flMinDist, distToCheck, numChecksPerDir, pResult );
+}
+
+bool CAI_TacticalServices::FindLateralCover( const Vector &vNearPos, const Vector &vecThreat, float flMinDist, float distToCheck, int numChecksPerDir, Vector *pResult )
+{
+ AI_PROFILE_SCOPE( CAI_TacticalServices_FindLateralCover );
+
+ MARK_TASK_EXPENSIVE();
+
+ Vector vecLeftTest;
+ Vector vecRightTest;
+ Vector vecStepRight;
+ Vector vecCheckStart;
+ int i;
+
+ if ( TestLateralCover( vecThreat, vNearPos, flMinDist ) )
+ {
+ *pResult = GetLocalOrigin();
+ return true;
+ }
+
+ if( !ai_find_lateral_cover.GetBool() )
+ {
+ // Force the NPC to use the nodegraph to find cover. NOTE: We let the above code run
+ // to detect the case where the NPC may already be standing in cover, but we don't
+ // make any additional lateral checks.
+ return false;
+ }
+
+ Vector right = vecThreat - vNearPos;
+ float temp;
+
+ right.z = 0;
+ VectorNormalize( right );
+ temp = right.x;
+ right.x = -right.y;
+ right.y = temp;
+
+ vecStepRight = right * (distToCheck / (float)numChecksPerDir);
+ vecStepRight.z = 0;
+
+ vecLeftTest = vecRightTest = vNearPos;
+ vecCheckStart = vecThreat;
+
+ for ( i = 0 ; i < numChecksPerDir ; i++ )
+ {
+ vecLeftTest = vecLeftTest - vecStepRight;
+ vecRightTest = vecRightTest + vecStepRight;
+
+ if (TestLateralCover( vecCheckStart, vecLeftTest, flMinDist ))
+ {
+ *pResult = vecLeftTest;
+ return true;
+ }
+
+ if (TestLateralCover( vecCheckStart, vecRightTest, flMinDist ))
+ {
+ *pResult = vecRightTest;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-------------------------------------
+// Purpose: Find a nearby node that further away from the enemy than the
+// min range of my current weapon if there is one or just futher
+// away than my current location if I don't have a weapon.
+// Used to back away for attacks
+//-------------------------------------
+
+int CAI_TacticalServices::FindBackAwayNode(const Vector &vecThreat )
+{
+ if ( !CAI_NetworkManager::NetworksLoaded() )
+ {
+ DevWarning( 2, "Graph not ready for FindBackAwayNode!\n" );
+ return NO_NODE;
+ }
+
+ int iMyNode = GetPathfinder()->NearestNodeToNPC();
+ int iThreatNode = GetPathfinder()->NearestNodeToPoint( vecThreat );
+
+ if ( iMyNode == NO_NODE )
+ {
+ DevWarning( 2, "FindBackAwayNode() - %s has no nearest node!\n", GetEntClassname());
+ return NO_NODE;
+ }
+ if ( iThreatNode == NO_NODE )
+ {
+ // DevWarning( 2, "FindBackAwayNode() - Threat has no nearest node!\n" );
+ iThreatNode = iMyNode;
+ // return false;
+ }
+
+ // A vector pointing to the threat.
+ Vector vecToThreat;
+ vecToThreat = vecThreat - GetLocalOrigin();
+
+ // Get my current distance from the threat
+ float flCurDist = VectorNormalize( vecToThreat );
+
+ // Check my neighbors to find a node that's further away
+ for (int link = 0; link < GetNetwork()->GetNode(iMyNode)->NumLinks(); link++)
+ {
+ CAI_Link *nodeLink = GetNetwork()->GetNode(iMyNode)->GetLinkByIndex(link);
+
+ if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) )
+ continue;
+
+ int destID = nodeLink->DestNodeID(iMyNode);
+
+ float flTestDist = ( vecThreat - GetNetwork()->GetNode(destID)->GetPosition(GetHullType()) ).Length();
+
+ if ( flTestDist > flCurDist )
+ {
+ // Make sure this node doesn't take me past the enemy's position.
+ Vector vecToNode;
+ vecToNode = GetNetwork()->GetNode(destID)->GetPosition(GetHullType()) - GetLocalOrigin();
+ VectorNormalize( vecToNode );
+
+ if( DotProduct( vecToNode, vecToThreat ) < 0.0 )
+ {
+ return destID;
+ }
+ }
+ }
+ return NO_NODE;
+}
+
+//-------------------------------------
+// FindCover - tries to find a nearby node that will hide
+// the caller from its enemy.
+//
+// If supplied, search will return a node at least as far
+// away as MinDist, but no farther than MaxDist.
+// if MaxDist isn't supplied, it defaults to a reasonable
+// value
+//-------------------------------------
+
+int CAI_TacticalServices::FindCoverNode(const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist )
+{
+ return FindCoverNode(GetLocalOrigin(), vThreatPos, vThreatEyePos, flMinDist, flMaxDist );
+}
+
+//-------------------------------------
+
+int CAI_TacticalServices::FindCoverNode(const Vector &vNearPos, const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist )
+{
+ if ( !CAI_NetworkManager::NetworksLoaded() )
+ return NO_NODE;
+
+ AI_PROFILE_SCOPE( CAI_TacticalServices_FindCoverNode );
+
+ MARK_TASK_EXPENSIVE();
+
+ DebugFindCover( g_AIDebugFindCoverNode, GetOuter()->EyePosition(), vThreatEyePos, 0, 255, 255 );
+
+ int iMyNode = GetPathfinder()->NearestNodeToPoint( vNearPos );
+
+ if ( iMyNode == NO_NODE )
+ {
+ Vector pos = GetOuter()->GetAbsOrigin();
+ DevWarning( 2, "FindCover() - %s has no nearest node! (Check near %f %f %f)\n", GetEntClassname(), pos.x, pos.y, pos.z);
+ return NO_NODE;
+ }
+
+ if ( !flMaxDist )
+ {
+ // user didn't supply a MaxDist, so work up a crazy one.
+ flMaxDist = 784;
+ }
+
+ if ( flMinDist > 0.5 * flMaxDist)
+ {
+ flMinDist = 0.5 * flMaxDist;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // We're going to search for a cover node by expanding to our current node's neighbors
+ // and then their neighbors, until cover is found, or all nodes are beyond MaxDist
+ // ------------------------------------------------------------------------------------
+ AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * GetNetwork()->NumNodes() );
+ CNodeList list( pBuffer, GetNetwork()->NumNodes() );
+ CVarBitVec wasVisited(GetNetwork()->NumNodes()); // Nodes visited
+
+ // mark start as visited
+ list.Insert( AI_NearNode_t(iMyNode, 0) );
+ wasVisited.Set( iMyNode );
+ float flMinDistSqr = flMinDist*flMinDist;
+ float flMaxDistSqr = flMaxDist*flMaxDist;
+
+ static int nSearchRandomizer = 0; // tries to ensure the links are searched in a different order each time;
+
+ // Search until the list is empty
+ while( list.Count() )
+ {
+ // Get the node that is closest in the number of steps and remove from the list
+ int nodeIndex = list.ElementAtHead().nodeIndex;
+ list.RemoveAtHead();
+
+ CAI_Node *pNode = GetNetwork()->GetNode(nodeIndex);
+ Vector nodeOrigin = pNode->GetPosition(GetHullType());
+
+ float dist = (vNearPos - nodeOrigin).LengthSqr();
+ if (dist >= flMinDistSqr && dist < flMaxDistSqr)
+ {
+ Activity nCoverActivity = GetOuter()->GetCoverActivity( pNode->GetHint() );
+ Vector vEyePos = nodeOrigin + GetOuter()->EyeOffset(nCoverActivity);
+
+ if ( GetOuter()->IsValidCover( nodeOrigin, pNode->GetHint() ) )
+ {
+ // Check if this location will block the threat's line of sight to me
+ if (GetOuter()->IsCoverPosition(vThreatEyePos, vEyePos))
+ {
+ // --------------------------------------------------------
+ // Don't let anyone else use this node for a while
+ // --------------------------------------------------------
+ pNode->Lock( 1.0 );
+
+ if ( pNode->GetHint() && ( pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_MED || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_LOW ) )
+ {
+ if ( GetOuter()->GetHintNode() )
+ {
+ GetOuter()->GetHintNode()->Unlock(GetOuter()->GetHintDelay(GetOuter()->GetHintNode()->HintType()));
+ GetOuter()->SetHintNode( NULL );
+ }
+
+ GetOuter()->SetHintNode( pNode->GetHint() );
+ }
+
+ // The next NPC who searches should use a slight different pattern
+ nSearchRandomizer = nodeIndex;
+ DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 0, 255, 0 );
+ return nodeIndex;
+ }
+ else
+ {
+ DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 255, 0, 0 );
+ }
+ }
+ else
+ {
+ DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 0, 0, 255 );
+ }
+ }
+
+ // Add its children to the search list
+ // Go through each link
+ // UNDONE: Pass in a cost function to measure each link?
+ for ( int link = 0; link < GetNetwork()->GetNode(nodeIndex)->NumLinks(); link++ )
+ {
+ int index = (link + nSearchRandomizer) % GetNetwork()->GetNode(nodeIndex)->NumLinks();
+ CAI_Link *nodeLink = GetNetwork()->GetNode(nodeIndex)->GetLinkByIndex(index);
+
+ if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) )
+ continue;
+
+ int newID = nodeLink->DestNodeID(nodeIndex);
+
+ // If not already on the closed list, add to it and set its distance
+ if (!wasVisited.IsBitSet(newID))
+ {
+ // Don't accept climb nodes or nodes that aren't ready to use yet
+ if ( GetNetwork()->GetNode(newID)->GetType() != NODE_CLIMB && !GetNetwork()->GetNode(newID)->IsLocked() )
+ {
+ // UNDONE: Shouldn't we really accumulate the distance by path rather than
+ // absolute distance. After all, we are performing essentially an A* here.
+ nodeOrigin = GetNetwork()->GetNode(newID)->GetPosition(GetHullType());
+ dist = (vNearPos - nodeOrigin).LengthSqr();
+
+ // use distance to threat as a heuristic to keep AIs from running toward
+ // the threat in order to take cover from it.
+ float threatDist = (vThreatPos - nodeOrigin).LengthSqr();
+
+ // Now check this node is not too close towards the threat
+ if ( dist < threatDist * 1.5 )
+ {
+ list.Insert( AI_NearNode_t(newID, dist) );
+ }
+ }
+ // mark visited
+ wasVisited.Set(newID);
+ }
+ }
+ }
+
+ // We failed. Not cover node was found
+ // Clear hint node used to set ducking
+ GetOuter()->ClearHintNode();
+ return NO_NODE;
+}
+
+
+//-------------------------------------
+// Purpose: Return node ID that has line of sight to target I want to shoot
+//
+// Input : pNPC - npc that's looking for a place to shoot from
+// vThreatPos - position of entity/location I'm trying to shoot
+// vThreatEyePos - eye position of entity I'm trying to shoot. If
+// entity has no eye position, just give vThreatPos again
+// flMinThreatDist - minimum distance that node must be from vThreatPos
+// flMaxThreadDist - maximum distance that node can be from vThreadPos
+// vThreatFacing - optional argument. If given the returned node
+// will also be behind the given facing direction (flanking)
+// flBlockTime - how long to block this node from use
+// Output : int - ID number of node that meets qualifications
+//-------------------------------------
+
+int CAI_TacticalServices::FindLosNode(const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinThreatDist, float flMaxThreatDist, float flBlockTime, FlankType_t eFlankType, const Vector &vecFlankRefPos, float flFlankParam )
+{
+ if ( !CAI_NetworkManager::NetworksLoaded() )
+ return NO_NODE;
+
+ AI_PROFILE_SCOPE( CAI_TacticalServices_FindLosNode );
+
+ MARK_TASK_EXPENSIVE();
+
+ int iMyNode = GetPathfinder()->NearestNodeToNPC();
+ if ( iMyNode == NO_NODE )
+ {
+ Vector pos = GetOuter()->GetAbsOrigin();
+ DevWarning( 2, "FindCover() - %s has no nearest node! (Check near %f %f %f)\n", GetEntClassname(), pos.x, pos.y, pos.z);
+ return NO_NODE;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // We're going to search for a shoot node by expanding to our current node's neighbors
+ // and then their neighbors, until a shooting position is found, or all nodes are beyond MaxDist
+ // ------------------------------------------------------------------------------------
+ AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * GetNetwork()->NumNodes() );
+ CNodeList list( pBuffer, GetNetwork()->NumNodes() );
+ CVarBitVec wasVisited(GetNetwork()->NumNodes()); // Nodes visited
+
+ // mark start as visited
+ wasVisited.Set( iMyNode );
+ list.Insert( AI_NearNode_t(iMyNode, 0) );
+
+ static int nSearchRandomizer = 0; // tries to ensure the links are searched in a different order each time;
+
+ while ( list.Count() )
+ {
+ int nodeIndex = list.ElementAtHead().nodeIndex;
+ // remove this item from the list
+ list.RemoveAtHead();
+
+ const Vector &nodeOrigin = GetNetwork()->GetNode(nodeIndex)->GetPosition(GetHullType());
+
+ // HACKHACK: Can't we rework this loop and get rid of this?
+ // skip the starting node, or we probably wouldn't have called this function.
+ if ( nodeIndex != iMyNode )
+ {
+ bool skip = false;
+
+ // See if the node satisfies the flanking criteria.
+ switch ( eFlankType )
+ {
+ case FLANKTYPE_NONE:
+ break;
+
+ case FLANKTYPE_RADIUS:
+ {
+ Vector vecDist = nodeOrigin - vecFlankRefPos;
+ if ( vecDist.Length() < flFlankParam )
+ {
+ skip = true;
+ }
+
+ break;
+ }
+
+ case FLANKTYPE_ARC:
+ {
+ Vector vecEnemyToRef = vecFlankRefPos - vThreatPos;
+ VectorNormalize( vecEnemyToRef );
+
+ Vector vecEnemyToNode = nodeOrigin - vThreatPos;
+ VectorNormalize( vecEnemyToNode );
+
+ float flDot = DotProduct( vecEnemyToRef, vecEnemyToNode );
+
+ if ( RAD2DEG( acos( flDot ) ) < flFlankParam )
+ {
+ skip = true;
+ }
+
+ break;
+ }
+ }
+
+ // Don't accept climb nodes, and assume my nearest node isn't valid because
+ // we decided to make this check in the first place. Keep moving
+ if ( !skip && !GetNetwork()->GetNode(nodeIndex)->IsLocked() &&
+ GetNetwork()->GetNode(nodeIndex)->GetType() != NODE_CLIMB )
+ {
+ // Now check its distance and only accept if in range
+ float flThreatDist = ( nodeOrigin - vThreatPos ).Length();
+
+ if ( flThreatDist < flMaxThreatDist &&
+ flThreatDist > flMinThreatDist )
+ {
+ CAI_Node *pNode = GetNetwork()->GetNode(nodeIndex);
+ if ( GetOuter()->IsValidShootPosition( nodeOrigin, pNode, pNode->GetHint() ) )
+ {
+ if (GetOuter()->TestShootPosition(nodeOrigin,vThreatEyePos))
+ {
+ // Note when this node was used, so we don't try
+ // to use it again right away.
+ GetNetwork()->GetNode(nodeIndex)->Lock( flBlockTime );
+
+#if 0
+ if ( GetOuter()->GetHintNode() )
+ {
+ GetOuter()->GetHintNode()->Unlock(GetOuter()->GetHintDelay(GetOuter()->GetHintNode()->HintType()));
+ GetOuter()->SetHintNode( NULL );
+ }
+
+ // This used to not be set, why? (kenb)
+ // @Note (toml 05-19-04): I think because stomping the hint can lead to
+ // unintended side effects. The hint node is primarily a high level
+ // tool, and certain NPCs break if it gets slammed here. If we need
+ // this, we should propagate it out and let the schedule selector
+ // or task decide to set the hint node
+ GetOuter()->SetHintNode( GetNetwork()->GetNode(nodeIndex)->GetHint() );
+#endif
+ if ( ShouldDebugLos( nodeIndex ) )
+ {
+ NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:los!", nodeIndex), false, 1 );
+ }
+
+ // The next NPC who searches should use a slight different pattern
+ nSearchRandomizer = nodeIndex;
+ return nodeIndex;
+ }
+ else
+ {
+ if ( ShouldDebugLos( nodeIndex ) )
+ {
+ NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:!shoot", nodeIndex), false, 1 );
+ }
+ }
+ }
+ else
+ {
+ if ( ShouldDebugLos( nodeIndex ) )
+ {
+ NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:!valid", nodeIndex), false, 1 );
+ }
+ }
+ }
+ else
+ {
+ if ( ShouldDebugLos( nodeIndex ) )
+ {
+ CFmtStr msg( "%d:%s", nodeIndex, ( flThreatDist < flMaxThreatDist ) ? "too close" : "too far" );
+ NDebugOverlay::Text( nodeOrigin, msg, false, 1 );
+ }
+ }
+ }
+ }
+
+ // Go through each link and add connected nodes to the list
+ for (int link=0; link < GetNetwork()->GetNode(nodeIndex)->NumLinks();link++)
+ {
+ int index = (link + nSearchRandomizer) % GetNetwork()->GetNode(nodeIndex)->NumLinks();
+ CAI_Link *nodeLink = GetNetwork()->GetNode(nodeIndex)->GetLinkByIndex(index);
+
+ if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) )
+ continue;
+
+ int newID = nodeLink->DestNodeID(nodeIndex);
+
+ // If not already visited, add to the list
+ if (!wasVisited.IsBitSet(newID))
+ {
+ float dist = (GetLocalOrigin() - GetNetwork()->GetNode(newID)->GetPosition(GetHullType())).LengthSqr();
+ list.Insert( AI_NearNode_t(newID, dist) );
+ wasVisited.Set( newID );
+ }
+ }
+ }
+ // We failed. No range attack node node was found
+ return NO_NODE;
+}
+
+//-------------------------------------
+// Checks lateral LOS
+//-------------------------------------
+bool CAI_TacticalServices::TestLateralLos( const Vector &vecCheckStart, const Vector &vecCheckEnd )
+{
+ trace_t tr;
+
+ // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first.
+ AI_TraceLOS( vecCheckStart, vecCheckEnd + GetOuter()->GetViewOffset(), NULL, &tr );
+
+ if (tr.fraction == 1.0)
+ {
+ if ( GetOuter()->IsValidShootPosition( vecCheckEnd, NULL, NULL ) )
+ {
+ if (GetOuter()->TestShootPosition(vecCheckEnd,vecCheckStart))
+ {
+ AIMoveTrace_t moveTrace;
+ GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND, GetLocalOrigin(), vecCheckEnd, MASK_NPCSOLID, NULL, &moveTrace );
+ if (moveTrace.fStatus == AIMR_OK)
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//-------------------------------------
+
+bool CAI_TacticalServices::FindLateralLos( const Vector &vecThreat, Vector *pResult )
+{
+ AI_PROFILE_SCOPE( CAI_TacticalServices_FindLateralLos );
+
+ if( !m_bAllowFindLateralLos )
+ {
+ return false;
+ }
+
+ MARK_TASK_EXPENSIVE();
+
+ Vector vecLeftTest;
+ Vector vecRightTest;
+ Vector vecStepRight;
+ Vector vecCheckStart;
+ bool bLookingForEnemy = GetEnemy() && VectorsAreEqual(vecThreat, GetEnemy()->EyePosition(), 0.1f);
+ int i;
+
+ if( !bLookingForEnemy || GetOuter()->HasCondition(COND_SEE_ENEMY) || GetOuter()->HasCondition(COND_HAVE_ENEMY_LOS) ||
+ GetOuter()->GetTimeScheduleStarted() == gpGlobals->curtime ) // Conditions get nuked before tasks run, assume should try
+ {
+ // My current position might already be valid.
+ if ( TestLateralLos(vecThreat, GetLocalOrigin()) )
+ {
+ *pResult = GetLocalOrigin();
+ return true;
+ }
+ }
+
+ if( !ai_find_lateral_los.GetBool() )
+ {
+ // Allows us to turn off lateral LOS at the console. Allow the above code to run
+ // just in case the NPC has line of sight to begin with.
+ return false;
+ }
+
+ int iChecks = COVER_CHECKS;
+ int iDelta = COVER_DELTA;
+
+ // If we're limited in how far we're allowed to move laterally, don't bother checking past it
+ int iMaxLateralDelta = GetOuter()->GetMaxTacticalLateralMovement();
+ if ( iMaxLateralDelta != MAXTACLAT_IGNORE && iMaxLateralDelta < iDelta )
+ {
+ iChecks = 1;
+ iDelta = iMaxLateralDelta;
+ }
+
+ Vector right;
+ AngleVectors( GetLocalAngles(), NULL, &right, NULL );
+ vecStepRight = right * iDelta;
+ vecStepRight.z = 0;
+
+ vecLeftTest = vecRightTest = GetLocalOrigin();
+ vecCheckStart = vecThreat;
+
+ for ( i = 0 ; i < iChecks; i++ )
+ {
+ vecLeftTest = vecLeftTest - vecStepRight;
+ vecRightTest = vecRightTest + vecStepRight;
+
+ if (TestLateralLos( vecCheckStart, vecLeftTest ))
+ {
+ *pResult = vecLeftTest;
+ return true;
+ }
+
+ if (TestLateralLos( vecCheckStart, vecRightTest ))
+ {
+ *pResult = vecRightTest;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-------------------------------------
+
+Vector CAI_TacticalServices::GetNodePos( int node )
+{
+ return GetNetwork()->GetNode((int)node)->GetPosition(GetHullType());
+}
+
+//-----------------------------------------------------------------------------
|