diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_tacticalservices.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-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.cpp | 1588 |
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()); +} + +//----------------------------------------------------------------------------- |