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.

376 lines
12 KiB

  1. // NextBotChasePath.h
  2. // Maintain and follow a "chase path" to a selected Actor
  3. // Author: Michael Booth, September 2006
  4. // Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
  5. #ifndef _NEXT_BOT_CHASE_PATH_
  6. #define _NEXT_BOT_CHASE_PATH_
  7. #include "nav.h"
  8. #include "NextBotInterface.h"
  9. #include "NextBotLocomotionInterface.h"
  10. #include "NextBotChasePath.h"
  11. #include "NextBotUtil.h"
  12. #include "NextBotPathFollow.h"
  13. #include "tier0/vprof.h"
  14. //----------------------------------------------------------------------------------------------
  15. /**
  16. * A ChasePath extends a PathFollower to periodically recompute a path to a chase
  17. * subject, and to move along the path towards that subject.
  18. */
  19. class ChasePath : public PathFollower
  20. {
  21. public:
  22. enum SubjectChaseType
  23. {
  24. LEAD_SUBJECT,
  25. DONT_LEAD_SUBJECT
  26. };
  27. ChasePath( SubjectChaseType chaseHow = DONT_LEAD_SUBJECT );
  28. virtual ~ChasePath() { }
  29. virtual void Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos = NULL ); // update path to chase target and move bot along path
  30. virtual float GetLeadRadius( void ) const; // range where movement leading begins - beyond this just head right for the subject
  31. virtual float GetMaxPathLength( void ) const; // return maximum path length
  32. virtual Vector PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const; // try to cutoff our chase subject, knowing our relative positions and velocities
  33. virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const; // return true if situation has changed enough to warrant recomputing the current path
  34. virtual float GetLifetime( void ) const; // Return duration this path is valid. Path will become invalid at its earliest opportunity once this duration elapses. Zero = infinite lifetime
  35. virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
  36. private:
  37. void RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos );
  38. CountdownTimer m_failTimer; // throttle re-pathing if last path attempt failed
  39. CountdownTimer m_throttleTimer; // require a minimum time between re-paths
  40. CountdownTimer m_lifetimeTimer;
  41. EHANDLE m_lastPathSubject; // the subject used to compute the current/last path
  42. SubjectChaseType m_chaseHow;
  43. };
  44. inline ChasePath::ChasePath( SubjectChaseType chaseHow )
  45. {
  46. m_failTimer.Invalidate();
  47. m_throttleTimer.Invalidate();
  48. m_lifetimeTimer.Invalidate();
  49. m_lastPathSubject = NULL;
  50. m_chaseHow = chaseHow;
  51. }
  52. inline float ChasePath::GetLeadRadius( void ) const
  53. {
  54. return 500.0f; // 1000.0f;
  55. }
  56. inline float ChasePath::GetMaxPathLength( void ) const
  57. {
  58. // no limit
  59. return 0.0f;
  60. }
  61. inline float ChasePath::GetLifetime( void ) const
  62. {
  63. // infinite duration
  64. return 0.0f;
  65. }
  66. inline void ChasePath::Invalidate( void )
  67. {
  68. // path is gone, repath at earliest opportunity
  69. m_throttleTimer.Invalidate();
  70. m_lifetimeTimer.Invalidate();
  71. // extend
  72. PathFollower::Invalidate();
  73. }
  74. //----------------------------------------------------------------------------------------------
  75. /**
  76. * Maintain a path to our chase subject and move along that path
  77. */
  78. inline void ChasePath::Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos )
  79. {
  80. VPROF_BUDGET( "ChasePath::Update", "NextBot" );
  81. // maintain the path to the subject
  82. RefreshPath( bot, subject, cost, pPredictedSubjectPos );
  83. // move along the path towards the subject
  84. PathFollower::Update( bot );
  85. }
  86. //----------------------------------------------------------------------------------------------
  87. /**
  88. * Return true if situation has changed enough to warrant recomputing the current path
  89. */
  90. inline bool ChasePath::IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const
  91. {
  92. // the closer we get, the more accurate our path needs to be
  93. Vector to = subject->GetAbsOrigin() - bot->GetPosition();
  94. const float minTolerance = 0.0f; // 25.0f;
  95. const float toleranceRate = 0.33f; // 1.0f; // 0.15f;
  96. float tolerance = minTolerance + toleranceRate * to.Length();
  97. return ( subject->GetAbsOrigin() - GetEndPosition() ).IsLengthGreaterThan( tolerance );
  98. }
  99. //----------------------------------------------------------------------------------------------
  100. /**
  101. * Periodically rebuild the path to our victim
  102. */
  103. inline void ChasePath::RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos )
  104. {
  105. VPROF_BUDGET( "ChasePath::RefreshPath", "NextBot" );
  106. ILocomotion *mover = bot->GetLocomotionInterface();
  107. // don't change our path if we're on a ladder
  108. if ( IsValid() && mover->IsUsingLadder() )
  109. {
  110. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  111. {
  112. DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  113. }
  114. // don't allow repath until a moment AFTER we have left the ladder
  115. m_throttleTimer.Start( 1.0f );
  116. return;
  117. }
  118. if ( subject == NULL )
  119. {
  120. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  121. {
  122. DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No subject.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  123. }
  124. return;
  125. }
  126. if ( !m_failTimer.IsElapsed() )
  127. {
  128. // if ( bot->IsDebugging( NEXTBOT_PATH ) )
  129. // {
  130. // DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Fail timer not elapsed.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  131. // }
  132. return;
  133. }
  134. // if our path subject changed, repath immediately
  135. if ( subject != m_lastPathSubject )
  136. {
  137. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  138. {
  139. DevMsg( "%3.2f: bot(#%d) Chase path subject changed (from %p to %p).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_lastPathSubject.Get(), subject );
  140. }
  141. Invalidate();
  142. // new subject, fresh attempt
  143. m_failTimer.Invalidate();
  144. }
  145. if ( IsValid() && !m_throttleTimer.IsElapsed() )
  146. {
  147. // require a minimum time between repaths, as long as we have a path to follow
  148. // if ( bot->IsDebugging( NEXTBOT_PATH ) )
  149. // {
  150. // DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  151. // }
  152. return;
  153. }
  154. if ( IsValid() && m_lifetimeTimer.HasStarted() && m_lifetimeTimer.IsElapsed() )
  155. {
  156. // this path's lifetime has elapsed
  157. Invalidate();
  158. }
  159. if ( !IsValid() || IsRepathNeeded( bot, subject ) )
  160. {
  161. // the situation has changed - try a new path
  162. bool isPath;
  163. Vector pathTarget = subject->GetAbsOrigin();
  164. if ( m_chaseHow == LEAD_SUBJECT )
  165. {
  166. pathTarget = pPredictedSubjectPos ? *pPredictedSubjectPos : PredictSubjectPosition( bot, subject );
  167. isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() );
  168. }
  169. else if ( subject->MyCombatCharacterPointer() && subject->MyCombatCharacterPointer()->GetLastKnownArea() )
  170. {
  171. isPath = Compute( bot, subject->MyCombatCharacterPointer(), cost, GetMaxPathLength() );
  172. }
  173. else
  174. {
  175. isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() );
  176. }
  177. if ( isPath )
  178. {
  179. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  180. {
  181. const float size = 20.0f;
  182. NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, RandomInt( 0, 200 ), 255, 255, true, 30.0f );
  183. DevMsg( "%3.2f: bot(#%d) REPATH\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  184. }
  185. m_lastPathSubject = subject;
  186. const float minRepathInterval = 0.5f;
  187. m_throttleTimer.Start( minRepathInterval );
  188. // track the lifetime of this new path
  189. float lifetime = GetLifetime();
  190. if ( lifetime > 0.0f )
  191. {
  192. m_lifetimeTimer.Start( lifetime );
  193. }
  194. else
  195. {
  196. m_lifetimeTimer.Invalidate();
  197. }
  198. }
  199. else
  200. {
  201. // can't reach subject - throttle retry based on range to subject
  202. m_failTimer.Start( 0.005f * ( bot->GetRangeTo( subject ) ) );
  203. // allow bot to react to path failure
  204. bot->OnMoveToFailure( this, FAIL_NO_PATH_EXISTS );
  205. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  206. {
  207. const float size = 20.0f;
  208. const float dT = 90.0f;
  209. int c = RandomInt( 0, 100 );
  210. NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, c, c, 255, true, dT );
  211. NDebugOverlay::HorzArrow( bot->GetPosition(), pathTarget, 5.0f, 255, c, c, 255, true, dT );
  212. DevMsg( "%3.2f: bot(#%d) REPATH FAILED\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
  213. }
  214. Invalidate();
  215. }
  216. }
  217. }
  218. //----------------------------------------------------------------------------------------------------------------------------------------------
  219. //----------------------------------------------------------------------------------------------------------------------------------------------
  220. /**
  221. * Directly beeline toward victim if we have a clear shot, otherwise pathfind.
  222. */
  223. class DirectChasePath : public ChasePath
  224. {
  225. public:
  226. DirectChasePath( ChasePath::SubjectChaseType chaseHow = ChasePath::DONT_LEAD_SUBJECT ) : ChasePath( chaseHow )
  227. {
  228. }
  229. //-------------------------------------------------------------------------------------------------------
  230. virtual void Update( INextBot *me, CBaseEntity *victim, const IPathCost &pathCost, Vector *pPredictedSubjectPos = NULL ) // update path to chase target and move bot along path
  231. {
  232. Assert( !pPredictedSubjectPos );
  233. bool bComputedPredictedPosition;
  234. Vector vecPredictedPosition;
  235. if ( !DirectChase( &bComputedPredictedPosition, &vecPredictedPosition, me, victim ) )
  236. {
  237. // path around obstacles to reach our victim
  238. ChasePath::Update( me, victim, pathCost, bComputedPredictedPosition ? &vecPredictedPosition : NULL );
  239. }
  240. NotifyVictim( me, victim );
  241. }
  242. //-------------------------------------------------------------------------------------------------------
  243. bool DirectChase( bool *pPredictedPositionComputed, Vector *pPredictedPos, INextBot *me, CBaseEntity *victim ) // if there is nothing between us and our victim, run directly at them
  244. {
  245. *pPredictedPositionComputed = false;
  246. ILocomotion *mover = me->GetLocomotionInterface();
  247. if ( me->IsImmobile() || mover->IsScrambling() )
  248. {
  249. return false;
  250. }
  251. if ( IsDiscontinuityAhead( me, CLIMB_UP ) )
  252. {
  253. return false;
  254. }
  255. if ( IsDiscontinuityAhead( me, JUMP_OVER_GAP ) )
  256. {
  257. return false;
  258. }
  259. Vector leadVictimPos = PredictSubjectPosition( me, victim );
  260. // Don't want to have to compute the predicted position twice.
  261. *pPredictedPositionComputed = true;
  262. *pPredictedPos = leadVictimPos;
  263. if ( !mover->IsPotentiallyTraversable( mover->GetFeet(), leadVictimPos ) )
  264. {
  265. return false;
  266. }
  267. // the way is clear - move directly towards our victim
  268. mover->FaceTowards( leadVictimPos );
  269. mover->Approach( leadVictimPos );
  270. me->GetBodyInterface()->AimHeadTowards( victim );
  271. // old path is no longer useful since we've moved off of it
  272. Invalidate();
  273. return true;
  274. }
  275. //-------------------------------------------------------------------------------------------------------
  276. virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const // return true if situation has changed enough to warrant recomputing the current path
  277. {
  278. if ( ChasePath::IsRepathNeeded( bot, subject ) )
  279. {
  280. return true;
  281. }
  282. return bot->GetLocomotionInterface()->IsStuck() && bot->GetLocomotionInterface()->GetStuckDuration() > 2.0f;
  283. }
  284. //-------------------------------------------------------------------------------------------------------
  285. /**
  286. * Determine exactly where the path goes between the given two areas
  287. * on the path. Return this point in 'crossPos'.
  288. */
  289. virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const
  290. {
  291. Vector center;
  292. float halfWidth;
  293. from->ComputePortal( to, dir, &center, &halfWidth );
  294. *crossPos = center;
  295. }
  296. void NotifyVictim( INextBot *me, CBaseEntity *victim );
  297. };
  298. #endif // _NEXT_BOT_CHASE_PATH_