You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
794 lines
25 KiB
794 lines
25 KiB
//========= 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()); |
|
} |
|
|
|
//-----------------------------------------------------------------------------
|
|
|