aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/ai_tacticalservices.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_tacticalservices.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_tacticalservices.cpp')
-rw-r--r--mp/src/game/server/ai_tacticalservices.cpp1588
1 files changed, 794 insertions, 794 deletions
diff --git a/mp/src/game/server/ai_tacticalservices.cpp b/mp/src/game/server/ai_tacticalservices.cpp
index 70fc643c..87399c4f 100644
--- a/mp/src/game/server/ai_tacticalservices.cpp
+++ b/mp/src/game/server/ai_tacticalservices.cpp
@@ -1,794 +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( NO_NODE, 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());
-}
-
-//-----------------------------------------------------------------------------
+//========= 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( NO_NODE, 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());
+}
+
+//-----------------------------------------------------------------------------