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.

1898 lines
56 KiB

  1. // NextBotPathFollow.cpp
  2. // Path following
  3. // Author: Michael Booth, April 2005
  4. // Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
  5. #include "cbase.h"
  6. #include "BasePropDoor.h"
  7. #include "nav_mesh.h"
  8. #include "NextBot.h"
  9. #include "NextBotPathFollow.h"
  10. #include "NextBotUtil.h"
  11. #include "NextBotLocomotionInterface.h"
  12. #include "NextBotBodyInterface.h"
  13. #include "NextBotVisionInterface.h"
  14. #include "tier0/vprof.h"
  15. // memdbgon must be the last include file in a .cpp file!!!
  16. #include "tier0/memdbgon.h"
  17. ConVar NextBotSpeedLookAheadRange( "nb_speed_look_ahead_range", "150", FCVAR_CHEAT );
  18. ConVar NextBotGoalLookAheadRange( "nb_goal_look_ahead_range", "50", FCVAR_CHEAT );
  19. ConVar NextBotLadderAlignRange( "nb_ladder_align_range", "50", FCVAR_CHEAT );
  20. ConVar NextBotAllowAvoiding( "nb_allow_avoiding", "1", FCVAR_CHEAT );
  21. ConVar NextBotAllowClimbing( "nb_allow_climbing", "1", FCVAR_CHEAT );
  22. ConVar NextBotAllowGapJumping( "nb_allow_gap_jumping", "1", FCVAR_CHEAT );
  23. ConVar NextBotDebugClimbing( "nb_debug_climbing", "0", FCVAR_CHEAT );
  24. //--------------------------------------------------------------------------------------------------------------
  25. /**
  26. * Constructor
  27. */
  28. PathFollower::PathFollower( void )
  29. {
  30. m_goal = NULL;
  31. m_didAvoidCheck = false;
  32. m_avoidTimer.Invalidate();
  33. m_waitTimer.Invalidate();
  34. m_hindrance = NULL;
  35. m_minLookAheadRange = -1.0f;
  36. }
  37. //--------------------------------------------------------------------------------------------------------------
  38. class CDetachPath
  39. {
  40. public:
  41. CDetachPath( PathFollower *path )
  42. {
  43. m_path = path;
  44. }
  45. bool operator() ( INextBot *bot )
  46. {
  47. bot->NotifyPathDestruction( m_path );
  48. return true;
  49. }
  50. PathFollower *m_path;
  51. };
  52. //--------------------------------------------------------------------------------------------------------------
  53. PathFollower::~PathFollower()
  54. {
  55. // allow bots to detach pointer to me
  56. CDetachPath detach( this );
  57. TheNextBots().ForEachBot( detach );
  58. }
  59. //--------------------------------------------------------------------------------------------------------------
  60. /**
  61. * When the path is invalidated, the follower is also reset
  62. */
  63. void PathFollower::Invalidate( void )
  64. {
  65. // extend
  66. Path::Invalidate();
  67. m_goal = NULL;
  68. m_avoidTimer.Invalidate();
  69. m_waitTimer.Invalidate();
  70. m_hindrance = NULL;
  71. }
  72. //--------------------------------------------------------------------------------------------------------------
  73. /**
  74. * Invoked when the path is (re)computed (path is valid at the time of this call)
  75. */
  76. void PathFollower::OnPathChanged( INextBot *bot, Path::ResultType result )
  77. {
  78. // start from the beginning
  79. m_goal = FirstSegment();
  80. }
  81. //--------------------------------------------------------------------------------------------------------------
  82. /**
  83. * Adjust speed based on path curvature
  84. */
  85. void PathFollower::AdjustSpeed( INextBot *bot )
  86. {
  87. ILocomotion *mover = bot->GetLocomotionInterface();
  88. // if we're coming up on a gap jump, or we're in the air, use maximum speed
  89. if ( ( m_goal && m_goal->type == JUMP_OVER_GAP ) || !mover->IsOnGround() )
  90. {
  91. mover->SetDesiredSpeed( mover->GetRunSpeed() );
  92. return;
  93. }
  94. MoveCursorToClosestPosition( bot->GetPosition() );
  95. const Path::Data &data = GetCursorData();
  96. // speed based on curvature
  97. mover->SetDesiredSpeed( mover->GetRunSpeed() + fabs( data.curvature ) * ( mover->GetWalkSpeed() - mover->GetRunSpeed() ) );
  98. }
  99. //--------------------------------------------------------------------------------------------------------------
  100. /**
  101. * Return true if reached current goal along path
  102. * NOTE: Ladder goals are handled elsewhere
  103. */
  104. bool PathFollower::IsAtGoal( INextBot *bot ) const
  105. {
  106. VPROF_BUDGET( "PathFollower::IsAtGoal", "NextBot" );
  107. ILocomotion *mover = bot->GetLocomotionInterface();
  108. IBody *body = bot->GetBodyInterface();
  109. //
  110. // m_goal is the node we are moving toward along the path
  111. // current is the node just behind us
  112. //
  113. const Segment *current = PriorSegment( m_goal );
  114. Vector toGoal = m_goal->pos - mover->GetFeet();
  115. // if ( m_goal->type == JUMP_OVER_GAP && !mover->IsOnGround() )
  116. // {
  117. // // jumping over a gap, don't skip ahead until we land
  118. // return false;
  119. // }
  120. if ( current == NULL )
  121. {
  122. // passed goal
  123. return true;
  124. }
  125. else if ( m_goal->type == DROP_DOWN )
  126. {
  127. // m_goal is the top of the drop-down, and the following segment is the landing point
  128. const Segment *landing = NextSegment( m_goal );
  129. if ( landing == NULL )
  130. {
  131. // passed goal or corrupt path
  132. return true;
  133. }
  134. else
  135. {
  136. // did we reach the ground
  137. if ( mover->GetFeet().z - landing->pos.z < mover->GetStepHeight() )
  138. {
  139. // reached goal
  140. return true;
  141. }
  142. }
  143. /// @todo: it is possible to fall into a bad place and get stuck - should move back onto the path
  144. }
  145. else if ( m_goal->type == CLIMB_UP )
  146. {
  147. // once jump is started, assume it is successful, since
  148. // nav mesh may be substantially off from actual ground height at landing
  149. const Segment *landing = NextSegment( m_goal );
  150. if ( landing == NULL )
  151. {
  152. // passed goal or corrupt path
  153. return true;
  154. }
  155. else if ( /*!mover->IsOnGround() && */ mover->GetFeet().z > m_goal->pos.z + mover->GetStepHeight() )
  156. {
  157. // we're off the ground, presumably climbing - assume we reached the goal
  158. return true;
  159. }
  160. /* This breaks infected climbing up holes in the ceiling - they can get within 2D range of m_goal before finding a ledge to climb up to
  161. else if ( mover->IsOnGround() )
  162. {
  163. // proximity check
  164. // Z delta can be anything, since we may be climbing over a tall fence, a physics prop, etc.
  165. const float rangeTolerance = 10.0f;
  166. if ( toGoal.AsVector2D().IsLengthLessThan( rangeTolerance ) )
  167. {
  168. // reached goal
  169. return true;
  170. }
  171. }
  172. */
  173. }
  174. else
  175. {
  176. const Segment *next = NextSegment( m_goal );
  177. if ( next )
  178. {
  179. // because mover may be off the path, check if it crossed the plane of the goal
  180. // check against average of current and next forward vectors
  181. Vector2D dividingPlane;
  182. if ( current->ladder )
  183. {
  184. dividingPlane = m_goal->forward.AsVector2D();
  185. }
  186. else
  187. {
  188. dividingPlane = current->forward.AsVector2D() + m_goal->forward.AsVector2D();
  189. }
  190. if ( DotProduct2D( toGoal.AsVector2D(), dividingPlane ) < 0.0001f &&
  191. abs( toGoal.z ) < body->GetStandHullHeight() )
  192. {
  193. // only skip higher Z goal if next goal is directly reachable
  194. // can't use this for positions below us because we need to be able
  195. // to climb over random objects along our path that we can't actually
  196. // move *through*
  197. if ( toGoal.z < mover->GetStepHeight() || ( mover->IsPotentiallyTraversable( mover->GetFeet(), next->pos ) && !mover->HasPotentialGap( mover->GetFeet(), next->pos ) ) )
  198. {
  199. // passed goal
  200. return true;
  201. }
  202. }
  203. }
  204. // proximity check
  205. // Z delta can be anything, since we may be climbing over a tall fence, a physics prop, etc.
  206. const float rangeTolerance = 25.0f; // was 10.0f for L4D - need a better solution here (MSB 5/15/09)
  207. if ( toGoal.AsVector2D().IsLengthLessThan( rangeTolerance ) )
  208. {
  209. // reached goal
  210. return true;
  211. }
  212. }
  213. return false;
  214. }
  215. //--------------------------------------------------------------------------------------------------------------
  216. /**
  217. * Move bot along ladder. Return true if ladder motion is in progress, false if complete.
  218. */
  219. bool PathFollower::LadderUpdate( INextBot *bot )
  220. {
  221. VPROF_BUDGET( "PathFollower::LadderUpdate", "NextBot" );
  222. ILocomotion *mover = bot->GetLocomotionInterface();
  223. IBody *body = bot->GetBodyInterface();
  224. if ( mover->IsUsingLadder() )
  225. {
  226. // wait for locomotor to finish traversing ladder
  227. return true;
  228. }
  229. if ( m_goal->ladder == NULL )
  230. {
  231. // Check if we have somehow ended up on a ladder, if so, and its a tall down-ladder we are expecting, jump the path ahead.
  232. // This happens for players, who run off ledges and the gamemovement sticks them onto ladders. We only care about
  233. // tall down-ladders, because up ladders work without this, and short ladders aren't dangerous to miss and drop down
  234. // instead of climbing down.
  235. if ( bot->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
  236. {
  237. // 'current' is the segment we are on/just passed over
  238. const Segment *current = PriorSegment( m_goal );
  239. if ( current == NULL )
  240. {
  241. return false;
  242. }
  243. // Start with current, the segment we are currently traversing. Skip the distance check for that segment, because
  244. // the pos is (hopefully) behind us. And if it's a long path segment, it's already outside the climbLookAheadRange,
  245. // and thus it would prevent us looking at m_goal and further for imminent planned climbs.
  246. // 'current' is the segment we are on/just passed over
  247. const float ladderLookAheadRange = 50.0f;
  248. for( const Segment *s = current; s; s = NextSegment( s ) )
  249. {
  250. if ( s != current && ( s->pos - mover->GetFeet() ).AsVector2D().IsLengthGreaterThan( ladderLookAheadRange ) )
  251. {
  252. break;
  253. }
  254. // Only consider reasonably tall down ladders - if we don't grab onto a short ladder, it hopefully won't be a bad fall.
  255. if ( s->ladder != NULL && s->how == GO_LADDER_DOWN && s->ladder->m_length > mover->GetMaxJumpHeight() )
  256. {
  257. float destinationHeightDelta = s->pos.z - mover->GetFeet().z;
  258. if ( fabs(destinationHeightDelta) < mover->GetMaxJumpHeight() )
  259. {
  260. // Advance the goal, and fall through to the normal codepath.
  261. m_goal = s;
  262. break;
  263. }
  264. }
  265. }
  266. }
  267. if ( m_goal->ladder == NULL )
  268. {
  269. // no ladder to use
  270. return false;
  271. }
  272. }
  273. // start using the ladder
  274. const float mountRange = 25.0f;
  275. if ( m_goal->how == GO_LADDER_UP )
  276. {
  277. // check if we're off the ladder and at the top
  278. if ( !mover->IsUsingLadder() && mover->GetFeet().z > m_goal->ladder->m_top.z - mover->GetStepHeight() )
  279. {
  280. // we're up
  281. m_goal = NextSegment( m_goal );
  282. return false;
  283. }
  284. // approach the ladder
  285. Vector2D to = ( m_goal->ladder->m_bottom - mover->GetFeet() ).AsVector2D();
  286. body->AimHeadTowards( m_goal->ladder->m_top - 50.0f * m_goal->ladder->GetNormal() + Vector( 0, 0, body->GetCrouchHullHeight() ),
  287. IBody::CRITICAL,
  288. 2.0f,
  289. NULL,
  290. "Mounting upward ladder" );
  291. float range = to.NormalizeInPlace();
  292. if ( range < NextBotLadderAlignRange.GetFloat() )
  293. {
  294. // getting close - line up
  295. Vector2D ladderNormal2D = m_goal->ladder->GetNormal().AsVector2D();
  296. float dot = DotProduct2D( ladderNormal2D, to );
  297. const float cos5 = 0.9f;
  298. if ( dot < -cos5 )
  299. {
  300. // lined up - continue approach
  301. mover->Approach( m_goal->ladder->m_bottom );
  302. if ( range < mountRange )
  303. {
  304. // go up ladder
  305. mover->ClimbLadder( m_goal->ladder, m_goal->area );
  306. }
  307. }
  308. else
  309. {
  310. // rotate around ladder and maintain distance from it
  311. Vector myPerp( -to.y, to.x, 0.0f );
  312. Vector2D ladderPerp2D( -ladderNormal2D.y, ladderNormal2D.x );
  313. Vector goal = m_goal->ladder->m_bottom;
  314. float alignRange = NextBotLadderAlignRange.GetFloat();
  315. if ( dot < 0.0f )
  316. {
  317. // we are on the correct side of the ladder
  318. // align range should drop off as we reach alignment
  319. alignRange = mountRange + (1.0f + dot) * (alignRange - mountRange);
  320. }
  321. goal.x -= alignRange * to.x;
  322. goal.y -= alignRange * to.y;
  323. if ( DotProduct2D( to, ladderPerp2D ) < 0.0f )
  324. {
  325. goal += 10.0f * myPerp;
  326. }
  327. else
  328. {
  329. goal -= 10.0f * myPerp;
  330. }
  331. mover->Approach( goal );
  332. }
  333. }
  334. else
  335. {
  336. // approach the base of the ladder - use normal path following in case there are jumps/climbs on the way to the ladder
  337. return false;
  338. }
  339. }
  340. else // go down ladder
  341. {
  342. // check if we fell off and are now below the ladder
  343. if ( mover->GetFeet().z < m_goal->ladder->m_bottom.z + mover->GetStepHeight() )
  344. {
  345. // we fell
  346. m_goal = NextSegment( m_goal );
  347. }
  348. else
  349. {
  350. // approach the ladder
  351. Vector mountPoint = m_goal->ladder->m_top + 0.5f * body->GetHullWidth() * m_goal->ladder->GetNormal();
  352. Vector2D to = ( mountPoint - mover->GetFeet() ).AsVector2D();
  353. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  354. {
  355. const float size = 5.0f;
  356. NDebugOverlay::Sphere( mountPoint, size, 255, 0, 255, true, 0.1f );
  357. }
  358. body->AimHeadTowards( m_goal->ladder->m_bottom + 50.0f * m_goal->ladder->GetNormal() + Vector( 0, 0, body->GetCrouchHullHeight() ),
  359. IBody::CRITICAL,
  360. 1.0f,
  361. NULL,
  362. "Mounting downward ladder" );
  363. float range = to.NormalizeInPlace();
  364. // Approach the top of the ladder. If we're already on the ladder, start descending.
  365. if ( range < mountRange || bot->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
  366. {
  367. // go down ladder
  368. mover->DescendLadder( m_goal->ladder, m_goal->area );
  369. // increment goal segment since locomotor will move us along the ladder
  370. m_goal = NextSegment( m_goal );
  371. }
  372. else
  373. {
  374. // approach the top of the ladder - use normal path following in case there are jumps/climbs on the way to the ladder
  375. return false;
  376. }
  377. }
  378. }
  379. return true;
  380. }
  381. //--------------------------------------------------------------------------------------------------------------
  382. /**
  383. * Check if we have reached our current path goal and
  384. * iterate to next goal or finish the path
  385. */
  386. bool PathFollower::CheckProgress( INextBot *bot )
  387. {
  388. ILocomotion *mover = bot->GetLocomotionInterface();
  389. // skip nearby goal points that are redundant to smooth path following motion
  390. if ( m_minLookAheadRange > 0.0f )
  391. {
  392. const Vector &myFeet = mover->GetFeet();
  393. while( m_goal && m_goal->type == ON_GROUND && mover->IsOnGround() )
  394. {
  395. if ( ( m_goal->pos - myFeet ).IsLengthLessThan( m_minLookAheadRange ) )
  396. {
  397. // goal is too close - step to next segment
  398. const Path::Segment *nextSegment = NextSegment( m_goal );
  399. if ( !nextSegment || nextSegment->type != ON_GROUND )
  400. {
  401. // can't skip ahead to next segment - head towards current goal
  402. break;
  403. }
  404. /*
  405. if ( DotProduct( mover->GetMotionVector(), nextSegment->forward ) <= 0.1f )
  406. {
  407. // don't skip sharp turns
  408. break;
  409. }
  410. */
  411. // can we reach the next path segment directly
  412. if ( mover->IsPotentiallyTraversable( myFeet, nextSegment->pos ) && !mover->HasPotentialGap( myFeet, nextSegment->pos ) )
  413. {
  414. m_goal = nextSegment;
  415. }
  416. else
  417. {
  418. // can't directly reach next segment - keep heading towards current goal
  419. break;
  420. }
  421. }
  422. else
  423. {
  424. // goal is farther than min lookahead
  425. break;
  426. }
  427. }
  428. }
  429. if ( IsAtGoal( bot ) )
  430. {
  431. // iterate to next segment of the path
  432. const Path::Segment *nextSegment = NextSegment( m_goal );
  433. if ( nextSegment == NULL )
  434. {
  435. // must be on ground to complete the path
  436. if ( mover->IsOnGround() )
  437. {
  438. // the end of the path has been reached
  439. mover->GetBot()->OnMoveToSuccess( this );
  440. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  441. {
  442. DevMsg( "PathFollower: OnMoveToSuccess\n" );
  443. }
  444. // don't invalidate if OnMoveToSuccess just recomputed a new path
  445. if ( GetAge() > 0.0f )
  446. {
  447. Invalidate();
  448. }
  449. return false;
  450. }
  451. }
  452. else
  453. {
  454. // keep moving
  455. m_goal = nextSegment;
  456. }
  457. }
  458. return true;
  459. }
  460. //--------------------------------------------------------------------------------------------------------------
  461. /**
  462. * Move mover along path
  463. */
  464. void PathFollower::Update( INextBot *bot )
  465. {
  466. VPROF_BUDGET( "PathFollower::Update", "NextBotSpiky" );
  467. // track most recent path followed
  468. bot->SetCurrentPath( this );
  469. ILocomotion *mover = bot->GetLocomotionInterface();
  470. if ( !IsValid() || m_goal == NULL )
  471. {
  472. return;
  473. }
  474. if ( !m_waitTimer.IsElapsed() )
  475. {
  476. // still waiting
  477. //mover->ClearStuckStatus( "Waiting for blocker to move" );
  478. return;
  479. }
  480. // m_didAvoidCheck = false;
  481. if ( LadderUpdate( bot ) )
  482. {
  483. // we are traversing a ladder
  484. return;
  485. }
  486. // adjust speed based on path curvature
  487. AdjustSpeed( bot );
  488. if ( CheckProgress( bot ) == false )
  489. {
  490. // goal reached
  491. return;
  492. }
  493. // use the direction towards the goal as 'forward' direction
  494. Vector forward = m_goal->pos - mover->GetFeet();
  495. if ( m_goal->type == CLIMB_UP )
  496. {
  497. const Segment *next = NextSegment( m_goal );
  498. if ( next )
  499. {
  500. // use landing of climb up as forward to help ledge detection
  501. forward = next->pos - mover->GetFeet();
  502. }
  503. }
  504. forward.z = 0.0f;
  505. float goalRange = forward.NormalizeInPlace();
  506. Vector left( -forward.y, forward.x, 0.0f );
  507. if ( left.IsZero() )
  508. {
  509. // if left is zero, forward must also be - path follow failure
  510. mover->GetBot()->OnMoveToFailure( this, FAIL_STUCK );
  511. // don't invalidate if OnMoveToFailure just recomputed a new path
  512. if ( GetAge() > 0.0f )
  513. {
  514. Invalidate();
  515. }
  516. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  517. {
  518. DevMsg( "PathFollower: OnMoveToFailure( FAIL_STUCK ) because forward and left are ZERO\n" );
  519. }
  520. return;
  521. }
  522. // unit vectors must follow floor slope
  523. const Vector &normal = mover->GetGroundNormal();
  524. // get forward vector along floor
  525. forward = CrossProduct( left, normal );
  526. // correct the sideways vector
  527. left = CrossProduct( normal, forward );
  528. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  529. {
  530. float axisSize = 25.0f;
  531. NDebugOverlay::Line( mover->GetFeet(), mover->GetFeet() + axisSize * forward, 255, 0, 0, true, 0.1f );
  532. NDebugOverlay::Line( mover->GetFeet(), mover->GetFeet() + axisSize * normal, 0, 255, 0, true, 0.1f );
  533. NDebugOverlay::Line( mover->GetFeet(), mover->GetFeet() + axisSize * left, 0, 0, 255, true, 0.1f );
  534. }
  535. // climb up ledges
  536. if ( !Climbing( bot, m_goal, forward, left, goalRange ) )
  537. {
  538. // a failed climb could mean an invalid path
  539. if ( !IsValid() )
  540. {
  541. return;
  542. }
  543. // jump over gaps
  544. JumpOverGaps( bot, m_goal, forward, left, goalRange );
  545. }
  546. // event callbacks from the above climbs and jumps may invalidate the path
  547. if ( !IsValid() )
  548. {
  549. return;
  550. }
  551. // if our movement goal is high above us, we must have fallen
  552. CNavArea *myArea = bot->GetEntity()->GetLastKnownArea();
  553. bool isOnStairs = ( myArea && myArea->HasAttributes( NAV_MESH_STAIRS ) );
  554. // limit too high distance to reasonable value for bots that can climb very high
  555. float tooHighDistance = mover->GetMaxJumpHeight();
  556. if ( !m_goal->ladder && !mover->IsClimbingOrJumping() && !isOnStairs && m_goal->pos.z > mover->GetFeet().z + tooHighDistance )
  557. {
  558. const float closeRange = 25.0f; // 75.0f;
  559. Vector2D to( mover->GetFeet().x - m_goal->pos.x, mover->GetFeet().y - m_goal->pos.y );
  560. if ( mover->IsStuck() || to.IsLengthLessThan( closeRange ) )
  561. {
  562. // the goal is too high to reach
  563. // check if we can reach the next segment, in case this was a "jump down" situation
  564. const Path::Segment *next = NextSegment( m_goal );
  565. if ( mover->IsStuck() || !next || ( next->pos.z - mover->GetFeet().z > mover->GetMaxJumpHeight() ) || !mover->IsPotentiallyTraversable( mover->GetFeet(), next->pos ) )
  566. {
  567. // the next node is too high, too - we really did fall off the path
  568. mover->GetBot()->OnMoveToFailure( this, FAIL_FELL_OFF );
  569. // don't invalidate if OnMoveToFailure just recomputed a new path
  570. if ( GetAge() > 0.0f )
  571. {
  572. Invalidate();
  573. }
  574. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  575. {
  576. DevMsg( "PathFollower: OnMoveToFailure( FAIL_FELL_OFF )\n" );
  577. }
  578. return;
  579. }
  580. }
  581. }
  582. Vector goalPos = m_goal->pos;
  583. // avoid small obstacles
  584. forward = goalPos - mover->GetFeet();
  585. forward.z = 0.0f;
  586. float rangeToGoal = forward.NormalizeInPlace();
  587. left.x = -forward.y;
  588. left.y = forward.x;
  589. left.z = 0.0f;
  590. if ( true || m_goal != LastSegment() ) // think more about this - we often need to avoid to reach the final goal pos, too (MSB 5/15/09)
  591. {
  592. const float nearLedgeRange = 50.0f;
  593. if ( rangeToGoal > nearLedgeRange || ( m_goal && m_goal->type != CLIMB_UP ) )
  594. {
  595. goalPos = Avoid( bot, goalPos, forward, left );
  596. }
  597. }
  598. // face towards movement goal
  599. if ( mover->IsOnGround() )
  600. {
  601. mover->FaceTowards( goalPos );
  602. }
  603. // move bot along path
  604. mover->Approach( goalPos );
  605. // Currently, Approach determines STAND or CROUCH.
  606. // Override this if we're approaching a climb or a jump
  607. if ( m_goal && ( m_goal->type == CLIMB_UP || m_goal->type == JUMP_OVER_GAP ) )
  608. {
  609. bot->GetBodyInterface()->SetDesiredPosture( IBody::STAND );
  610. }
  611. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  612. {
  613. const Segment *start = GetCurrentGoal();
  614. if ( start )
  615. {
  616. start = PriorSegment( start );
  617. }
  618. Draw( start );
  619. /*
  620. else
  621. {
  622. DrawInterpolated( 0.0f, GetLength() );
  623. }
  624. */
  625. NDebugOverlay::Cross3D( goalPos, 5.0f, 150, 150, 255, true, 0.1f );
  626. NDebugOverlay::Line( bot->GetEntity()->WorldSpaceCenter(), goalPos, 255, 255, 0, true, 0.1f );
  627. }
  628. }
  629. //--------------------------------------------------------------------------------------------------------------
  630. /**
  631. * If entity is returned, it is blocking us from continuing along our path
  632. */
  633. CBaseEntity *PathFollower::FindBlocker( INextBot *bot )
  634. {
  635. IIntention *think = bot->GetIntentionInterface();
  636. // if we don't care about hindrances, don't do the expensive tests
  637. if ( think->IsHindrance( bot, IS_ANY_HINDRANCE_POSSIBLE ) != ANSWER_YES )
  638. return NULL;
  639. ILocomotion *mover = bot->GetLocomotionInterface();
  640. IBody *body = bot->GetBodyInterface();
  641. trace_t result;
  642. NextBotTraceFilterOnlyActors filter( bot->GetEntity(), COLLISION_GROUP_NONE );
  643. const float size = body->GetHullWidth()/4.0f; // keep this small to avoid lockups when groups of bots get close
  644. Vector blockerMins( -size, -size, mover->GetStepHeight() );
  645. Vector blockerMaxs( size, size, body->GetCrouchHullHeight() );
  646. Vector from = mover->GetFeet();
  647. float range = 0.0f;
  648. const float maxHindranceRangeAlong = 750.0f;
  649. // because our path goal may be far ahead of us if the way to there is unobstructed, we
  650. // need to start looking from the point of the path we are actually standing on
  651. MoveCursorToClosestPosition( mover->GetFeet() );
  652. for( const Segment *s = GetCursorData().segmentPrior; s && range < maxHindranceRangeAlong; s = NextSegment( s ) )
  653. {
  654. // trace along direction toward goal a minimum range, in case goal and hindrance are
  655. // very close, but goal is closer
  656. Vector traceForward = s->pos - from;
  657. float traceRange = traceForward.NormalizeInPlace();
  658. const float minTraceRange = 2.0f * body->GetHullWidth();
  659. if ( traceRange < minTraceRange )
  660. {
  661. traceRange = minTraceRange;
  662. }
  663. mover->TraceHull( from, from + traceRange * traceForward, blockerMins, blockerMaxs, body->GetSolidMask(), &filter, &result );
  664. if ( result.DidHitNonWorldEntity() )
  665. {
  666. // if blocker is close, they could be behind us - check
  667. Vector toBlocker = result.m_pEnt->GetAbsOrigin() - bot->GetLocomotionInterface()->GetFeet();
  668. Vector alongPath = s->pos - from;
  669. alongPath.z = 0.0f;
  670. if ( DotProduct( toBlocker, alongPath ) > 0.0f )
  671. {
  672. // ask the bot if this really is a hindrance
  673. if ( think->IsHindrance( bot, result.m_pEnt ) == ANSWER_YES )
  674. {
  675. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  676. {
  677. NDebugOverlay::Circle( bot->GetLocomotionInterface()->GetFeet(), QAngle( -90.0f, 0, 0 ), 10.0f, 255, 0, 0, 255, true, 1.0f );
  678. NDebugOverlay::HorzArrow( bot->GetLocomotionInterface()->GetFeet(), result.m_pEnt->GetAbsOrigin(), 1.0f, 255, 0, 0, 255, true, 1.0f );
  679. }
  680. // we are blocked
  681. return result.m_pEnt;
  682. }
  683. }
  684. }
  685. from = s->pos;
  686. range += s->length;
  687. }
  688. return NULL;
  689. }
  690. //--------------------------------------------------------------------------------------------------------------
  691. /**
  692. * Do reflex avoidance movements of very nearby obstacles.
  693. * Return adjusted goal.
  694. */
  695. Vector PathFollower::Avoid( INextBot *bot, const Vector &goalPos, const Vector &forward, const Vector &left )
  696. {
  697. VPROF_BUDGET( "PathFollower::Avoid", "NextBotExpensive" );
  698. if ( !NextBotAllowAvoiding.GetBool() )
  699. {
  700. return goalPos;
  701. }
  702. if ( !m_avoidTimer.IsElapsed() )
  703. {
  704. return goalPos;
  705. }
  706. // low frequency check until we actually hit something we need to avoid
  707. const float avoidInterval = 0.5f; // 1.0f;
  708. m_avoidTimer.Start( avoidInterval );
  709. ILocomotion *mover = bot->GetLocomotionInterface();
  710. IBody *body = bot->GetBodyInterface();
  711. unsigned int mask = body->GetSolidMask();
  712. Vector adjustedGoal = goalPos;
  713. if ( mover->IsClimbingOrJumping() || !mover->IsOnGround() )
  714. {
  715. return adjustedGoal;
  716. }
  717. // we want to avoid other players, etc
  718. trace_t result;
  719. NextBotTraceFilterOnlyActors filter( bot->GetEntity(), COLLISION_GROUP_NONE );
  720. //
  721. // Check for potential blockers along our path and wait if we're blocked
  722. //
  723. m_hindrance = FindBlocker( bot );
  724. if ( m_hindrance != NULL )
  725. {
  726. // wait
  727. m_waitTimer.Start( avoidInterval * RandomFloat( 1.0f, 2.0f ) );
  728. return mover->GetFeet();
  729. }
  730. // if we are in a "precise" area, do not use avoid volumes
  731. CNavArea *area = bot->GetEntity()->GetLastKnownArea();
  732. if ( area && ( area->GetAttributes() & NAV_MESH_PRECISE ) )
  733. {
  734. return adjustedGoal;
  735. }
  736. m_didAvoidCheck = true;
  737. const float offset = ( body->GetHullWidth()/4.0f ) + 2.0f;
  738. const float range = mover->IsRunning() ? 50.0f : 30.0f;
  739. const float size = body->GetHullWidth()/4.0f;
  740. m_hullMin = Vector( -size, -size, mover->GetStepHeight()+0.1f );
  741. // only use crouch-high avoid volumes, since we'll just crouch if higher obstacles are near
  742. m_hullMax = Vector( size, size, body->GetCrouchHullHeight() );
  743. Vector nextStepHullMin( -size, -size, 2.0f * mover->GetStepHeight() + 0.1f );
  744. // avoid any open doors in our way
  745. CBasePropDoor *door = NULL;
  746. // check left side
  747. m_leftFrom = mover->GetFeet() + offset * left;
  748. m_leftTo = m_leftFrom + range * forward;
  749. m_isLeftClear = true;
  750. float leftAvoid = 0.0f;
  751. NextBotTraversableTraceFilter traverseFilter( bot );
  752. mover->TraceHull( m_leftFrom, m_leftTo, m_hullMin, m_hullMax, mask, &traverseFilter, &result );
  753. if ( result.fraction < 1.0f || result.startsolid )
  754. {
  755. // if this sensor is starting in a solid, set fraction to emulate being against a wall
  756. if ( result.startsolid )
  757. {
  758. result.fraction = 0.0f;
  759. }
  760. leftAvoid = clamp( 1.0f - result.fraction, 0.0f, 1.0f );
  761. m_isLeftClear = false;
  762. // track any doors we need to avoid
  763. if ( result.DidHitNonWorldEntity() )
  764. {
  765. door = dynamic_cast< CBasePropDoor * >( result.m_pEnt );
  766. }
  767. // check for steps
  768. // float firstHit = result.fraction;
  769. // mover->TraceHull( m_leftFrom, m_leftTo, nextStepHullMin, m_hullMax, mask, &filter, &result );
  770. // if ( result.fraction <= firstHit ) //+ mover->GetStepHeight()/2.0f )
  771. // {
  772. // // it's not a step - we hit something
  773. // m_isLeftClear = false;
  774. // }
  775. }
  776. // check right side
  777. m_rightFrom = mover->GetFeet() - offset * left;
  778. m_rightTo = m_rightFrom + range * forward;
  779. m_isRightClear = true;
  780. float rightAvoid = 0.0f;
  781. mover->TraceHull( m_rightFrom, m_rightTo, m_hullMin, m_hullMax, mask, &traverseFilter, &result );
  782. if ( result.fraction < 1.0f || result.startsolid )
  783. {
  784. // if this sensor is starting in a solid, set fraction to emulate being against a wall
  785. if ( result.startsolid )
  786. {
  787. result.fraction = 0.0f;
  788. }
  789. rightAvoid = clamp( 1.0f - result.fraction, 0.0f, 1.0f );
  790. m_isRightClear = false;
  791. // track any doors we need to avoid
  792. if ( !door && result.DidHitNonWorldEntity() )
  793. {
  794. door = dynamic_cast< CBasePropDoor * >( result.m_pEnt );
  795. }
  796. // check for steps
  797. // float firstHit = result.fraction;
  798. // mover->TraceHull( m_rightFrom, m_rightTo, nextStepHullMin, m_hullMax, mask, &filter, &result );
  799. // if ( result.fraction <= firstHit ) // + mover->GetStepHeight()/2.0f)
  800. // {
  801. // // it's not a step - we hit something
  802. // m_isRightClear = false;
  803. // }
  804. }
  805. // avoid doors directly in our way
  806. if ( door && !m_isLeftClear && !m_isRightClear )
  807. {
  808. Vector forward, right, up;
  809. AngleVectors( door->GetAbsAngles(), &forward, &right, &up );
  810. const float doorWidth = 100.0f;
  811. Vector doorEdge = door->GetAbsOrigin() - doorWidth * right;
  812. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  813. {
  814. NDebugOverlay::Axis( door->GetAbsOrigin(), door->GetAbsAngles(), 20.0f, true, 10.0f );
  815. NDebugOverlay::Line( door->GetAbsOrigin(), doorEdge, 255, 255, 0, true, 10.0f );
  816. }
  817. // move around door
  818. adjustedGoal.x = doorEdge.x;
  819. adjustedGoal.y = doorEdge.y;
  820. // do avoid check again next frame
  821. m_avoidTimer.Invalidate();
  822. }
  823. else if ( !m_isLeftClear || !m_isRightClear )
  824. {
  825. // adjust goal to avoid small obstacle
  826. float avoidResult = 0.0f;
  827. if ( m_isLeftClear )
  828. {
  829. avoidResult = -rightAvoid;
  830. }
  831. else if (m_isRightClear)
  832. {
  833. avoidResult = leftAvoid;
  834. }
  835. else
  836. {
  837. // both left and right are blocked, avoid nearest
  838. const float equalTolerance = 0.01f;
  839. if ( fabs( rightAvoid - leftAvoid ) < equalTolerance )
  840. {
  841. // squarely against a wall, etc
  842. return adjustedGoal;
  843. }
  844. else if ( rightAvoid > leftAvoid )
  845. {
  846. avoidResult = -rightAvoid;
  847. }
  848. else
  849. {
  850. avoidResult = leftAvoid;
  851. }
  852. }
  853. // adjust goal to avoid obstacle
  854. Vector avoidDir = 0.5f * forward - left * avoidResult;
  855. avoidDir.NormalizeInPlace();
  856. adjustedGoal = mover->GetFeet() + 100.0f * avoidDir;
  857. // do avoid check again next frame
  858. m_avoidTimer.Invalidate();
  859. }
  860. return adjustedGoal;
  861. }
  862. #ifdef EXPERIMENTAL_LEDGE_FINDER
  863. //--------------------------------------------------------------------------------------------------------------
  864. /**
  865. * Given a hull that defines the area of space that may contain a climbable ledge,
  866. * subdivide it until we find the ledge.
  867. */
  868. bool PathFollower::FindClimbLedge( INextBot *bot, Vector startTracePos, Vector ledgeRegionMins, Vector ledgeRegionMaxs )
  869. {
  870. float deltaZ = ledgeRegionMaxs.z - ledgeRegionMins.z;
  871. if ( deltaZ <= bot->GetLocomotionInterface()->GetStepHeight() )
  872. {
  873. // reached minimum subdivision limit - stop
  874. return false;
  875. }
  876. trace_t result;
  877. NextBotTraversableTraceFilter filter( bot, ILocomotion::IMMEDIATELY );
  878. mover->TraceHull( startTracePos, startTracePos,
  879. ledgeRegionMins, ledgeRegionMaxs,
  880. bot->GetBodyInterface()->GetSolidMask(), &filter, &result );
  881. if ( result.DidHit() )
  882. {
  883. // volume is blocked - split into upper and lower volumes and try again
  884. float midZ = ( ledgeRegionMins.z + ledgeRegionMaxs.z ) / 2.0f;
  885. Vector upperLedgeRegionMins( ledgeRegionMins.x, ledgeRegionMins.y, midZ );
  886. Vector upperLedgeRegionMaxs = ledgeRegionMaxs;
  887. FindClimbLedge( bot, startTracePos, upperLedgeRegionMins, upperLedgeRegionMaxs );
  888. Vector lowerLedgeRegionMins = ledgeRegionMins;
  889. Vector lowerLedgeRegionMaxs( ledgeRegionMaxs.x, ledgeRegionMaxs.y, midZ );
  890. FindClimbLedge( bot, startTracePos, lowerLedgeRegionMins, lowerLedgeRegionMaxs );
  891. }
  892. else
  893. {
  894. // volume is clear, trace straight down to find ledge and keep lowest one we've found
  895. mover->TraceHull( startTracePos,
  896. startTracePos + Vector( 0, 0, -100.0f ),
  897. ledgeRegionMins, ledgeRegionMaxs,
  898. bot->GetBodyInterface()->GetSolidMask(), &filter, &result );
  899. }
  900. }
  901. #endif // _DEBUG
  902. //--------------------------------------------------------------------------------------------------------------
  903. /**
  904. * Climb up ledges
  905. */
  906. bool PathFollower::Climbing( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &right, float goalRange )
  907. {
  908. VPROF_BUDGET( "PathFollower::Climbing", "NextBot" );
  909. ILocomotion *mover = bot->GetLocomotionInterface();
  910. IBody *body = bot->GetBodyInterface();
  911. CNavArea *myArea = bot->GetEntity()->GetLastKnownArea();
  912. if ( !mover->IsAbleToClimb() || !NextBotAllowClimbing.GetBool() )
  913. {
  914. return false;
  915. }
  916. // use the 2D direction towards our goal
  917. Vector climbDirection = forward;
  918. climbDirection.z = 0.0f;
  919. climbDirection.NormalizeInPlace();
  920. // we can't have this as large as our hull width, or we'll find ledges ahead of us
  921. // that we will fall from when we climb up because our hull wont actually touch at the top.
  922. const float ledgeLookAheadRange = body->GetHullWidth() - 1;
  923. if ( mover->IsClimbingOrJumping() || mover->IsAscendingOrDescendingLadder() || !mover->IsOnGround() )
  924. {
  925. return false;
  926. }
  927. // can be in any posture when we climb
  928. if ( m_goal == NULL )
  929. {
  930. return false;
  931. }
  932. if ( TheNavMesh->IsAuthoritative() )
  933. {
  934. //
  935. // Trust what that nav mesh tells us.
  936. // No need for expensive ledge-finding for games with simpler geometry (like TF2)
  937. //
  938. if ( m_goal->type == CLIMB_UP )
  939. {
  940. const Segment *afterClimb = NextSegment( m_goal );
  941. if ( afterClimb && afterClimb->area )
  942. {
  943. // find closest point on climb-destination area
  944. Vector nearClimbGoal;
  945. afterClimb->area->GetClosestPointOnArea( mover->GetFeet(), &nearClimbGoal );
  946. climbDirection = nearClimbGoal - mover->GetFeet();
  947. climbDirection.z = 0.0f;
  948. climbDirection.NormalizeInPlace();
  949. if ( mover->ClimbUpToLedge( nearClimbGoal, climbDirection, NULL ) )
  950. return true;
  951. }
  952. }
  953. return false;
  954. }
  955. // If we're approaching a CLIMB_UP link, save off the height delta for it, and trust the nav *just* enough
  956. // to climb up to that ledge and only that ledge. We keep as large a tolerance as possible, to trust
  957. // the nav as little as possible. There's no valid way to have another CLIMB_UP link within crouch height,
  958. // because we can't actually fit in between the two areas, so one climb is invalid.
  959. float climbUpLedgeHeightDelta = -1.0f;
  960. const float climbUpLedgeTolerance = body->GetCrouchHullHeight();
  961. if ( m_goal->type == CLIMB_UP )
  962. {
  963. const Segment *afterClimb = NextSegment( m_goal );
  964. if ( afterClimb && afterClimb->area )
  965. {
  966. // find closest point on climb-destination area
  967. Vector nearClimbGoal;
  968. afterClimb->area->GetClosestPointOnArea( mover->GetFeet(), &nearClimbGoal );
  969. climbDirection = nearClimbGoal - mover->GetFeet();
  970. climbUpLedgeHeightDelta = climbDirection.z;
  971. climbDirection.z = 0.0f;
  972. climbDirection.NormalizeInPlace();
  973. }
  974. }
  975. // don't try to climb up stairs
  976. if ( m_goal->area->HasAttributes( NAV_MESH_STAIRS ) || ( myArea && myArea->HasAttributes( NAV_MESH_STAIRS ) ) )
  977. {
  978. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  979. {
  980. NDebugOverlay::Cross3D( mover->GetFeet(), 5.0f, 0, 255, 255, true, 5.0f );
  981. DevMsg( "%3.2f: %s ON STAIRS\n", gpGlobals->curtime, bot->GetDebugIdentifier() );
  982. }
  983. return false;
  984. }
  985. // 'current' is the segment we are on/just passed over
  986. const Segment *current = PriorSegment( m_goal );
  987. if ( current == NULL )
  988. {
  989. return false;
  990. }
  991. // If path segment immediately ahead of us is not obstructed, don't try to climb.
  992. // This is required to try to avoid accidentally climbing onto valid high ledges when we really want to run UNDER them to our destination.
  993. // We need to check "immediate" traversability to pay attention to breakable objects in our way that we should climb over.
  994. // We also need to check traversability out to 2 * ledgeLookAheadRange in case our goal is just before a tricky ledge climb and once we pass the goal it will be too late.
  995. // When we're in a CLIMB_UP segment, allow us to look for ledges - we know the destination ledge height, and will only grab the correct ledge.
  996. Vector toGoal = m_goal->pos - mover->GetFeet();
  997. toGoal.NormalizeInPlace();
  998. if ( toGoal.z < mover->GetTraversableSlopeLimit() &&
  999. !mover->IsStuck() && m_goal->type != CLIMB_UP &&
  1000. mover->IsPotentiallyTraversable( mover->GetFeet(), mover->GetFeet() + 2.0f * ledgeLookAheadRange * toGoal, ILocomotion::IMMEDIATELY ) )
  1001. {
  1002. return false;
  1003. }
  1004. // can't do this - we have to find the ledge to deal with breakable railings
  1005. #if 0
  1006. // If our path requires a climb, do the climb.
  1007. // This solves some issues where there are several possible climbable ledges at a given
  1008. // location, and we need to know which ledge to climb - just use the preplanned path's choice.
  1009. const Segment *ledge = NextSegment( m_goal );
  1010. if ( m_goal->type == CLIMB_UP && ledge )
  1011. {
  1012. const float startClimbRange = body->GetHullWidth();
  1013. if ( ( m_goal->pos - mover->GetFeet() ).IsLengthLessThan( startClimbRange ) )
  1014. {
  1015. mover->ClimbUpToLedge( ledge->pos, climbDirection );
  1016. return true;
  1017. }
  1018. }
  1019. #endif
  1020. // Determine if we're approaching a planned climb.
  1021. // Start with current, the segment we are currently traversing. Skip the distance check for that segment, because
  1022. // the pos is (hopefully) behind us. And if it's a long path segment, it's already outside the climbLookAheadRange,
  1023. // and thus it would prevent us looking at m_goal and further for imminent planned climbs.
  1024. const float climbLookAheadRange = 150.0f;
  1025. bool isPlannedClimbImminent = false;
  1026. float plannedClimbZ = 0.0f;
  1027. for( const Segment *s = current; s; s = NextSegment( s ) )
  1028. {
  1029. if ( s != current && ( s->pos - mover->GetFeet() ).AsVector2D().IsLengthGreaterThan( climbLookAheadRange ) )
  1030. {
  1031. break;
  1032. }
  1033. if ( s->type == CLIMB_UP )
  1034. {
  1035. isPlannedClimbImminent = true;
  1036. const Segment *next = NextSegment( s );
  1037. if ( next )
  1038. {
  1039. plannedClimbZ = next->pos.z;
  1040. }
  1041. break;
  1042. }
  1043. }
  1044. unsigned int mask = body->GetSolidMask();
  1045. trace_t result;
  1046. NextBotTraversableTraceFilter filter( bot, ILocomotion::IMMEDIATELY );
  1047. const float hullWidth = body->GetHullWidth();
  1048. const float halfSize = hullWidth / 2.0f;
  1049. const float minHullHeight = body->GetCrouchHullHeight();
  1050. const float minLedgeHeight = mover->GetStepHeight() + 0.1f;
  1051. Vector skipStepHeightHullMin( -halfSize, -halfSize, minLedgeHeight );
  1052. // need to use minimum actual hull height here to catch porous fences and railings
  1053. Vector skipStepHeightHullMax( halfSize, halfSize, minHullHeight + 0.1f );
  1054. // Find the highest height we can stand at our current location.
  1055. // Using the full width hull catches on small lips/ledges, so back up and try again.
  1056. float ceilingFraction;
  1057. // We can't use IsPotentiallyTraversable to test for ledges, because it's smaller Hull can cause the
  1058. // next trace (trace the ceiling height forward) to start solid.
  1059. // mover->IsPotentiallyTraversable( mover->GetFeet(), mover->GetFeet() + Vector( 0, 0, mover->GetMaxJumpHeight() ), ILocomotion::IMMEDIATELY, &ceilingFraction );
  1060. // Instead of IsPotentiallyTraversable, we back up the same distance and use a second upward trace
  1061. // to see if that one finds a higher ceiling. If so, we use that ceiling height, and use the
  1062. // backed-up feet position for the ledge finding traces.
  1063. Vector feet( mover->GetFeet() );
  1064. Vector ceiling( feet + Vector( 0, 0, mover->GetMaxJumpHeight() ) );
  1065. mover->TraceHull( feet, ceiling,
  1066. skipStepHeightHullMin, skipStepHeightHullMax, mask, &filter, &result );
  1067. ceilingFraction = result.fraction;
  1068. bool isBackupTraceUsed = false;
  1069. if ( ceilingFraction < 1.0f || result.startsolid )
  1070. {
  1071. trace_t backupTrace;
  1072. const float backupDistance = hullWidth * 0.25f; // The IsPotentiallyTraversable check this replaces uses a 1/4 hull width trace
  1073. Vector backupFeet( feet - climbDirection * backupDistance );
  1074. Vector backupCeiling( backupFeet + Vector( 0, 0, mover->GetMaxJumpHeight() ) );
  1075. mover->TraceHull( backupFeet, backupCeiling,
  1076. skipStepHeightHullMin, skipStepHeightHullMax, mask, &filter, &backupTrace );
  1077. if ( !backupTrace.startsolid && backupTrace.fraction > ceilingFraction )
  1078. {
  1079. bot->DebugConColorMsg( NEXTBOT_PATH, Color( 255, 255, 255, 255 ), "%s backing up when looking for max ledge height\n", bot->GetDebugIdentifier() );
  1080. result = backupTrace;
  1081. ceilingFraction = result.fraction;
  1082. feet = backupFeet;
  1083. ceiling = backupCeiling;
  1084. isBackupTraceUsed = true;
  1085. }
  1086. }
  1087. float maxLedgeHeight = ceilingFraction * mover->GetMaxJumpHeight();
  1088. if ( maxLedgeHeight <= mover->GetStepHeight() )
  1089. {
  1090. return false; // early out when we can't even climb StepHeight.
  1091. }
  1092. //
  1093. // Check for ledge climbs over things in our way.
  1094. // Even if we have a CLIMB_UP link in our path, we still need
  1095. // to find the actual ledge by tracing the local geometry.
  1096. //
  1097. Vector climbHullMax( halfSize, halfSize, maxLedgeHeight );
  1098. Vector ledgePos = feet; // to be computed below
  1099. mover->TraceHull( feet,
  1100. feet + climbDirection * ledgeLookAheadRange,
  1101. skipStepHeightHullMin, climbHullMax, mask, &filter, &result );
  1102. if ( bot->IsDebugging( NEXTBOT_PATH ) && NextBotDebugClimbing.GetBool() )
  1103. {
  1104. // show ledge-finding hull as we move
  1105. NDebugOverlay::SweptBox( feet,
  1106. feet + climbDirection * ledgeLookAheadRange,
  1107. skipStepHeightHullMin, climbHullMax, vec3_angle,
  1108. 255, 100, 0, 255, 0.1f );
  1109. }
  1110. bool wasPotentialLedgeFound = result.DidHit() && !result.startsolid;
  1111. // To test climbing up past small lips on walls, we can force the bot to run past the overhang and use the backup trace:
  1112. // wasPotentialLedgeFound = wasPotentialLedgeFound && (result.fraction == 0 || isBackupTraceUsed);
  1113. if ( wasPotentialLedgeFound )
  1114. {
  1115. VPROF_BUDGET( "PathFollower::Climbing( Search for ledge to climb )", "NextBot" );
  1116. if ( bot->IsDebugging( NEXTBOT_PATH ) && NextBotDebugClimbing.GetBool() )
  1117. {
  1118. // show ledge-finding hull that found a ledge candidate
  1119. NDebugOverlay::SweptBox( feet,
  1120. feet + climbDirection * ledgeLookAheadRange,
  1121. skipStepHeightHullMin, climbHullMax, vec3_angle,
  1122. 255, 100, 0, 100, 999.9f );
  1123. // show primary climb direction
  1124. NDebugOverlay::HorzArrow( feet, feet + 50.0f * climbDirection, 2.0f, 0, 255, 0, 255, true, 9999.9f );
  1125. }
  1126. // what are we climbing over?
  1127. CBaseEntity *obstacle = result.m_pEnt;
  1128. if ( !result.DidHitNonWorldEntity() || bot->IsAbleToClimbOnto( obstacle ) )
  1129. {
  1130. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  1131. {
  1132. DevMsg( "%3.2f: %s at potential ledge climb\n", gpGlobals->curtime, bot->GetDebugIdentifier() );
  1133. }
  1134. // the low hull sweep hit an obstacle - note how 'far in' this is
  1135. float ledgeFrontWallDepth = ledgeLookAheadRange * result.fraction;
  1136. float minLedgeDepth = body->GetHullWidth()/2.0f; // 5.0f;
  1137. if ( m_goal->type == CLIMB_UP )
  1138. {
  1139. // Climbing up to a narrow nav area indicates a narrow ledge. We need to reduce our minLedgeDepth
  1140. // here or our path will say we should climb but we'll forever fail to find a wide enough ledge.
  1141. const Segment *afterClimb = NextSegment( m_goal );
  1142. if ( afterClimb && afterClimb->area )
  1143. {
  1144. Vector depthVector = climbDirection * minLedgeDepth;
  1145. depthVector.z = 0;
  1146. if ( fabs( depthVector.x ) > afterClimb->area->GetSizeX() )
  1147. {
  1148. depthVector.x = (depthVector.x > 0) ? afterClimb->area->GetSizeX() : -afterClimb->area->GetSizeX();
  1149. }
  1150. if ( fabs( depthVector.y ) > afterClimb->area->GetSizeY() )
  1151. {
  1152. depthVector.y = (depthVector.y > 0) ? afterClimb->area->GetSizeY() : -afterClimb->area->GetSizeY();
  1153. }
  1154. float areaDepth = depthVector.NormalizeInPlace();
  1155. minLedgeDepth = MIN( minLedgeDepth, areaDepth );
  1156. }
  1157. }
  1158. //
  1159. // Find the ledge. Start at the lowest jump we can make
  1160. // and step up until we find the actual ledge.
  1161. //
  1162. // The scan is limited to maxLedgeHeight in case our max
  1163. // jump/climb height is so tall the highest horizontal hull
  1164. // trace could be on the other side of the ceiling above us
  1165. //
  1166. float ledgeHeight = minLedgeHeight;
  1167. const float ledgeHeightIncrement = 0.5f * mover->GetStepHeight();
  1168. bool foundWall = false;
  1169. bool foundLedge = false;
  1170. // once we have found the ledge's front wall, we must look at least minLedgeDepth farther in to verify it is a ledge
  1171. // NOTE: This *must* be ledgeLookAheadRange since ledges are compared against the initial trace which was ledgeLookAheadRange deep
  1172. float ledgeTopLookAheadRange = ledgeLookAheadRange;
  1173. // TODO: we also must look far enough ahead in case the ledge we actually find is "deeper" than the initial wall at the base
  1174. Vector climbHullMin( -halfSize, -halfSize, 0.0f );
  1175. Vector climbHullMax( halfSize, halfSize, minHullHeight );
  1176. Vector wallPos;
  1177. float wallDepth = 0.0f;
  1178. bool isLastIteration = false;
  1179. while( true )
  1180. {
  1181. // trace forward to find the wall in front of us, or the empty space of the ledge above us
  1182. mover->TraceHull( feet + Vector( 0, 0, ledgeHeight ),
  1183. feet + Vector( 0, 0, ledgeHeight ) + climbDirection * ledgeTopLookAheadRange,
  1184. climbHullMin, climbHullMax, mask, &filter, &result );
  1185. float traceDepth = ledgeTopLookAheadRange * result.fraction;
  1186. if ( !result.startsolid )
  1187. {
  1188. // if trace reached minLedgeDepth farther, this is a potential ledge
  1189. if ( foundWall )
  1190. {
  1191. // TODO: test that potential ledge is flat enough to stand on
  1192. if ( ( traceDepth - ledgeFrontWallDepth ) > minLedgeDepth )
  1193. {
  1194. bool isUsable = true;
  1195. // initialize ledgePos from result of last trace
  1196. ledgePos = result.endpos;
  1197. // Find the actual ground level on the potential ledge
  1198. // Only trace back down to the previous ledge height trace.
  1199. // The ledge can be no lower, or we would've found it in the last iteration.
  1200. mover->TraceHull( ledgePos,
  1201. ledgePos + Vector( 0, 0, -ledgeHeightIncrement ),
  1202. climbHullMin, climbHullMax, mask, &filter, &result );
  1203. ledgePos = result.endpos;
  1204. // if the whole trace is in solid, we're out of luck, but
  1205. // if the trace just started solid, 'ledgePos' should still be valid
  1206. // since the trace left the solid and then hit.
  1207. // if the trace hit nothing, the potential ledge is actually deeper in
  1208. const float MinGroundNormal = 0.7f; // players can't stand on ground steeper than 0.7
  1209. if ( result.allsolid || !result.DidHit() || result.plane.normal.z < MinGroundNormal )
  1210. {
  1211. // not a usable ledge, try again
  1212. isUsable = false;
  1213. }
  1214. else
  1215. {
  1216. if ( climbUpLedgeHeightDelta > 0.0f )
  1217. {
  1218. // if we're climbing to a specific ledge via a CLIMB_UP link, only climb to that ledge.
  1219. // Do this only for the world (which includes static props) so we can still opportunistically
  1220. // climb up onto breakable railings and physics props.
  1221. if ( result.DidHitWorld() )
  1222. {
  1223. float potentialLedgeHeight = result.endpos.z - feet.z;
  1224. if ( fabs(potentialLedgeHeight - climbUpLedgeHeightDelta) > climbUpLedgeTolerance )
  1225. {
  1226. isUsable = false;
  1227. }
  1228. }
  1229. }
  1230. }
  1231. if ( isUsable )
  1232. {
  1233. // back up until we no longer are hitting the ledge to determine the
  1234. // exact ledge edge position
  1235. Vector validLedgePos = ledgePos; // save off a valid ledge pos
  1236. const float edgeTolerance = 4.0f;
  1237. const float maxBackUp = hullWidth;
  1238. float backUpSoFar = edgeTolerance;
  1239. Vector testPos = ledgePos;
  1240. while( backUpSoFar < maxBackUp )
  1241. {
  1242. testPos -= edgeTolerance * climbDirection;
  1243. backUpSoFar += edgeTolerance;
  1244. mover->TraceHull( testPos,
  1245. testPos + Vector( 0, 0, -ledgeHeightIncrement ),
  1246. climbHullMin, climbHullMax, mask, &filter, &result );
  1247. if ( bot->IsDebugging( NEXTBOT_PATH ) && NextBotDebugClimbing.GetBool() )
  1248. {
  1249. // show edge-finder hulls
  1250. NDebugOverlay::SweptBox( testPos,
  1251. testPos + Vector( 0, 0, -mover->GetStepHeight() ),
  1252. climbHullMin, climbHullMax, vec3_angle, 255, 0, 0, 255, 999.9f );
  1253. }
  1254. if ( result.DidHit() && result.plane.normal.z >= MinGroundNormal )
  1255. {
  1256. // we hit, this is closer to the actual ledge edge
  1257. ledgePos = result.endpos;
  1258. }
  1259. else
  1260. {
  1261. // nothing but air or a steep slope below us, we have found the edge
  1262. break;
  1263. }
  1264. }
  1265. // we want ledgePos to be right on the edge itself, so move
  1266. // it ahead by half of the hull width
  1267. ledgePos += climbDirection * halfSize;
  1268. // Make sure this doesn't embed us in the far wall if the ledge is narrow, since we would
  1269. // have backed up less than halfSize.
  1270. Vector climbHullMinStep( climbHullMin ); // skip StepHeight for sloped ledges
  1271. mover->TraceHull( validLedgePos,
  1272. ledgePos,
  1273. climbHullMinStep, climbHullMax, mask, &filter, &result );
  1274. ledgePos = result.endpos;
  1275. // Now since ledgePos + StepHeight is valid, trace down to find ground on sloped ledges.
  1276. mover->TraceHull( ledgePos + Vector( 0, 0, StepHeight ),
  1277. ledgePos,
  1278. climbHullMin, climbHullMax, mask, &filter, &result );
  1279. if ( !result.startsolid )
  1280. {
  1281. ledgePos = result.endpos;
  1282. }
  1283. }
  1284. /*** NOTE: While this saves us from climbing into a window below the window we want to get in,
  1285. *** it also causes us to climb in midair high over crates sitting against walls we need to climb over.
  1286. if ( isUsable && m_goal->type == CLIMB_UP )
  1287. {
  1288. // we can only accept ledges at least as high as our current CLIMB_UP destination
  1289. // NOTE: Can't use plannedClimbZ here, since that could be 2 or 3 short climbs ahead
  1290. const Segment *ledge = NextSegment( m_goal );
  1291. if ( !ledge || ledgeHeight < ledge->pos.z - feet.z - mover->GetStepHeight() )
  1292. {
  1293. // this ledge is below the CLIMB_UP destination - can't use it
  1294. isUsable = false;
  1295. }
  1296. }
  1297. */
  1298. if ( isUsable )
  1299. {
  1300. // found a usable ledge here
  1301. foundLedge = true;
  1302. break;
  1303. }
  1304. }
  1305. }
  1306. else if ( result.DidHit() )
  1307. {
  1308. // this iteration hit the wall under the ledge,
  1309. // meaning the next iteration that reaches far enough will be our ledge
  1310. // Since we know that our desired route is likely blocked (via the
  1311. // IsTraversable check above) - any ledge we hit we must climb.
  1312. // found a valid ledge wall
  1313. foundWall = true;
  1314. wallDepth = traceDepth;
  1315. // make sure the subsequent traces are at least minLedgeDepth deeper than
  1316. // the wall we just found, or all ledge checks will fail
  1317. float minTraceDepth = traceDepth + minLedgeDepth + 0.1f;
  1318. if ( ledgeTopLookAheadRange < minTraceDepth )
  1319. {
  1320. ledgeTopLookAheadRange = minTraceDepth;
  1321. }
  1322. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  1323. {
  1324. DevMsg( "%3.2f: Climbing - found wall.\n", gpGlobals->curtime );
  1325. if ( NextBotDebugClimbing.GetBool() )
  1326. {
  1327. NDebugOverlay::HorzArrow( result.endpos, result.endpos + 20.0f * result.plane.normal, 5.0f, 255, 100, 0, 255, true, 9999.9f );
  1328. }
  1329. wallPos = result.endpos;
  1330. }
  1331. }
  1332. else if ( ledgeHeight > body->GetCrouchHullHeight() && !isPlannedClimbImminent )
  1333. {
  1334. // we haven't hit anything yet, and we're already above our heads - no obstacle
  1335. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  1336. {
  1337. DevMsg( "%3.2f: Climbing - skipping overhead climb we can walk/crawl under.\n", gpGlobals->curtime );
  1338. }
  1339. break;
  1340. }
  1341. }
  1342. ledgeHeight += ledgeHeightIncrement;
  1343. if ( ledgeHeight >= maxLedgeHeight )
  1344. {
  1345. if ( isLastIteration )
  1346. {
  1347. // tested at max height
  1348. break;
  1349. }
  1350. // check one more time at max jump height
  1351. isLastIteration = true;
  1352. ledgeHeight = maxLedgeHeight;
  1353. }
  1354. }
  1355. if ( foundLedge )
  1356. {
  1357. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  1358. {
  1359. DevMsg( "%3.2f: STARTING LEDGE CLIMB UP\n", gpGlobals->curtime );
  1360. if ( NextBotDebugClimbing.GetBool() )
  1361. {
  1362. NDebugOverlay::Cross3D( ledgePos, 10.0f, 0, 255, 0, true, 9999.9f );
  1363. // display approximation of idealized ledge that has been found
  1364. Vector side( -climbDirection.y, climbDirection.x, 0.0f );
  1365. // this is an approximation, since AABB can hit at any angle
  1366. Vector base = feet + halfSize * climbDirection;
  1367. Vector wallBottomLeft = base + halfSize * side;
  1368. Vector wallBottomRight = base - halfSize * side;
  1369. Vector wallTopLeft = wallBottomLeft + Vector( 0, 0, ledgeHeight );
  1370. Vector wallTopRight = wallBottomRight + Vector( 0, 0, ledgeHeight );
  1371. NDebugOverlay::Triangle( wallBottomRight, wallBottomLeft, wallTopLeft, 255, 100, 0, 100, true, 9999.9f );
  1372. NDebugOverlay::Triangle( wallBottomRight, wallTopLeft, wallTopRight, 255, 100, 0, 100, true, 9999.9f );
  1373. Vector ledgeLeft = ledgePos + halfSize * side;
  1374. Vector ledgeRight = ledgePos - halfSize * side;
  1375. NDebugOverlay::Triangle( wallTopRight, wallTopLeft, ledgeLeft, 0, 100, 255, 100, true, 9999.9f );
  1376. NDebugOverlay::Triangle( wallTopRight, ledgeLeft, ledgeRight, 0, 100, 255, 100, true, 9999.9f );
  1377. }
  1378. }
  1379. if ( !mover->ClimbUpToLedge( ledgePos, climbDirection, obstacle ) )
  1380. {
  1381. // climb failed - build a new path in case we're now stuck
  1382. //Invalidate();
  1383. return false;
  1384. }
  1385. return true;
  1386. }
  1387. else if ( bot->IsDebugging( NEXTBOT_PATH ) )
  1388. {
  1389. DevMsg( "%3.2f: CANT FIND LEDGE TO CLIMB\n", gpGlobals->curtime );
  1390. }
  1391. }
  1392. }
  1393. return false;
  1394. }
  1395. //--------------------------------------------------------------------------------------------------------------
  1396. /**
  1397. * Jump over gaps
  1398. */
  1399. bool PathFollower::JumpOverGaps( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &right, float goalRange )
  1400. {
  1401. VPROF_BUDGET( "PathFollower::JumpOverGaps", "NextBot" );
  1402. ILocomotion *mover = bot->GetLocomotionInterface();
  1403. IBody *body = bot->GetBodyInterface();
  1404. if ( !mover->IsAbleToJumpAcrossGaps() || !NextBotAllowGapJumping.GetBool() )
  1405. {
  1406. return false;
  1407. }
  1408. if ( mover->IsClimbingOrJumping() || mover->IsAscendingOrDescendingLadder() || !mover->IsOnGround() )
  1409. {
  1410. return false;
  1411. }
  1412. if ( !body->IsActualPosture( IBody::STAND ) )
  1413. {
  1414. // can't jump if we're not standing
  1415. return false;
  1416. }
  1417. if ( m_goal == NULL )
  1418. {
  1419. return false;
  1420. }
  1421. trace_t result;
  1422. NextBotTraversableTraceFilter filter( bot, ILocomotion::IMMEDIATELY );
  1423. const float hullWidth = ( body ) ? body->GetHullWidth() : 1.0f;
  1424. // 'current' is the segment we are on/just passed over
  1425. const Segment *current = PriorSegment( m_goal );
  1426. if ( current == NULL )
  1427. {
  1428. return false;
  1429. }
  1430. const float minGapJumpRange = 2.0f * hullWidth;
  1431. const Segment *gap = NULL;
  1432. if ( current->type == JUMP_OVER_GAP )
  1433. {
  1434. gap = current;
  1435. }
  1436. else
  1437. {
  1438. float searchRange = goalRange;
  1439. for( const Segment *s = m_goal; s; s = NextSegment( s ) )
  1440. {
  1441. if ( searchRange > minGapJumpRange )
  1442. {
  1443. break;
  1444. }
  1445. if ( s->type == JUMP_OVER_GAP )
  1446. {
  1447. gap = s;
  1448. break;
  1449. }
  1450. searchRange += s->length;
  1451. }
  1452. }
  1453. if ( gap )
  1454. {
  1455. VPROF_BUDGET( "PathFollower::GapJumping", "NextBot" );
  1456. float halfWidth = hullWidth/2.0f;
  1457. if ( mover->IsGap( mover->GetFeet() + halfWidth * gap->forward, gap->forward ) )
  1458. {
  1459. // there is a gap to jump over
  1460. const Segment *landing = NextSegment( gap );
  1461. if ( landing )
  1462. {
  1463. mover->JumpAcrossGap( landing->pos, landing->forward );
  1464. // if we're jumping over this gap, make sure our goal is the landing so we aim for it
  1465. m_goal = landing;
  1466. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  1467. {
  1468. NDebugOverlay::Cross3D( m_goal->pos, 5.0f, 0, 255, 255, true, 5.0f );
  1469. DevMsg( "%3.2f: GAP JUMP\n", gpGlobals->curtime );
  1470. }
  1471. return true;
  1472. }
  1473. }
  1474. }
  1475. return false;
  1476. }
  1477. //--------------------------------------------------------------------------------------------------------------
  1478. /**
  1479. * Draw the path for debugging
  1480. */
  1481. void PathFollower::Draw( const Path::Segment *start ) const
  1482. {
  1483. if ( m_goal == NULL )
  1484. return;
  1485. // show avoid volumes
  1486. if ( m_didAvoidCheck )
  1487. {
  1488. QAngle angles( 0, 0, 0 );
  1489. if (m_isLeftClear)
  1490. NDebugOverlay::SweptBox( m_leftFrom, m_leftTo, m_hullMin, m_hullMax, angles, 0, 255, 0, 255, 0.1f );
  1491. else
  1492. NDebugOverlay::SweptBox( m_leftFrom, m_leftTo, m_hullMin, m_hullMax, angles, 255, 0, 0, 255, 0.1f );
  1493. if (m_isRightClear)
  1494. NDebugOverlay::SweptBox( m_rightFrom, m_rightTo, m_hullMin, m_hullMax, angles, 0, 255, 0, 255, 0.1f );
  1495. else
  1496. NDebugOverlay::SweptBox( m_rightFrom, m_rightTo, m_hullMin, m_hullMax, angles, 255, 0, 0, 255, 0.1f );
  1497. const_cast< PathFollower * >( this )->m_didAvoidCheck = false;
  1498. }
  1499. // highlight current goal segment
  1500. if ( m_goal )
  1501. {
  1502. const float size = 5.0f;
  1503. NDebugOverlay::Sphere( m_goal->pos, size, 255, 255, 0, true, 0.1f );
  1504. switch( m_goal->how )
  1505. {
  1506. case GO_NORTH:
  1507. case GO_SOUTH:
  1508. NDebugOverlay::Line( m_goal->m_portalCenter - Vector( m_goal->m_portalHalfWidth, 0, 0 ), m_goal->m_portalCenter + Vector( m_goal->m_portalHalfWidth, 0, 0 ), 255, 0, 255, true, 0.1f );
  1509. break;
  1510. default:
  1511. NDebugOverlay::Line( m_goal->m_portalCenter - Vector( 0, m_goal->m_portalHalfWidth, 0 ), m_goal->m_portalCenter + Vector( 0, m_goal->m_portalHalfWidth, 0 ), 255, 0, 255, true, 0.1f );
  1512. break;
  1513. }
  1514. // 'current' is the segment we are on/just passed over
  1515. const Segment *current = PriorSegment( m_goal );
  1516. if ( current )
  1517. {
  1518. NDebugOverlay::Line( current->pos, m_goal->pos, 255, 255, 0, true, 0.1f );
  1519. }
  1520. }
  1521. // extend
  1522. Path::Draw();
  1523. }
  1524. //--------------------------------------------------------------------------------------------------------------
  1525. /**
  1526. * Return true if there is a the given discontinuity ahead in the path within the given range (-1 = entire remaining path)
  1527. */
  1528. bool PathFollower::IsDiscontinuityAhead( INextBot *bot, Path::SegmentType type, float range ) const
  1529. {
  1530. if ( m_goal )
  1531. {
  1532. const Path::Segment *current = PriorSegment( m_goal );
  1533. if ( current && current->type == type )
  1534. {
  1535. // we're on the discontinuity now
  1536. return true;
  1537. }
  1538. float rangeSoFar = ( m_goal->pos - bot->GetLocomotionInterface()->GetFeet() ).Length();
  1539. for( const Segment *s = m_goal; s; s = NextSegment( s ) )
  1540. {
  1541. if ( rangeSoFar >= range )
  1542. {
  1543. break;
  1544. }
  1545. if ( s->type == type )
  1546. {
  1547. return true;
  1548. }
  1549. rangeSoFar += s->length;
  1550. }
  1551. }
  1552. return false;
  1553. }