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.

573 lines
15 KiB

  1. // NextBotRetreatPath.h
  2. // Maintain and follow a path that leads safely away from the given Actor
  3. // Author: Michael Booth, February 2007
  4. // Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
  5. #ifndef _NEXT_BOT_RETREAT_PATH_
  6. #define _NEXT_BOT_RETREAT_PATH_
  7. #include "nav.h"
  8. #include "NextBotInterface.h"
  9. #include "NextBotLocomotionInterface.h"
  10. #include "NextBotRetreatPath.h"
  11. #include "NextBotUtil.h"
  12. #include "NextBotPathFollow.h"
  13. #include "tier0/vprof.h"
  14. //----------------------------------------------------------------------------------------------
  15. /**
  16. * A RetreatPath extends a PathFollower to periodically recompute a path
  17. * away from a threat, and to move along the path away from that threat.
  18. */
  19. class RetreatPath : public PathFollower
  20. {
  21. public:
  22. RetreatPath( void );
  23. virtual ~RetreatPath() { }
  24. void Update( INextBot *bot, CBaseEntity *threat ); // update path away from threat and move bot along path
  25. virtual float GetMaxPathLength( void ) const; // return maximum path length
  26. virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
  27. private:
  28. void RefreshPath( INextBot *bot, CBaseEntity *threat );
  29. CountdownTimer m_throttleTimer; // require a minimum time between re-paths
  30. EHANDLE m_pathThreat; // the threat of our existing path
  31. Vector m_pathThreatPos; // where the threat was when the path was built
  32. };
  33. inline RetreatPath::RetreatPath( void )
  34. {
  35. m_throttleTimer.Invalidate();
  36. m_pathThreat = NULL;
  37. }
  38. inline float RetreatPath::GetMaxPathLength( void ) const
  39. {
  40. return 1000.0f;
  41. }
  42. inline void RetreatPath::Invalidate( void )
  43. {
  44. // path is gone, repath at earliest opportunity
  45. m_throttleTimer.Invalidate();
  46. m_pathThreat = NULL;
  47. // extend
  48. PathFollower::Invalidate();
  49. }
  50. //----------------------------------------------------------------------------------------------
  51. /**
  52. * Maintain a path to our chase threat and move along that path
  53. */
  54. inline void RetreatPath::Update( INextBot *bot, CBaseEntity *threat )
  55. {
  56. VPROF_BUDGET( "RetreatPath::Update", "NextBot" );
  57. if ( threat == NULL )
  58. {
  59. return;
  60. }
  61. // if our path threat changed, repath immediately
  62. if ( threat != m_pathThreat )
  63. {
  64. if ( bot->IsDebugging( INextBot::PATH ) )
  65. {
  66. DevMsg( "%3.2f: bot(#%d) Chase path threat changed (from %X to %X).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_pathThreat.Get(), threat );
  67. }
  68. Invalidate();
  69. }
  70. // maintain the path away from the threat
  71. RefreshPath( bot, threat );
  72. // move along the path towards the threat
  73. PathFollower::Update( bot );
  74. }
  75. //--------------------------------------------------------------------------------------------------------------
  76. /**
  77. * Build a path away from retreatFromArea up to retreatRange in length.
  78. */
  79. class RetreatPathBuilder
  80. {
  81. public:
  82. RetreatPathBuilder( INextBot *me, CBaseEntity *threat, float retreatRange = 500.0f )
  83. {
  84. m_me = me;
  85. m_mover = me->GetLocomotionInterface();
  86. m_threat = threat;
  87. m_retreatRange = retreatRange;
  88. }
  89. CNavArea *ComputePath( void )
  90. {
  91. VPROF_BUDGET( "NavAreaBuildRetreatPath", "NextBot" );
  92. if ( m_mover == NULL )
  93. return NULL;
  94. CNavArea *startArea = m_me->GetEntity()->GetLastKnownArea();
  95. if ( startArea == NULL )
  96. return NULL;
  97. CNavArea *retreatFromArea = TheNavMesh->GetNearestNavArea( m_threat->GetAbsOrigin() );
  98. if ( retreatFromArea == NULL )
  99. return NULL;
  100. startArea->SetParent( NULL );
  101. // start search
  102. CNavArea::ClearSearchLists();
  103. float initCost = Cost( startArea, NULL, NULL );
  104. if ( initCost < 0.0f )
  105. return NULL;
  106. int teamID = m_me->GetEntity()->GetTeamNumber();
  107. startArea->SetTotalCost( initCost );
  108. startArea->AddToOpenList();
  109. // keep track of the area farthest away from the threat
  110. CNavArea *farthestArea = NULL;
  111. float farthestRange = 0.0f;
  112. //
  113. // Dijkstra's algorithm (since we don't know our goal).
  114. // Build a path as far away from the retreat area as possible.
  115. // Minimize total path length and danger.
  116. // Maximize distance to threat of end of path.
  117. //
  118. while( !CNavArea::IsOpenListEmpty() )
  119. {
  120. // get next area to check
  121. CNavArea *area = CNavArea::PopOpenList();
  122. area->AddToClosedList();
  123. // don't consider blocked areas
  124. if ( area->IsBlocked( teamID ) )
  125. continue;
  126. // build adjacent area array
  127. CollectAdjacentAreas( area );
  128. // search adjacent areas
  129. for( int i=0; i<m_adjAreaIndex; ++i )
  130. {
  131. CNavArea *newArea = m_adjAreaVector[ i ].area;
  132. // only visit each area once
  133. if ( newArea->IsClosed() )
  134. continue;
  135. // don't consider blocked areas
  136. if ( newArea->IsBlocked( teamID ) )
  137. continue;
  138. // don't use this area if it is out of range
  139. if ( ( newArea->GetCenter() - m_me->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( m_retreatRange ) )
  140. continue;
  141. // determine cost of traversing this area
  142. float newCost = Cost( newArea, area, m_adjAreaVector[ i ].ladder );
  143. // don't use adjacent area if cost functor says it is a dead-end
  144. if ( newCost < 0.0f )
  145. continue;
  146. if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
  147. {
  148. // we have already visited this area, and it has a better path
  149. continue;
  150. }
  151. else
  152. {
  153. // whether this area has been visited or not, we now have a better path
  154. newArea->SetParent( area, m_adjAreaVector[ i ].how );
  155. newArea->SetTotalCost( newCost );
  156. // use 'cost so far' to hold cumulative cost
  157. newArea->SetCostSoFar( newCost );
  158. // tricky bit here - relying on OpenList being sorted by cost
  159. if ( newArea->IsOpen() )
  160. {
  161. // area already on open list, update the list order to keep costs sorted
  162. newArea->UpdateOnOpenList();
  163. }
  164. else
  165. {
  166. newArea->AddToOpenList();
  167. }
  168. // keep track of area farthest from threat
  169. float threatRange = ( newArea->GetCenter() - m_threat->GetAbsOrigin() ).Length();
  170. if ( threatRange > farthestRange )
  171. {
  172. farthestArea = newArea;
  173. farthestRange = threatRange;
  174. }
  175. }
  176. }
  177. }
  178. return farthestArea;
  179. }
  180. /**
  181. * Build a vector of adjacent areas reachable from the given area
  182. */
  183. void CollectAdjacentAreas( CNavArea *area )
  184. {
  185. m_adjAreaIndex = 0;
  186. const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
  187. FOR_EACH_VEC( adjNorth, it )
  188. {
  189. if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
  190. break;
  191. m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
  192. m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
  193. m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
  194. ++m_adjAreaIndex;
  195. }
  196. const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
  197. FOR_EACH_VEC( adjSouth, it )
  198. {
  199. if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
  200. break;
  201. m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
  202. m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
  203. m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
  204. ++m_adjAreaIndex;
  205. }
  206. const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
  207. FOR_EACH_VEC( adjWest, it )
  208. {
  209. if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
  210. break;
  211. m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
  212. m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
  213. m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
  214. ++m_adjAreaIndex;
  215. }
  216. const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
  217. FOR_EACH_VEC( adjEast, it )
  218. {
  219. if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
  220. break;
  221. m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
  222. m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
  223. m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
  224. ++m_adjAreaIndex;
  225. }
  226. const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
  227. FOR_EACH_VEC( adjUpLadder, it )
  228. {
  229. CNavLadder *ladder = adjUpLadder[ it ].ladder;
  230. if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
  231. {
  232. m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
  233. m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
  234. m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
  235. ++m_adjAreaIndex;
  236. }
  237. if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
  238. {
  239. m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
  240. m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
  241. m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
  242. ++m_adjAreaIndex;
  243. }
  244. if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
  245. {
  246. m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
  247. m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
  248. m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
  249. ++m_adjAreaIndex;
  250. }
  251. }
  252. const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
  253. FOR_EACH_VEC( adjDownLadder, it )
  254. {
  255. CNavLadder *ladder = adjDownLadder[ it ].ladder;
  256. if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
  257. break;
  258. if ( ladder->m_bottomArea )
  259. {
  260. m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
  261. m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
  262. m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
  263. ++m_adjAreaIndex;
  264. }
  265. }
  266. }
  267. /**
  268. * Cost minimizes path length traveled thus far and "danger" (proximity to threat(s))
  269. */
  270. float Cost( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
  271. {
  272. // check if we can use this area
  273. if ( !m_mover->IsAreaTraversable( area ) )
  274. {
  275. return -1.0f;
  276. }
  277. int teamID = m_me->GetEntity()->GetTeamNumber();
  278. if ( area->IsBlocked( teamID ) )
  279. {
  280. return -1.0f;
  281. }
  282. const float debugDeltaT = 3.0f;
  283. float cost;
  284. const float maxThreatRange = 500.0f;
  285. const float dangerDensity = 1000.0f;
  286. if ( fromArea == NULL )
  287. {
  288. cost = 0.0f;
  289. if ( area->Contains( m_threat->GetAbsOrigin() ) )
  290. {
  291. // maximum danger - threat is in the area with us
  292. cost += 10.0f * dangerDensity;
  293. if ( m_me->IsDebugging( INextBot::PATH ) )
  294. {
  295. area->DrawFilled( 255, 0, 0, 128 );
  296. }
  297. }
  298. else
  299. {
  300. // danger proportional to range to us
  301. float rangeToThreat = ( m_threat->GetAbsOrigin() - m_me->GetEntity()->GetAbsOrigin() ).Length();
  302. if ( rangeToThreat < maxThreatRange )
  303. {
  304. cost += dangerDensity * ( 1.0f - ( rangeToThreat / maxThreatRange ) );
  305. if ( m_me->IsDebugging( INextBot::PATH ) )
  306. {
  307. NDebugOverlay::Line( m_me->GetEntity()->GetAbsOrigin(), m_threat->GetAbsOrigin(), 255, 0, 0, true, debugDeltaT );
  308. }
  309. }
  310. }
  311. }
  312. else
  313. {
  314. // compute distance traveled along path so far
  315. float dist;
  316. if ( ladder )
  317. {
  318. const float ladderCostFactor = 100.0f;
  319. dist = ladderCostFactor * ladder->m_length;
  320. }
  321. else
  322. {
  323. Vector to = area->GetCenter() - fromArea->GetCenter();
  324. dist = to.Length();
  325. // check for vertical discontinuities
  326. Vector closeFrom, closeTo;
  327. area->GetClosestPointOnArea( fromArea->GetCenter(), &closeTo );
  328. fromArea->GetClosestPointOnArea( area->GetCenter(), &closeFrom );
  329. float deltaZ = closeTo.z - closeFrom.z;
  330. if ( deltaZ > m_mover->GetMaxJumpHeight() )
  331. {
  332. // too high to jump
  333. return -1.0f;
  334. }
  335. else if ( -deltaZ > m_mover->GetDeathDropHeight() )
  336. {
  337. // too far down to drop
  338. return -1.0f;
  339. }
  340. // prefer to maintain our level
  341. const float climbCost = 10.0f;
  342. dist += climbCost * fabs( deltaZ );
  343. }
  344. cost = dist + fromArea->GetTotalCost();
  345. // Add in danger cost due to threat
  346. // Assume straight line between areas and find closest point
  347. // to the threat along that line segment. The distance between
  348. // the threat and closest point on the line is the danger cost.
  349. // path danger is CUMULATIVE
  350. float dangerCost = fromArea->GetCostSoFar();
  351. Vector close;
  352. float t;
  353. CalcClosestPointOnLineSegment( m_threat->GetAbsOrigin(), area->GetCenter(), fromArea->GetCenter(), close, &t );
  354. if ( t < 0.0f )
  355. {
  356. close = area->GetCenter();
  357. }
  358. else if ( t > 1.0f )
  359. {
  360. close = fromArea->GetCenter();
  361. }
  362. float rangeToThreat = ( m_threat->GetAbsOrigin() - close ).Length();
  363. if ( rangeToThreat < maxThreatRange )
  364. {
  365. float dangerFactor = 1.0f - ( rangeToThreat / maxThreatRange );
  366. dangerCost = dangerDensity * dangerFactor;
  367. if ( m_me->IsDebugging( INextBot::PATH ) )
  368. {
  369. NDebugOverlay::HorzArrow( fromArea->GetCenter(), area->GetCenter(), 5, 255 * dangerFactor, 0, 0, 255, true, debugDeltaT );
  370. Vector to = close - m_threat->GetAbsOrigin();
  371. to.NormalizeInPlace();
  372. NDebugOverlay::Line( close, close - 50.0f * to, 255, 0, 0, true, debugDeltaT );
  373. }
  374. }
  375. cost += dangerCost;
  376. }
  377. return cost;
  378. }
  379. private:
  380. INextBot *m_me;
  381. ILocomotion *m_mover;
  382. CBaseEntity *m_threat;
  383. float m_retreatRange;
  384. enum { MAX_ADJ_AREAS = 64 };
  385. struct AdjInfo
  386. {
  387. CNavArea *area;
  388. CNavLadder *ladder;
  389. NavTraverseType how;
  390. };
  391. AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
  392. int m_adjAreaIndex;
  393. };
  394. //----------------------------------------------------------------------------------------------
  395. /**
  396. * Periodically rebuild the path away from our threat
  397. */
  398. inline void RetreatPath::RefreshPath( INextBot *bot, CBaseEntity *threat )
  399. {
  400. VPROF_BUDGET( "RetreatPath::RefreshPath", "NextBot" );
  401. if ( threat == NULL )
  402. {
  403. if ( bot->IsDebugging( INextBot::PATH ) )
  404. {
  405. DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No threat.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  406. }
  407. return;
  408. }
  409. // don't change our path if we're on a ladder
  410. ILocomotion *mover = bot->GetLocomotionInterface();
  411. if ( IsValid() && mover && mover->IsUsingLadder() )
  412. {
  413. if ( bot->IsDebugging( INextBot::PATH ) )
  414. {
  415. DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  416. }
  417. return;
  418. }
  419. // the closer we get, the more accurate our path needs to be
  420. Vector to = threat->GetAbsOrigin() - bot->GetPosition();
  421. const float minTolerance = 0.0f;
  422. const float toleranceRate = 0.33f;
  423. float tolerance = minTolerance + toleranceRate * to.Length();
  424. if ( !IsValid() || ( threat->GetAbsOrigin() - m_pathThreatPos ).IsLengthGreaterThan( tolerance ) )
  425. {
  426. if ( !m_throttleTimer.IsElapsed() )
  427. {
  428. // require a minimum time between repaths, as long as we have a path to follow
  429. if ( bot->IsDebugging( INextBot::PATH ) )
  430. {
  431. DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  432. }
  433. return;
  434. }
  435. // remember our path threat
  436. m_pathThreat = threat;
  437. m_pathThreatPos = threat->GetAbsOrigin();
  438. RetreatPathBuilder retreat( bot, threat, GetMaxPathLength() );
  439. CNavArea *goalArea = retreat.ComputePath();
  440. if ( goalArea )
  441. {
  442. AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
  443. }
  444. else
  445. {
  446. // all adjacent areas are too far away - just move directly away from threat
  447. Vector to = threat->GetAbsOrigin() - bot->GetPosition();
  448. BuildTrivialPath( bot, bot->GetPosition() - to );
  449. }
  450. const float minRepathInterval = 0.5f;
  451. m_throttleTimer.Start( minRepathInterval );
  452. }
  453. }
  454. #endif // _NEXT_BOT_RETREAT_PATH_