Team Fortress 2 Source Code as on 22/4/2020
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.

1923 lines
57 KiB

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