//========= 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()); } //-----------------------------------------------------------------------------