Counter Strike : Global Offensive Source Code
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

  1. //========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ====
  2. //
  3. //=============================================================================
  4. #include "cbase.h"
  5. #include "bitstring.h"
  6. #include "ai_tacticalservices.h"
  7. #include "ai_basenpc.h"
  8. #include "ai_node.h"
  9. #include "ai_network.h"
  10. #include "ai_link.h"
  11. #include "ai_moveprobe.h"
  12. #include "ai_pathfinder.h"
  13. #include "ai_navigator.h"
  14. #include "ai_networkmanager.h"
  15. #include "ai_hint.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. ConVar ai_find_lateral_cover( "ai_find_lateral_cover", "1" );
  19. ConVar ai_find_lateral_los( "ai_find_lateral_los", "1" );
  20. #ifdef _DEBUG
  21. ConVar ai_debug_cover( "ai_debug_cover", "0" );
  22. int g_AIDebugFindCoverNode = -1;
  23. #define DebugFindCover( node, from, to, r, g, b ) \
  24. if ( !ai_debug_cover.GetBool() || \
  25. (g_AIDebugFindCoverNode != -1 && g_AIDebugFindCoverNode != node) || \
  26. !GetOuter()->m_bSelected ) \
  27. ; \
  28. else \
  29. NDebugOverlay::Line( from, to, r, g, b, false, 1 )
  30. #define DebugFindCover2( node, from, to, r, g, b ) \
  31. if ( ai_debug_cover.GetInt() < 2 || \
  32. (g_AIDebugFindCoverNode != -1 && g_AIDebugFindCoverNode != node) || \
  33. !GetOuter()->m_bSelected ) \
  34. ; \
  35. else \
  36. NDebugOverlay::Line( from, to, r, g, b, false, 1 )
  37. ConVar ai_debug_tactical_los( "ai_debug_tactical_los", "0" );
  38. int g_AIDebugFindLosNode = -1;
  39. #define ShouldDebugLos( node ) ( ai_debug_tactical_los.GetBool() && ( g_AIDebugFindLosNode == -1 || g_AIDebugFindLosNode == ( node ) ) && GetOuter()->m_bSelected )
  40. #else
  41. #define DebugFindCover( node, from, to, r, g, b ) ((void)0)
  42. #define DebugFindCover2( node, from, to, r, g, b ) ((void)0)
  43. #define ShouldDebugLos( node ) false
  44. #endif
  45. //-----------------------------------------------------------------------------
  46. BEGIN_SIMPLE_DATADESC(CAI_TacticalServices)
  47. // m_pNetwork (not saved)
  48. // m_pPathfinder (not saved)
  49. DEFINE_FIELD( m_bAllowFindLateralLos, FIELD_BOOLEAN ),
  50. END_DATADESC();
  51. //-------------------------------------
  52. void CAI_TacticalServices::Init( CAI_Network *pNetwork )
  53. {
  54. Assert( pNetwork );
  55. m_pNetwork = pNetwork;
  56. m_pPathfinder = GetOuter()->GetPathfinder();
  57. Assert( m_pPathfinder );
  58. }
  59. //-------------------------------------
  60. 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)
  61. {
  62. AI_PROFILE_SCOPE( CAI_TacticalServices_FindLos );
  63. MARK_TASK_EXPENSIVE();
  64. int node = FindLosNode( threatPos, threatEyePos,
  65. minThreatDist, maxThreatDist,
  66. blockTime, eFlankType, vecFlankRefPos, flFlankParam );
  67. if (node == NO_NODE)
  68. return false;
  69. *pResult = GetNodePos( node );
  70. return true;
  71. }
  72. //-------------------------------------
  73. bool CAI_TacticalServices::FindLos(const Vector &threatPos, const Vector &threatEyePos, float minThreatDist, float maxThreatDist, float blockTime, Vector *pResult)
  74. {
  75. return FindLos( threatPos, threatEyePos, minThreatDist, maxThreatDist, blockTime, FLANKTYPE_NONE, vec3_origin, 0, pResult );
  76. }
  77. //-------------------------------------
  78. bool CAI_TacticalServices::FindBackAwayPos( const Vector &vecThreat, Vector *pResult )
  79. {
  80. MARK_TASK_EXPENSIVE();
  81. Vector vMoveAway = GetAbsOrigin() - vecThreat;
  82. vMoveAway.NormalizeInPlace();
  83. if ( GetOuter()->GetNavigator()->FindVectorGoal( pResult, vMoveAway, 10*12, 10*12, true ) )
  84. return true;
  85. int node = FindBackAwayNode( vecThreat );
  86. if (node != NO_NODE)
  87. {
  88. *pResult = GetNodePos( node );
  89. return true;
  90. }
  91. if ( GetOuter()->GetNavigator()->FindVectorGoal( pResult, vMoveAway, GetHullWidth() * 4, GetHullWidth() * 2, true ) )
  92. return true;
  93. return false;
  94. }
  95. //-------------------------------------
  96. bool CAI_TacticalServices::FindCoverPos( const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist, Vector *pResult )
  97. {
  98. return FindCoverPos( GetLocalOrigin(), vThreatPos, vThreatEyePos, flMinDist, flMaxDist, pResult );
  99. }
  100. //-------------------------------------
  101. bool CAI_TacticalServices::FindCoverPos( const Vector &vNearPos, const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist, Vector *pResult )
  102. {
  103. AI_PROFILE_SCOPE( CAI_TacticalServices_FindCoverPos );
  104. MARK_TASK_EXPENSIVE();
  105. int node = FindCoverNode( vNearPos, vThreatPos, vThreatEyePos, flMinDist, flMaxDist );
  106. if (node == NO_NODE)
  107. return false;
  108. *pResult = GetNodePos( node );
  109. return true;
  110. }
  111. //-------------------------------------
  112. // Checks lateral cover
  113. //-------------------------------------
  114. bool CAI_TacticalServices::TestLateralCover( const Vector &vecCheckStart, const Vector &vecCheckEnd, float flMinDist )
  115. {
  116. trace_t tr;
  117. if ( (vecCheckStart - vecCheckEnd).LengthSqr() > Square(flMinDist) )
  118. {
  119. if (GetOuter()->IsCoverPosition(vecCheckStart, vecCheckEnd + GetOuter()->GetViewOffset()))
  120. {
  121. if ( GetOuter()->IsValidCover ( vecCheckEnd, NULL ) )
  122. {
  123. AIMoveTrace_t moveTrace;
  124. GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND, GetLocalOrigin(), vecCheckEnd, GetOuter()->GetAITraceMask(), NULL, &moveTrace );
  125. if (moveTrace.fStatus == AIMR_OK)
  126. {
  127. DebugFindCover( NO_NODE, vecCheckEnd + GetOuter()->GetViewOffset(), vecCheckStart, 0, 255, 0 );
  128. return true;
  129. }
  130. }
  131. }
  132. }
  133. DebugFindCover( NO_NODE, vecCheckEnd + GetOuter()->GetViewOffset(), vecCheckStart, 255, 0, 0 );
  134. return false;
  135. }
  136. //-------------------------------------
  137. // FindLateralCover - attempts to locate a spot in the world
  138. // directly to the left or right of the caller that will
  139. // conceal them from view of pSightEnt
  140. //-------------------------------------
  141. #define COVER_CHECKS 5// how many checks are made
  142. #define COVER_DELTA 48// distance between checks
  143. bool CAI_TacticalServices::FindLateralCover( const Vector &vecThreat, float flMinDist, Vector *pResult )
  144. {
  145. return FindLateralCover( vecThreat, flMinDist, COVER_CHECKS * COVER_DELTA, COVER_CHECKS, pResult );
  146. }
  147. bool CAI_TacticalServices::FindLateralCover( const Vector &vecThreat, float flMinDist, float distToCheck, int numChecksPerDir, Vector *pResult )
  148. {
  149. return FindLateralCover( GetAbsOrigin(), vecThreat, flMinDist, distToCheck, numChecksPerDir, pResult );
  150. }
  151. bool CAI_TacticalServices::FindLateralCover( const Vector &vNearPos, const Vector &vecThreat, float flMinDist, float distToCheck, int numChecksPerDir, Vector *pResult )
  152. {
  153. AI_PROFILE_SCOPE( CAI_TacticalServices_FindLateralCover );
  154. MARK_TASK_EXPENSIVE();
  155. Vector vecLeftTest;
  156. Vector vecRightTest;
  157. Vector vecStepRight;
  158. Vector vecCheckStart;
  159. int i;
  160. if ( TestLateralCover( vecThreat, vNearPos, flMinDist ) )
  161. {
  162. *pResult = GetLocalOrigin();
  163. return true;
  164. }
  165. if( !ai_find_lateral_cover.GetBool() )
  166. {
  167. // Force the NPC to use the nodegraph to find cover. NOTE: We let the above code run
  168. // to detect the case where the NPC may already be standing in cover, but we don't
  169. // make any additional lateral checks.
  170. return false;
  171. }
  172. Vector right = vecThreat - vNearPos;
  173. float temp;
  174. right.z = 0;
  175. VectorNormalize( right );
  176. temp = right.x;
  177. right.x = -right.y;
  178. right.y = temp;
  179. vecStepRight = right * (distToCheck / (float)numChecksPerDir);
  180. vecStepRight.z = 0;
  181. vecLeftTest = vecRightTest = vNearPos;
  182. vecCheckStart = vecThreat;
  183. for ( i = 0 ; i < numChecksPerDir ; i++ )
  184. {
  185. vecLeftTest = vecLeftTest - vecStepRight;
  186. vecRightTest = vecRightTest + vecStepRight;
  187. if (TestLateralCover( vecCheckStart, vecLeftTest, flMinDist ))
  188. {
  189. *pResult = vecLeftTest;
  190. return true;
  191. }
  192. if (TestLateralCover( vecCheckStart, vecRightTest, flMinDist ))
  193. {
  194. *pResult = vecRightTest;
  195. return true;
  196. }
  197. }
  198. return false;
  199. }
  200. //-------------------------------------
  201. // Purpose: Find a nearby node that further away from the enemy than the
  202. // min range of my current weapon if there is one or just futher
  203. // away than my current location if I don't have a weapon.
  204. // Used to back away for attacks
  205. //-------------------------------------
  206. int CAI_TacticalServices::FindBackAwayNode(const Vector &vecThreat )
  207. {
  208. if ( !CAI_NetworkManager::NetworksLoaded() )
  209. {
  210. DevWarning( 2, "Graph not ready for FindBackAwayNode!\n" );
  211. return NO_NODE;
  212. }
  213. int iMyNode = GetPathfinder()->NearestNodeToNPC();
  214. int iThreatNode = GetPathfinder()->NearestNodeToPoint( vecThreat );
  215. if ( iMyNode == NO_NODE )
  216. {
  217. DevWarning( 2, "FindBackAwayNode() - %s has no nearest node!\n", GetEntClassname());
  218. return NO_NODE;
  219. }
  220. if ( iThreatNode == NO_NODE )
  221. {
  222. // DevWarning( 2, "FindBackAwayNode() - Threat has no nearest node!\n" );
  223. iThreatNode = iMyNode;
  224. // return false;
  225. }
  226. // A vector pointing to the threat.
  227. Vector vecToThreat;
  228. vecToThreat = vecThreat - GetLocalOrigin();
  229. // Get my current distance from the threat
  230. float flCurDist = VectorNormalize( vecToThreat );
  231. // Check my neighbors to find a node that's further away
  232. for (int link = 0; link < GetNetwork()->GetNode(iMyNode)->NumLinks(); link++)
  233. {
  234. CAI_Link *nodeLink = GetNetwork()->GetNode(iMyNode)->GetLinkByIndex(link);
  235. if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) )
  236. continue;
  237. int destID = nodeLink->DestNodeID(iMyNode);
  238. float flTestDist = ( vecThreat - GetNetwork()->GetNode(destID)->GetPosition(GetHullType()) ).Length();
  239. if ( flTestDist > flCurDist )
  240. {
  241. // Make sure this node doesn't take me past the enemy's position.
  242. Vector vecToNode;
  243. vecToNode = GetNetwork()->GetNode(destID)->GetPosition(GetHullType()) - GetLocalOrigin();
  244. VectorNormalize( vecToNode );
  245. if( DotProduct( vecToNode, vecToThreat ) < 0.0 )
  246. {
  247. return destID;
  248. }
  249. }
  250. }
  251. return NO_NODE;
  252. }
  253. //-------------------------------------
  254. // FindCover - tries to find a nearby node that will hide
  255. // the caller from its enemy.
  256. //
  257. // If supplied, search will return a node at least as far
  258. // away as MinDist, but no farther than MaxDist.
  259. // if MaxDist isn't supplied, it defaults to a reasonable
  260. // value
  261. //-------------------------------------
  262. int CAI_TacticalServices::FindCoverNode(const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist )
  263. {
  264. return FindCoverNode(GetLocalOrigin(), vThreatPos, vThreatEyePos, flMinDist, flMaxDist );
  265. }
  266. //-------------------------------------
  267. int CAI_TacticalServices::FindCoverNode(const Vector &vNearPos, const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist )
  268. {
  269. if ( !CAI_NetworkManager::NetworksLoaded() )
  270. return NO_NODE;
  271. AI_PROFILE_SCOPE( CAI_TacticalServices_FindCoverNode );
  272. MARK_TASK_EXPENSIVE();
  273. DebugFindCover( g_AIDebugFindCoverNode, GetOuter()->EyePosition(), vThreatEyePos, 0, 255, 255 );
  274. int iMyNode = GetPathfinder()->NearestNodeToPoint( vNearPos );
  275. if ( iMyNode == NO_NODE )
  276. {
  277. Vector pos = GetOuter()->GetAbsOrigin();
  278. DevWarning( 2, "FindCover() - %s has no nearest node! (Check near %f %f %f)\n", GetEntClassname(), pos.x, pos.y, pos.z);
  279. return NO_NODE;
  280. }
  281. if ( !flMaxDist )
  282. {
  283. // user didn't supply a MaxDist, so work up a crazy one.
  284. flMaxDist = 784;
  285. }
  286. if ( flMinDist > 0.5 * flMaxDist)
  287. {
  288. flMinDist = 0.5 * flMaxDist;
  289. }
  290. // ------------------------------------------------------------------------------------
  291. // We're going to search for a cover node by expanding to our current node's neighbors
  292. // and then their neighbors, until cover is found, or all nodes are beyond MaxDist
  293. // ------------------------------------------------------------------------------------
  294. AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * GetNetwork()->NumNodes() );
  295. CNodeList list( pBuffer, GetNetwork()->NumNodes() );
  296. CVarBitVec wasVisited(GetNetwork()->NumNodes()); // Nodes visited
  297. // mark start as visited
  298. list.Insert( AI_NearNode_t(iMyNode, 0) );
  299. wasVisited.Set( iMyNode );
  300. float flMinDistSqr = flMinDist*flMinDist;
  301. float flMaxDistSqr = flMaxDist*flMaxDist;
  302. static int nSearchRandomizer = 0; // tries to ensure the links are searched in a different order each time;
  303. // Search until the list is empty
  304. while( list.Count() )
  305. {
  306. // Get the node that is closest in the number of steps and remove from the list
  307. int nodeIndex = list.ElementAtHead().nodeIndex;
  308. list.RemoveAtHead();
  309. CAI_Node *pNode = GetNetwork()->GetNode(nodeIndex);
  310. Vector nodeOrigin = pNode->GetPosition(GetHullType());
  311. float dist = (vNearPos - nodeOrigin).LengthSqr();
  312. if (dist >= flMinDistSqr && dist < flMaxDistSqr)
  313. {
  314. Activity nCoverActivity = GetOuter()->GetCoverActivity( pNode->GetHint() );
  315. Vector vEyePos = nodeOrigin + GetOuter()->EyeOffset(nCoverActivity);
  316. if ( GetOuter()->IsValidCover( nodeOrigin, pNode->GetHint() ) )
  317. {
  318. // Check if this location will block the threat's line of sight to me
  319. if (GetOuter()->IsCoverPosition(vThreatEyePos, vEyePos))
  320. {
  321. // --------------------------------------------------------
  322. // Don't let anyone else use this node for a while
  323. // --------------------------------------------------------
  324. pNode->Lock( 1.0 );
  325. if ( pNode->GetHint() && ( pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_MED || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_LOW ) )
  326. {
  327. if ( GetOuter()->GetHintNode() )
  328. {
  329. GetOuter()->GetHintNode()->Unlock(GetOuter()->GetHintDelay(GetOuter()->GetHintNode()->HintType()));
  330. GetOuter()->SetHintNode( NULL );
  331. }
  332. GetOuter()->SetHintNode( pNode->GetHint() );
  333. }
  334. // The next NPC who searches should use a slight different pattern
  335. nSearchRandomizer = nodeIndex;
  336. DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 0, 255, 0 );
  337. return nodeIndex;
  338. }
  339. else
  340. {
  341. DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 255, 0, 0 );
  342. }
  343. }
  344. else
  345. {
  346. DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 0, 0, 255 );
  347. }
  348. }
  349. // Add its children to the search list
  350. // Go through each link
  351. // UNDONE: Pass in a cost function to measure each link?
  352. for ( int link = 0; link < GetNetwork()->GetNode(nodeIndex)->NumLinks(); link++ )
  353. {
  354. int index = (link + nSearchRandomizer) % GetNetwork()->GetNode(nodeIndex)->NumLinks();
  355. CAI_Link *nodeLink = GetNetwork()->GetNode(nodeIndex)->GetLinkByIndex(index);
  356. if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) )
  357. continue;
  358. int newID = nodeLink->DestNodeID(nodeIndex);
  359. // If not already on the closed list, add to it and set its distance
  360. if (!wasVisited.IsBitSet(newID))
  361. {
  362. // Don't accept climb nodes or nodes that aren't ready to use yet
  363. if ( GetNetwork()->GetNode(newID)->GetType() != NODE_CLIMB && !GetNetwork()->GetNode(newID)->IsLocked() )
  364. {
  365. // UNDONE: Shouldn't we really accumulate the distance by path rather than
  366. // absolute distance. After all, we are performing essentially an A* here.
  367. nodeOrigin = GetNetwork()->GetNode(newID)->GetPosition(GetHullType());
  368. dist = (vNearPos - nodeOrigin).LengthSqr();
  369. // use distance to threat as a heuristic to keep AIs from running toward
  370. // the threat in order to take cover from it.
  371. float threatDist = (vThreatPos - nodeOrigin).LengthSqr();
  372. // Now check this node is not too close towards the threat
  373. if ( dist < threatDist * 1.5 )
  374. {
  375. list.Insert( AI_NearNode_t(newID, dist) );
  376. }
  377. }
  378. // mark visited
  379. wasVisited.Set(newID);
  380. }
  381. }
  382. }
  383. // We failed. Not cover node was found
  384. // Clear hint node used to set ducking
  385. GetOuter()->ClearHintNode();
  386. return NO_NODE;
  387. }
  388. //-------------------------------------
  389. // Purpose: Return node ID that has line of sight to target I want to shoot
  390. //
  391. // Input : pNPC - npc that's looking for a place to shoot from
  392. // vThreatPos - position of entity/location I'm trying to shoot
  393. // vThreatEyePos - eye position of entity I'm trying to shoot. If
  394. // entity has no eye position, just give vThreatPos again
  395. // flMinThreatDist - minimum distance that node must be from vThreatPos
  396. // flMaxThreadDist - maximum distance that node can be from vThreadPos
  397. // vThreatFacing - optional argument. If given the returned node
  398. // will also be behind the given facing direction (flanking)
  399. // flBlockTime - how long to block this node from use
  400. // Output : int - ID number of node that meets qualifications
  401. //-------------------------------------
  402. int CAI_TacticalServices::FindLosNode(const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinThreatDist, float flMaxThreatDist, float flBlockTime, FlankType_t eFlankType, const Vector &vecFlankRefPos, float flFlankParam )
  403. {
  404. if ( !CAI_NetworkManager::NetworksLoaded() )
  405. return NO_NODE;
  406. AI_PROFILE_SCOPE( CAI_TacticalServices_FindLosNode );
  407. MARK_TASK_EXPENSIVE();
  408. int iMyNode = GetPathfinder()->NearestNodeToNPC();
  409. if ( iMyNode == NO_NODE )
  410. {
  411. Vector pos = GetOuter()->GetAbsOrigin();
  412. DevWarning( 2, "FindCover() - %s has no nearest node! (Check near %f %f %f)\n", GetEntClassname(), pos.x, pos.y, pos.z);
  413. return NO_NODE;
  414. }
  415. // ------------------------------------------------------------------------------------
  416. // We're going to search for a shoot node by expanding to our current node's neighbors
  417. // and then their neighbors, until a shooting position is found, or all nodes are beyond MaxDist
  418. // ------------------------------------------------------------------------------------
  419. AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * GetNetwork()->NumNodes() );
  420. CNodeList list( pBuffer, GetNetwork()->NumNodes() );
  421. CVarBitVec wasVisited(GetNetwork()->NumNodes()); // Nodes visited
  422. // mark start as visited
  423. wasVisited.Set( iMyNode );
  424. list.Insert( AI_NearNode_t(iMyNode, 0) );
  425. static int nSearchRandomizer = 0; // tries to ensure the links are searched in a different order each time;
  426. while ( list.Count() )
  427. {
  428. int nodeIndex = list.ElementAtHead().nodeIndex;
  429. // remove this item from the list
  430. list.RemoveAtHead();
  431. const Vector &nodeOrigin = GetNetwork()->GetNode(nodeIndex)->GetPosition(GetHullType());
  432. // HACKHACK: Can't we rework this loop and get rid of this?
  433. // skip the starting node, or we probably wouldn't have called this function.
  434. if ( nodeIndex != iMyNode )
  435. {
  436. bool skip = false;
  437. // See if the node satisfies the flanking criteria.
  438. switch ( eFlankType )
  439. {
  440. case FLANKTYPE_NONE:
  441. break;
  442. case FLANKTYPE_RADIUS:
  443. {
  444. Vector vecDist = nodeOrigin - vecFlankRefPos;
  445. if ( vecDist.Length() < flFlankParam )
  446. {
  447. skip = true;
  448. }
  449. break;
  450. }
  451. case FLANKTYPE_ARC:
  452. {
  453. Vector vecEnemyToRef = vecFlankRefPos - vThreatPos;
  454. VectorNormalize( vecEnemyToRef );
  455. Vector vecEnemyToNode = nodeOrigin - vThreatPos;
  456. VectorNormalize( vecEnemyToNode );
  457. float flDot = DotProduct( vecEnemyToRef, vecEnemyToNode );
  458. if ( RAD2DEG( acos( flDot ) ) < flFlankParam )
  459. {
  460. skip = true;
  461. }
  462. break;
  463. }
  464. }
  465. // Don't accept climb nodes, and assume my nearest node isn't valid because
  466. // we decided to make this check in the first place. Keep moving
  467. if ( !skip && !GetNetwork()->GetNode(nodeIndex)->IsLocked() &&
  468. GetNetwork()->GetNode(nodeIndex)->GetType() != NODE_CLIMB )
  469. {
  470. // Now check its distance and only accept if in range
  471. float flThreatDist = ( nodeOrigin - vThreatPos ).Length();
  472. if ( flThreatDist < flMaxThreatDist &&
  473. flThreatDist > flMinThreatDist )
  474. {
  475. CAI_Node *pNode = GetNetwork()->GetNode(nodeIndex);
  476. if ( GetOuter()->IsValidShootPosition( nodeOrigin, pNode, pNode->GetHint() ) )
  477. {
  478. if (GetOuter()->TestShootPosition(nodeOrigin,vThreatEyePos))
  479. {
  480. // Note when this node was used, so we don't try
  481. // to use it again right away.
  482. GetNetwork()->GetNode(nodeIndex)->Lock( flBlockTime );
  483. #if 0
  484. if ( GetOuter()->GetHintNode() )
  485. {
  486. GetOuter()->GetHintNode()->Unlock(GetOuter()->GetHintDelay(GetOuter()->GetHintNode()->HintType()));
  487. GetOuter()->SetHintNode( NULL );
  488. }
  489. // This used to not be set, why? (kenb)
  490. // @Note (toml 05-19-04): I think because stomping the hint can lead to
  491. // unintended side effects. The hint node is primarily a high level
  492. // tool, and certain NPCs break if it gets slammed here. If we need
  493. // this, we should propagate it out and let the schedule selector
  494. // or task decide to set the hint node
  495. GetOuter()->SetHintNode( GetNetwork()->GetNode(nodeIndex)->GetHint() );
  496. #endif
  497. if ( ShouldDebugLos( nodeIndex ) )
  498. {
  499. NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:los!", nodeIndex), false, 1 );
  500. }
  501. // The next NPC who searches should use a slight different pattern
  502. nSearchRandomizer = nodeIndex;
  503. return nodeIndex;
  504. }
  505. else
  506. {
  507. if ( ShouldDebugLos( nodeIndex ) )
  508. {
  509. NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:!shoot", nodeIndex), false, 1 );
  510. }
  511. }
  512. }
  513. else
  514. {
  515. if ( ShouldDebugLos( nodeIndex ) )
  516. {
  517. NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:!valid", nodeIndex), false, 1 );
  518. }
  519. }
  520. }
  521. else
  522. {
  523. if ( ShouldDebugLos( nodeIndex ) )
  524. {
  525. CFmtStr msg( "%d:%s", nodeIndex, ( flThreatDist < flMaxThreatDist ) ? "too close" : "too far" );
  526. NDebugOverlay::Text( nodeOrigin, msg, false, 1 );
  527. }
  528. }
  529. }
  530. }
  531. // Go through each link and add connected nodes to the list
  532. for (int link=0; link < GetNetwork()->GetNode(nodeIndex)->NumLinks();link++)
  533. {
  534. int index = (link + nSearchRandomizer) % GetNetwork()->GetNode(nodeIndex)->NumLinks();
  535. CAI_Link *nodeLink = GetNetwork()->GetNode(nodeIndex)->GetLinkByIndex(index);
  536. if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) )
  537. continue;
  538. int newID = nodeLink->DestNodeID(nodeIndex);
  539. // If not already visited, add to the list
  540. if (!wasVisited.IsBitSet(newID))
  541. {
  542. float dist = (GetLocalOrigin() - GetNetwork()->GetNode(newID)->GetPosition(GetHullType())).LengthSqr();
  543. list.Insert( AI_NearNode_t(newID, dist) );
  544. wasVisited.Set( newID );
  545. }
  546. }
  547. }
  548. // We failed. No range attack node node was found
  549. return NO_NODE;
  550. }
  551. //-------------------------------------
  552. // Checks lateral LOS
  553. //-------------------------------------
  554. bool CAI_TacticalServices::TestLateralLos( const Vector &vecCheckStart, const Vector &vecCheckEnd )
  555. {
  556. trace_t tr;
  557. // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first.
  558. AI_TraceLOS( vecCheckStart, vecCheckEnd + GetOuter()->GetViewOffset(), NULL, &tr );
  559. if (tr.fraction == 1.0)
  560. {
  561. if ( GetOuter()->IsValidShootPosition( vecCheckEnd, NULL, NULL ) )
  562. {
  563. if (GetOuter()->TestShootPosition(vecCheckEnd,vecCheckStart))
  564. {
  565. AIMoveTrace_t moveTrace;
  566. GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND, GetLocalOrigin(), vecCheckEnd, GetOuter()->GetAITraceMask(), NULL, &moveTrace );
  567. if (moveTrace.fStatus == AIMR_OK)
  568. {
  569. return true;
  570. }
  571. }
  572. }
  573. }
  574. return false;
  575. }
  576. //-------------------------------------
  577. bool CAI_TacticalServices::FindLateralLos( const Vector &vecThreat, Vector *pResult )
  578. {
  579. AI_PROFILE_SCOPE( CAI_TacticalServices_FindLateralLos );
  580. if( !m_bAllowFindLateralLos )
  581. {
  582. return false;
  583. }
  584. MARK_TASK_EXPENSIVE();
  585. Vector vecLeftTest;
  586. Vector vecRightTest;
  587. Vector vecStepRight;
  588. Vector vecCheckStart;
  589. bool bLookingForEnemy = GetEnemy() && VectorsAreEqual(vecThreat, GetEnemy()->EyePosition(), 0.1f);
  590. int i;
  591. if( !bLookingForEnemy || GetOuter()->HasCondition(COND_SEE_ENEMY) || GetOuter()->HasCondition(COND_HAVE_ENEMY_LOS) ||
  592. GetOuter()->GetTimeScheduleStarted() == gpGlobals->curtime ) // Conditions get nuked before tasks run, assume should try
  593. {
  594. // My current position might already be valid.
  595. if ( TestLateralLos(vecThreat, GetLocalOrigin()) )
  596. {
  597. *pResult = GetLocalOrigin();
  598. return true;
  599. }
  600. }
  601. if( !ai_find_lateral_los.GetBool() )
  602. {
  603. // Allows us to turn off lateral LOS at the console. Allow the above code to run
  604. // just in case the NPC has line of sight to begin with.
  605. return false;
  606. }
  607. int iChecks = COVER_CHECKS;
  608. int iDelta = COVER_DELTA;
  609. // If we're limited in how far we're allowed to move laterally, don't bother checking past it
  610. int iMaxLateralDelta = GetOuter()->GetMaxTacticalLateralMovement();
  611. if ( iMaxLateralDelta != MAXTACLAT_IGNORE && iMaxLateralDelta < iDelta )
  612. {
  613. iChecks = 1;
  614. iDelta = iMaxLateralDelta;
  615. }
  616. Vector right;
  617. AngleVectors( GetLocalAngles(), NULL, &right, NULL );
  618. vecStepRight = right * iDelta;
  619. vecStepRight.z = 0;
  620. vecLeftTest = vecRightTest = GetLocalOrigin();
  621. vecCheckStart = vecThreat;
  622. for ( i = 0 ; i < iChecks; i++ )
  623. {
  624. vecLeftTest = vecLeftTest - vecStepRight;
  625. vecRightTest = vecRightTest + vecStepRight;
  626. if (TestLateralLos( vecCheckStart, vecLeftTest ))
  627. {
  628. *pResult = vecLeftTest;
  629. return true;
  630. }
  631. if (TestLateralLos( vecCheckStart, vecRightTest ))
  632. {
  633. *pResult = vecRightTest;
  634. return true;
  635. }
  636. }
  637. return false;
  638. }
  639. //-------------------------------------
  640. Vector CAI_TacticalServices::GetNodePos( int node )
  641. {
  642. return GetNetwork()->GetNode((int)node)->GetPosition(GetHullType());
  643. }
  644. //-----------------------------------------------------------------------------