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.

2075 lines
54 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // Author: Michael S. Booth ([email protected]), 2003
  8. #include "cbase.h"
  9. #include "cs_bot.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. #ifdef _WIN32
  13. #pragma warning (disable:4701) // disable warning that variable *may* not be initialized
  14. #endif
  15. //--------------------------------------------------------------------------------------------------------------
  16. /**
  17. * Finds a point from which we can approach a descending ladder. First it tries behind the ladder,
  18. * then in front of ladder, based on LOS. Once we know the direction, we snap to the aproaching nav
  19. * area. Returns true if we're approaching from behind the ladder.
  20. */
  21. static bool FindDescendingLadderApproachPoint( const CNavLadder *ladder, const CNavArea *area, Vector *pos )
  22. {
  23. *pos = ladder->m_top - ladder->GetNormal() * 2.0f * HalfHumanWidth;
  24. trace_t result;
  25. UTIL_TraceLine( ladder->m_top, *pos, MASK_PLAYERSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
  26. if (result.fraction < 1.0f)
  27. {
  28. *pos = ladder->m_top + ladder->GetNormal() * 2.0f * HalfHumanWidth;
  29. area->GetClosestPointOnArea( *pos, pos );
  30. }
  31. // Use a cross product to determine which side of the ladder 'pos' is on
  32. Vector posToLadder = *pos - ladder->m_top;
  33. float dot = posToLadder.Dot( ladder->GetNormal() );
  34. return ( dot < 0.0f );
  35. }
  36. //--------------------------------------------------------------------------------------------------------------
  37. /**
  38. * Determine actual path positions bot will move between along the path
  39. */
  40. bool CCSBot::ComputePathPositions( void )
  41. {
  42. if (m_pathLength == 0)
  43. return false;
  44. // start in first area's center
  45. m_path[0].pos = m_path[0].area->GetCenter();
  46. m_path[0].ladder = NULL;
  47. m_path[0].how = NUM_TRAVERSE_TYPES;
  48. for( int i=1; i<m_pathLength; ++i )
  49. {
  50. const ConnectInfo *from = &m_path[ i-1 ];
  51. ConnectInfo *to = &m_path[ i ];
  52. if (to->how <= GO_WEST) // walk along the floor to the next area
  53. {
  54. to->ladder = NULL;
  55. // compute next point, keeping path as straight as possible
  56. from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, from->pos, &to->pos );
  57. // move goal position into the goal area a bit
  58. const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size
  59. AddDirectionVector( &to->pos, (NavDirType)to->how, stepInDist );
  60. // we need to walk out of "from" area, so keep Z where we can reach it
  61. to->pos.z = from->area->GetZ( to->pos );
  62. // if this is a "jump down" connection, we must insert an additional point on the path
  63. if (to->area->IsConnected( from->area, NUM_DIRECTIONS ) == false)
  64. {
  65. // this is a "jump down" link
  66. // compute direction of path just prior to "jump down"
  67. Vector2D dir;
  68. DirectionToVector2D( (NavDirType)to->how, &dir );
  69. // shift top of "jump down" out a bit to "get over the ledge"
  70. const float pushDist = 75.0f; // 25.0f;
  71. to->pos.x += pushDist * dir.x;
  72. to->pos.y += pushDist * dir.y;
  73. // insert a duplicate node to represent the bottom of the fall
  74. if (m_pathLength < MAX_PATH_LENGTH-1)
  75. {
  76. // copy nodes down
  77. for( int j=m_pathLength; j>i; --j )
  78. m_path[j] = m_path[j-1];
  79. // path is one node longer
  80. ++m_pathLength;
  81. // move index ahead into the new node we just duplicated
  82. ++i;
  83. m_path[i].pos.x = to->pos.x;
  84. m_path[i].pos.y = to->pos.y;
  85. // put this one at the bottom of the fall
  86. m_path[i].pos.z = to->area->GetZ( m_path[i].pos );
  87. }
  88. }
  89. }
  90. else if (to->how == GO_LADDER_UP) // to get to next area, must go up a ladder
  91. {
  92. // find our ladder
  93. const NavLadderConnectVector *pLadders = from->area->GetLadders( CNavLadder::LADDER_UP );
  94. int it;
  95. for ( it = 0; it < pLadders->Count(); ++it)
  96. {
  97. CNavLadder *ladder = (*pLadders)[ it ].ladder;
  98. // can't use "behind" area when ascending...
  99. if (ladder->m_topForwardArea == to->area ||
  100. ladder->m_topLeftArea == to->area ||
  101. ladder->m_topRightArea == to->area)
  102. {
  103. to->ladder = ladder;
  104. to->pos = ladder->m_bottom + ladder->GetNormal() * 2.0f * HalfHumanWidth;
  105. break;
  106. }
  107. }
  108. if (it == pLadders->Count())
  109. {
  110. PrintIfWatched( "ERROR: Can't find ladder in path\n" );
  111. return false;
  112. }
  113. }
  114. else if (to->how == GO_LADDER_DOWN) // to get to next area, must go down a ladder
  115. {
  116. // find our ladder
  117. const NavLadderConnectVector *pLadders = from->area->GetLadders( CNavLadder::LADDER_DOWN );
  118. int it;
  119. for ( it = 0; it < pLadders->Count(); ++it)
  120. {
  121. CNavLadder *ladder = (*pLadders)[ it ].ladder;
  122. if (ladder->m_bottomArea == to->area)
  123. {
  124. to->ladder = ladder;
  125. FindDescendingLadderApproachPoint( to->ladder, from->area, &to->pos );
  126. break;
  127. }
  128. }
  129. if (it == pLadders->Count())
  130. {
  131. PrintIfWatched( "ERROR: Can't find ladder in path\n" );
  132. return false;
  133. }
  134. }
  135. }
  136. return true;
  137. }
  138. //--------------------------------------------------------------------------------------------------------------
  139. /**
  140. * If next step of path uses a ladder, prepare to traverse it
  141. */
  142. void CCSBot::SetupLadderMovement( void )
  143. {
  144. if (m_pathIndex < 1 || m_pathLength == 0)
  145. return;
  146. const ConnectInfo *to = &m_path[ m_pathIndex ];
  147. const ConnectInfo *from = &m_path[ m_pathIndex - 1 ];
  148. if (to->ladder)
  149. {
  150. m_spotEncounter = NULL;
  151. m_areaEnteredTimestamp = gpGlobals->curtime;
  152. m_pathLadder = to->ladder;
  153. m_pathLadderTimestamp = gpGlobals->curtime;
  154. QAngle ladderAngles;
  155. VectorAngles( m_pathLadder->GetNormal(), ladderAngles );
  156. // to get to next area, we must traverse a ladder
  157. if (to->how == GO_LADDER_UP)
  158. {
  159. m_pathLadderState = APPROACH_ASCENDING_LADDER;
  160. m_pathLadderFaceIn = true;
  161. PrintIfWatched( "APPROACH_ASCENDING_LADDER\n" );
  162. m_goalPosition = m_pathLadder->m_bottom + m_pathLadder->GetNormal() * 2.0f * HalfHumanWidth;
  163. m_lookAheadAngle = AngleNormalizePositive( ladderAngles[ YAW ] + 180.0f );
  164. }
  165. else
  166. {
  167. // try to mount ladder "face out" first
  168. bool behind = FindDescendingLadderApproachPoint( m_pathLadder, from->area, &m_goalPosition );
  169. if ( behind )
  170. {
  171. PrintIfWatched( "APPROACH_DESCENDING_LADDER (face out)\n" );
  172. m_pathLadderState = APPROACH_DESCENDING_LADDER;
  173. m_pathLadderFaceIn = false;
  174. m_lookAheadAngle = ladderAngles[ YAW ];
  175. }
  176. else
  177. {
  178. PrintIfWatched( "APPROACH_DESCENDING_LADDER (face in)\n" );
  179. m_pathLadderState = APPROACH_DESCENDING_LADDER;
  180. m_pathLadderFaceIn = true;
  181. m_lookAheadAngle = AngleNormalizePositive( ladderAngles[ YAW ] + 180.0f );
  182. }
  183. }
  184. }
  185. }
  186. //--------------------------------------------------------------------------------------------------------------
  187. /// @todo What about ladders whose top AND bottom are messed up?
  188. void CCSBot::ComputeLadderEndpoint( bool isAscending )
  189. {
  190. trace_t result;
  191. Vector from, to;
  192. if (isAscending)
  193. {
  194. // find actual top in case m_pathLadder penetrates the ceiling
  195. // trace from our chest height at m_pathLadder base
  196. from = m_pathLadder->m_bottom + m_pathLadder->GetNormal() * HalfHumanWidth;
  197. from.z = GetAbsOrigin().z + HalfHumanHeight;
  198. to = m_pathLadder->m_top;
  199. }
  200. else
  201. {
  202. // find actual bottom in case m_pathLadder penetrates the floor
  203. // trace from our chest height at m_pathLadder top
  204. from = m_pathLadder->m_top + m_pathLadder->GetNormal() * HalfHumanWidth;
  205. from.z = GetAbsOrigin().z + HalfHumanHeight;
  206. to = m_pathLadder->m_bottom;
  207. }
  208. UTIL_TraceLine( from, m_pathLadder->m_bottom, MASK_PLAYERSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
  209. if (result.fraction == 1.0f)
  210. m_pathLadderEnd = to.z;
  211. else
  212. m_pathLadderEnd = from.z + result.fraction * (to.z - from.z);
  213. }
  214. //--------------------------------------------------------------------------------------------------------------
  215. /**
  216. * Navigate our current ladder. Return true if we are doing ladder navigation.
  217. * @todo Need Push() and Pop() for run/walk context to keep ladder speed contained.
  218. */
  219. bool CCSBot::UpdateLadderMovement( void )
  220. {
  221. if (m_pathLadder == NULL)
  222. return false;
  223. bool giveUp = false;
  224. // check for timeout
  225. const float ladderTimeoutDuration = 10.0f;
  226. if (gpGlobals->curtime - m_pathLadderTimestamp > ladderTimeoutDuration && !cv_bot_debug.GetBool())
  227. {
  228. PrintIfWatched( "Ladder timeout!\n" );
  229. giveUp = true;
  230. }
  231. else if (m_pathLadderState == APPROACH_ASCENDING_LADDER ||
  232. m_pathLadderState == APPROACH_DESCENDING_LADDER ||
  233. m_pathLadderState == ASCEND_LADDER ||
  234. m_pathLadderState == DESCEND_LADDER ||
  235. m_pathLadderState == DISMOUNT_ASCENDING_LADDER ||
  236. m_pathLadderState == MOVE_TO_DESTINATION)
  237. {
  238. if (m_isStuck)
  239. {
  240. PrintIfWatched( "Giving up ladder - stuck\n" );
  241. giveUp = true;
  242. }
  243. }
  244. if (giveUp)
  245. {
  246. // jump off ladder and give up
  247. Jump( MUST_JUMP );
  248. Wiggle();
  249. ResetStuckMonitor();
  250. DestroyPath();
  251. Run();
  252. return false;
  253. }
  254. else
  255. {
  256. ResetStuckMonitor();
  257. }
  258. Vector myOrigin = GetCentroid( this );
  259. // check if somehow we totally missed the ladder
  260. switch( m_pathLadderState )
  261. {
  262. case MOUNT_ASCENDING_LADDER:
  263. case MOUNT_DESCENDING_LADDER:
  264. case ASCEND_LADDER:
  265. case DESCEND_LADDER:
  266. {
  267. const float farAway = 200.0f;
  268. const Vector &ladderPos = (m_pathLadderState == MOUNT_ASCENDING_LADDER ||
  269. m_pathLadderState == ASCEND_LADDER) ? m_pathLadder->m_bottom : m_pathLadder->m_top;
  270. if ((ladderPos.AsVector2D() - myOrigin.AsVector2D()).IsLengthGreaterThan( farAway ))
  271. {
  272. PrintIfWatched( "Missed ladder\n" );
  273. Jump( MUST_JUMP );
  274. DestroyPath();
  275. Run();
  276. return false;
  277. }
  278. break;
  279. }
  280. }
  281. m_areaEnteredTimestamp = gpGlobals->curtime;
  282. const float tolerance = 10.0f;
  283. const float closeToGoal = 25.0f;
  284. switch( m_pathLadderState )
  285. {
  286. case APPROACH_ASCENDING_LADDER:
  287. {
  288. bool approached = false;
  289. Vector2D d( myOrigin.x - m_goalPosition.x, myOrigin.y - m_goalPosition.y );
  290. if (d.x * m_pathLadder->GetNormal().x + d.y * m_pathLadder->GetNormal().y < 0.0f)
  291. {
  292. Vector2D perp( -m_pathLadder->GetNormal().y, m_pathLadder->GetNormal().x );
  293. if (fabs(d.x * perp.x + d.y * perp.y) < tolerance && d.Length() < closeToGoal)
  294. approached = true;
  295. }
  296. // small radius will just slow them down a little for more accuracy in hitting their spot
  297. const float walkRange = 50.0f;
  298. if (d.IsLengthLessThan( walkRange ))
  299. {
  300. Walk();
  301. StandUp();
  302. }
  303. if ( d.IsLengthLessThan( 100.0f ) )
  304. {
  305. if ( !IsOnLadder() && (m_pathLadder->m_bottom.z - GetAbsOrigin().z > JumpCrouchHeight ) )
  306. {
  307. // find yaw to directly aim at ladder
  308. QAngle idealAngle;
  309. VectorAngles( GetAbsVelocity(), idealAngle );
  310. const float angleTolerance = 15.0f;
  311. if (AnglesAreEqual( EyeAngles().y, idealAngle.y, angleTolerance ))
  312. {
  313. Jump();
  314. }
  315. }
  316. }
  317. /// @todo Check that we are on the ladder we think we are
  318. if (IsOnLadder())
  319. {
  320. m_pathLadderState = ASCEND_LADDER;
  321. PrintIfWatched( "ASCEND_LADDER\n" );
  322. // find actual top in case m_pathLadder penetrates the ceiling
  323. ComputeLadderEndpoint( true );
  324. }
  325. else if (approached)
  326. {
  327. // face the m_pathLadder
  328. m_pathLadderState = FACE_ASCENDING_LADDER;
  329. PrintIfWatched( "FACE_ASCENDING_LADDER\n" );
  330. }
  331. else
  332. {
  333. // move toward ladder mount point
  334. MoveTowardsPosition( m_goalPosition );
  335. }
  336. break;
  337. }
  338. case APPROACH_DESCENDING_LADDER:
  339. {
  340. // fall check
  341. if (GetFeetZ() <= m_pathLadder->m_bottom.z + HalfHumanHeight)
  342. {
  343. PrintIfWatched( "Fell from ladder.\n" );
  344. m_pathLadderState = MOVE_TO_DESTINATION;
  345. m_path[ m_pathIndex ].area->GetClosestPointOnArea( m_pathLadder->m_bottom, &m_goalPosition );
  346. m_goalPosition += m_pathLadder->GetNormal() * HalfHumanWidth;
  347. PrintIfWatched( "MOVE_TO_DESTINATION\n" );
  348. }
  349. else
  350. {
  351. bool approached = false;
  352. Vector2D d( myOrigin.x - m_goalPosition.x, myOrigin.y - m_goalPosition.y );
  353. if (d.x * m_pathLadder->GetNormal().x + d.y * m_pathLadder->GetNormal().y > 0.0f)
  354. {
  355. Vector2D perp( -m_pathLadder->GetNormal().y, m_pathLadder->GetNormal().x );
  356. if (fabs(d.x * perp.x + d.y * perp.y) < tolerance && d.Length() < closeToGoal)
  357. approached = true;
  358. }
  359. // if approaching ladder from the side or "ahead", walk
  360. if (m_pathLadder->m_topBehindArea != m_lastKnownArea)
  361. {
  362. const float walkRange = 150.0f;
  363. if (!IsCrouching() && d.IsLengthLessThan( walkRange ))
  364. Walk();
  365. }
  366. /// @todo Check that we are on the ladder we think we are
  367. if (IsOnLadder())
  368. {
  369. // we slipped onto the ladder - climb it
  370. m_pathLadderState = DESCEND_LADDER;
  371. Run();
  372. PrintIfWatched( "DESCEND_LADDER\n" );
  373. // find actual bottom in case m_pathLadder penetrates the floor
  374. ComputeLadderEndpoint( false );
  375. }
  376. else if (approached)
  377. {
  378. // face the ladder
  379. m_pathLadderState = FACE_DESCENDING_LADDER;
  380. PrintIfWatched( "FACE_DESCENDING_LADDER\n" );
  381. }
  382. else
  383. {
  384. // move toward ladder mount point
  385. MoveTowardsPosition( m_goalPosition );
  386. }
  387. }
  388. break;
  389. }
  390. case FACE_ASCENDING_LADDER:
  391. {
  392. // find yaw to directly aim at ladder
  393. Vector to = m_pathLadder->GetPosAtHeight(myOrigin.z) - myOrigin;
  394. QAngle idealAngle;
  395. VectorAngles( to, idealAngle );
  396. if (m_path[ m_pathIndex ].area == m_pathLadder->m_topForwardArea)
  397. {
  398. m_pathLadderDismountDir = FORWARD;
  399. }
  400. else if (m_path[ m_pathIndex ].area == m_pathLadder->m_topLeftArea)
  401. {
  402. m_pathLadderDismountDir = LEFT;
  403. idealAngle[ YAW ] = AngleNormalizePositive( idealAngle[ YAW ] + 90.0f );
  404. }
  405. else if (m_path[ m_pathIndex ].area == m_pathLadder->m_topRightArea)
  406. {
  407. m_pathLadderDismountDir = RIGHT;
  408. idealAngle[ YAW ] = AngleNormalizePositive( idealAngle[ YAW ] - 90.0f );
  409. }
  410. const float angleTolerance = 5.0f;
  411. if (AnglesAreEqual( EyeAngles().y, idealAngle.y, angleTolerance ))
  412. {
  413. // move toward ladder until we become "on" it
  414. Run();
  415. ResetStuckMonitor();
  416. m_pathLadderState = MOUNT_ASCENDING_LADDER;
  417. switch (m_pathLadderDismountDir)
  418. {
  419. case LEFT: PrintIfWatched( "MOUNT_ASCENDING_LADDER LEFT\n" ); break;
  420. case RIGHT: PrintIfWatched( "MOUNT_ASCENDING_LADDER RIGHT\n" ); break;
  421. default: PrintIfWatched( "MOUNT_ASCENDING_LADDER FORWARD\n" ); break;
  422. }
  423. }
  424. break;
  425. }
  426. case FACE_DESCENDING_LADDER:
  427. {
  428. // find yaw to directly aim at ladder
  429. Vector to = m_pathLadder->GetPosAtHeight(myOrigin.z) - myOrigin;
  430. QAngle idealAngle;
  431. VectorAngles( to, idealAngle );
  432. const float angleTolerance = 5.0f;
  433. if (AnglesAreEqual( EyeAngles().y, idealAngle.y, angleTolerance ))
  434. {
  435. // move toward ladder until we become "on" it
  436. m_pathLadderState = MOUNT_DESCENDING_LADDER;
  437. ResetStuckMonitor();
  438. PrintIfWatched( "MOUNT_DESCENDING_LADDER\n" );
  439. }
  440. break;
  441. }
  442. case MOUNT_ASCENDING_LADDER:
  443. if (IsOnLadder())
  444. {
  445. m_pathLadderState = ASCEND_LADDER;
  446. PrintIfWatched( "ASCEND_LADDER\n" );
  447. // find actual top in case m_pathLadder penetrates the ceiling
  448. ComputeLadderEndpoint( true );
  449. }
  450. // move toward ladder mount point
  451. if ( !IsOnLadder() && (m_pathLadder->m_bottom.z - GetAbsOrigin().z > JumpCrouchHeight ) )
  452. {
  453. Jump();
  454. }
  455. switch( m_pathLadderDismountDir )
  456. {
  457. case RIGHT: StrafeLeft(); break;
  458. case LEFT: StrafeRight(); break;
  459. default: MoveForward(); break;
  460. }
  461. break;
  462. case MOUNT_DESCENDING_LADDER:
  463. // fall check
  464. if (GetFeetZ() <= m_pathLadder->m_bottom.z + HalfHumanHeight)
  465. {
  466. PrintIfWatched( "Fell from ladder.\n" );
  467. m_pathLadderState = MOVE_TO_DESTINATION;
  468. m_path[ m_pathIndex ].area->GetClosestPointOnArea( m_pathLadder->m_bottom, &m_goalPosition );
  469. m_goalPosition += m_pathLadder->GetNormal() * HalfHumanWidth;
  470. PrintIfWatched( "MOVE_TO_DESTINATION\n" );
  471. }
  472. else
  473. {
  474. if (IsOnLadder())
  475. {
  476. m_pathLadderState = DESCEND_LADDER;
  477. PrintIfWatched( "DESCEND_LADDER\n" );
  478. // find actual bottom in case m_pathLadder penetrates the floor
  479. ComputeLadderEndpoint( false );
  480. }
  481. // move toward ladder mount point
  482. MoveForward();
  483. }
  484. break;
  485. case ASCEND_LADDER:
  486. // run, so we can make our dismount jump to the side, if necessary
  487. Run();
  488. // if our destination area requires us to crouch, do it
  489. if (m_path[ m_pathIndex ].area->GetAttributes() & NAV_MESH_CROUCH)
  490. Crouch();
  491. // did we reach the top?
  492. if (GetFeetZ() >= m_pathLadderEnd)
  493. {
  494. // we reached the top - dismount
  495. m_pathLadderState = DISMOUNT_ASCENDING_LADDER;
  496. PrintIfWatched( "DISMOUNT_ASCENDING_LADDER\n" );
  497. if (m_path[ m_pathIndex ].area == m_pathLadder->m_topForwardArea)
  498. m_pathLadderDismountDir = FORWARD;
  499. else if (m_path[ m_pathIndex ].area == m_pathLadder->m_topLeftArea)
  500. m_pathLadderDismountDir = LEFT;
  501. else if (m_path[ m_pathIndex ].area == m_pathLadder->m_topRightArea)
  502. m_pathLadderDismountDir = RIGHT;
  503. m_pathLadderDismountTimestamp = gpGlobals->curtime;
  504. }
  505. else if (!IsOnLadder())
  506. {
  507. // we fall off the ladder, repath
  508. DestroyPath();
  509. return false;
  510. }
  511. // move up ladder
  512. switch( m_pathLadderDismountDir )
  513. {
  514. case RIGHT: StrafeLeft(); break;
  515. case LEFT: StrafeRight(); break;
  516. default: MoveForward(); break;
  517. }
  518. break;
  519. case DESCEND_LADDER:
  520. {
  521. Run();
  522. float destHeight = m_pathLadderEnd;
  523. if ( (m_path[ m_pathIndex ].area->GetAttributes() & NAV_MESH_NO_JUMP) == 0 )
  524. {
  525. destHeight += HalfHumanHeight;
  526. }
  527. if ( !IsOnLadder() || GetFeetZ() <= destHeight )
  528. {
  529. // we reached the bottom, or we fell off - dismount
  530. m_pathLadderState = MOVE_TO_DESTINATION;
  531. m_path[ m_pathIndex ].area->GetClosestPointOnArea( m_pathLadder->m_bottom, &m_goalPosition );
  532. m_goalPosition += m_pathLadder->GetNormal() * HalfHumanWidth;
  533. PrintIfWatched( "MOVE_TO_DESTINATION\n" );
  534. }
  535. // Move down ladder
  536. MoveForward();
  537. break;
  538. }
  539. case DISMOUNT_ASCENDING_LADDER:
  540. {
  541. if (gpGlobals->curtime - m_pathLadderDismountTimestamp >= 0.4f)
  542. {
  543. m_pathLadderState = MOVE_TO_DESTINATION;
  544. m_path[ m_pathIndex ].area->GetClosestPointOnArea( myOrigin, &m_goalPosition );
  545. PrintIfWatched( "MOVE_TO_DESTINATION\n" );
  546. }
  547. // We should already be facing the dismount point
  548. MoveForward();
  549. break;
  550. }
  551. case MOVE_TO_DESTINATION:
  552. if (m_path[ m_pathIndex ].area->Contains( myOrigin ))
  553. {
  554. // successfully traversed ladder and reached destination area
  555. // exit ladder state machine
  556. PrintIfWatched( "Ladder traversed.\n" );
  557. m_pathLadder = NULL;
  558. // incrememnt path index to next step beyond this ladder
  559. SetPathIndex( m_pathIndex+1 );
  560. ClearLookAt();
  561. return false;
  562. }
  563. MoveTowardsPosition( m_goalPosition );
  564. break;
  565. }
  566. if ( ( cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.GetInt() == 10 )
  567. {
  568. DrawPath();
  569. }
  570. return true;
  571. }
  572. //--------------------------------------------------------------------------------------------------------------
  573. /**
  574. * Compute closest point on path to given point
  575. * NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc
  576. */
  577. bool CCSBot::FindClosestPointOnPath( const Vector &worldPos, int startIndex, int endIndex, Vector *close ) const
  578. {
  579. if (!HasPath() || close == NULL)
  580. return false;
  581. Vector along, toWorldPos;
  582. Vector pos;
  583. const Vector *from, *to;
  584. float length;
  585. float closeLength;
  586. float closeDistSq = 9999999999.9;
  587. float distSq;
  588. for( int i=startIndex; i<=endIndex; ++i )
  589. {
  590. from = &m_path[i-1].pos;
  591. to = &m_path[i].pos;
  592. // compute ray along this path segment
  593. along = *to - *from;
  594. // make it a unit vector along the path
  595. length = along.NormalizeInPlace();
  596. // compute vector from start of segment to our point
  597. toWorldPos = worldPos - *from;
  598. // find distance of closest point on ray
  599. closeLength = DotProduct( toWorldPos, along );
  600. // constrain point to be on path segment
  601. if (closeLength <= 0.0f)
  602. pos = *from;
  603. else if (closeLength >= length)
  604. pos = *to;
  605. else
  606. pos = *from + closeLength * along;
  607. distSq = (pos - worldPos).LengthSqr();
  608. // keep the closest point so far
  609. if (distSq < closeDistSq)
  610. {
  611. closeDistSq = distSq;
  612. *close = pos;
  613. }
  614. }
  615. return true;
  616. }
  617. //--------------------------------------------------------------------------------------------------------------
  618. /**
  619. * Return the closest point to our current position on our current path
  620. * If "local" is true, only check the portion of the path surrounding m_pathIndex.
  621. */
  622. int CCSBot::FindOurPositionOnPath( Vector *close, bool local ) const
  623. {
  624. if (!HasPath())
  625. return -1;
  626. Vector along, toFeet;
  627. Vector feet = GetAbsOrigin();
  628. Vector eyes = feet + Vector( 0, 0, HalfHumanHeight ); // in case we're crouching
  629. Vector pos;
  630. const Vector *from, *to;
  631. float length;
  632. float closeLength;
  633. float closeDistSq = 9999999999.9;
  634. int closeIndex = -1;
  635. float distSq;
  636. int start, end;
  637. if (local)
  638. {
  639. start = m_pathIndex - 3;
  640. if (start < 1)
  641. start = 1;
  642. end = m_pathIndex + 3;
  643. if (end > m_pathLength)
  644. end = m_pathLength;
  645. }
  646. else
  647. {
  648. start = 1;
  649. end = m_pathLength;
  650. }
  651. for( int i=start; i<end; ++i )
  652. {
  653. from = &m_path[i-1].pos;
  654. to = &m_path[i].pos;
  655. // compute ray along this path segment
  656. along = *to - *from;
  657. // make it a unit vector along the path
  658. length = along.NormalizeInPlace();
  659. // compute vector from start of segment to our point
  660. toFeet = feet - *from;
  661. // find distance of closest point on ray
  662. closeLength = DotProduct( toFeet, along );
  663. // constrain point to be on path segment
  664. if (closeLength <= 0.0f)
  665. pos = *from;
  666. else if (closeLength >= length)
  667. pos = *to;
  668. else
  669. pos = *from + closeLength * along;
  670. distSq = (pos - feet).LengthSqr();
  671. // keep the closest point so far
  672. if (distSq < closeDistSq)
  673. {
  674. // don't use points we cant see
  675. Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
  676. if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ))
  677. continue;
  678. // don't use points we cant reach
  679. if (!IsStraightLinePathWalkable( pos ))
  680. continue;
  681. closeDistSq = distSq;
  682. if (close)
  683. *close = pos;
  684. closeIndex = i-1;
  685. }
  686. }
  687. return closeIndex;
  688. }
  689. //--------------------------------------------------------------------------------------------------------------
  690. /**
  691. * Test for un-jumpable height change, or unrecoverable fall
  692. */
  693. bool CCSBot::IsStraightLinePathWalkable( const Vector &goal ) const
  694. {
  695. // this is causing hang-up problems when crawling thru ducts/windows that drop off into rooms (they fail the "falling" check)
  696. return true;
  697. const float inc = GenerationStepSize;
  698. Vector feet = GetAbsOrigin();
  699. Vector dir = goal - feet;
  700. float length = dir.NormalizeInPlace();
  701. float lastGround;
  702. //if (!GetSimpleGroundHeight( &pev->origin, &lastGround ))
  703. // return false;
  704. lastGround = feet.z;
  705. float along=0.0f;
  706. Vector pos;
  707. float ground;
  708. bool done = false;
  709. while( !done )
  710. {
  711. along += inc;
  712. if (along > length)
  713. {
  714. along = length;
  715. done = true;
  716. }
  717. // compute step along path
  718. pos = feet + along * dir;
  719. pos.z += HalfHumanHeight;
  720. if (!TheNavMesh->GetSimpleGroundHeight( pos, &ground ))
  721. return false;
  722. // check for falling
  723. if (ground - lastGround < -StepHeight)
  724. return false;
  725. // check for unreachable jump
  726. // use slightly shorter jump limit, to allow for some fudge room
  727. if (ground - lastGround > JumpHeight)
  728. return false;
  729. lastGround = ground;
  730. }
  731. return true;
  732. }
  733. //--------------------------------------------------------------------------------------------------------------
  734. /**
  735. * Compute a point a fixed distance ahead along our path.
  736. * Returns path index just after point.
  737. */
  738. int CCSBot::FindPathPoint( float aheadRange, Vector *point, int *prevIndex )
  739. {
  740. Vector myOrigin = GetCentroid( this );
  741. // find path index just past aheadRange
  742. int afterIndex;
  743. // finds the closest point on local area of path, and returns the path index just prior to it
  744. Vector close;
  745. int startIndex = FindOurPositionOnPath( &close, true );
  746. if (prevIndex)
  747. *prevIndex = startIndex;
  748. if (startIndex <= 0)
  749. {
  750. // went off the end of the path
  751. // or next point in path is unwalkable (ie: jump-down)
  752. // keep same point
  753. return m_pathIndex;
  754. }
  755. // if we are crouching, just follow the path exactly
  756. if (IsCrouching())
  757. {
  758. // we want to move to the immediately next point along the path from where we are now
  759. int index = startIndex+1;
  760. if (index >= m_pathLength)
  761. index = m_pathLength-1;
  762. *point = m_path[ index ].pos;
  763. // if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling
  764. // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
  765. const float closeEpsilon = 20.0f; // 10
  766. while ((*point - close).AsVector2D().IsLengthLessThan( closeEpsilon ))
  767. {
  768. ++index;
  769. if (index >= m_pathLength)
  770. {
  771. index = m_pathLength-1;
  772. break;
  773. }
  774. *point = m_path[ index ].pos;
  775. }
  776. return index;
  777. }
  778. // make sure we use a node a minimum distance ahead of us, to avoid wiggling
  779. while (startIndex < m_pathLength-1)
  780. {
  781. Vector pos = m_path[ startIndex+1 ].pos;
  782. // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
  783. const float closeEpsilon = 20.0f;
  784. if ((pos - close).AsVector2D().IsLengthLessThan( closeEpsilon ))
  785. {
  786. ++startIndex;
  787. }
  788. else
  789. {
  790. break;
  791. }
  792. }
  793. // if we hit a ladder, stop, or jump area, must stop (dont use ladder behind us)
  794. if (startIndex > m_pathIndex && startIndex < m_pathLength &&
  795. (m_path[ startIndex ].ladder || m_path[ startIndex ].area->GetAttributes() & (NAV_MESH_JUMP | NAV_MESH_STOP)))
  796. {
  797. *point = m_path[ startIndex ].pos;
  798. return startIndex;
  799. }
  800. // we need the point just *ahead* of us
  801. ++startIndex;
  802. if (startIndex >= m_pathLength)
  803. startIndex = m_pathLength-1;
  804. // if we hit a ladder, stop, or jump area, must stop
  805. if (startIndex < m_pathLength &&
  806. (m_path[ startIndex ].ladder || m_path[ startIndex ].area->GetAttributes() & (NAV_MESH_JUMP | NAV_MESH_STOP)))
  807. {
  808. *point = m_path[ startIndex ].pos;
  809. return startIndex;
  810. }
  811. // note direction of path segment we are standing on
  812. Vector initDir = m_path[ startIndex ].pos - m_path[ startIndex-1 ].pos;
  813. initDir.NormalizeInPlace();
  814. Vector feet = GetAbsOrigin();
  815. Vector eyes = feet + Vector( 0, 0, HalfHumanHeight );
  816. float rangeSoFar = 0;
  817. // this flag is true if our ahead point is visible
  818. bool visible = true;
  819. Vector prevDir = initDir;
  820. // step along the path until we pass aheadRange
  821. bool isCorner = false;
  822. int i;
  823. for( i=startIndex; i<m_pathLength; ++i )
  824. {
  825. Vector pos = m_path[i].pos;
  826. Vector to = pos - m_path[i-1].pos;
  827. Vector dir = to;
  828. dir.NormalizeInPlace();
  829. // don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc)
  830. if (DotProduct( dir, initDir ) < 0.0f) // -0.25f
  831. {
  832. --i;
  833. break;
  834. }
  835. // if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc
  836. if (DotProduct( dir, prevDir ) < 0.5f)
  837. {
  838. isCorner = true;
  839. --i;
  840. break;
  841. }
  842. prevDir = dir;
  843. // don't use points we cant see
  844. Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
  845. if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ))
  846. {
  847. // presumably, the previous point is visible, so we will interpolate
  848. visible = false;
  849. break;
  850. }
  851. // if we encounter a ladder or jump area, we must stop
  852. if (i < m_pathLength &&
  853. (m_path[ i ].ladder || m_path[ i ].area->GetAttributes() & NAV_MESH_JUMP))
  854. break;
  855. // Check straight-line path from our current position to this position
  856. // Test for un-jumpable height change, or unrecoverable fall
  857. if (!IsStraightLinePathWalkable( pos ))
  858. {
  859. --i;
  860. break;
  861. }
  862. Vector along = (i == startIndex) ? (pos - feet) : (pos - m_path[i-1].pos);
  863. rangeSoFar += along.Length2D();
  864. // stop if we have gone farther than aheadRange
  865. if (rangeSoFar >= aheadRange)
  866. break;
  867. }
  868. if (i < startIndex)
  869. afterIndex = startIndex;
  870. else if (i < m_pathLength)
  871. afterIndex = i;
  872. else
  873. afterIndex = m_pathLength-1;
  874. // compute point on the path at aheadRange
  875. if (afterIndex == 0)
  876. {
  877. *point = m_path[0].pos;
  878. }
  879. else
  880. {
  881. // interpolate point along path segment
  882. const Vector *afterPoint = &m_path[ afterIndex ].pos;
  883. const Vector *beforePoint = &m_path[ afterIndex-1 ].pos;
  884. Vector to = *afterPoint - *beforePoint;
  885. float length = to.Length2D();
  886. float t = 1.0f - ((rangeSoFar - aheadRange) / length);
  887. if (t < 0.0f)
  888. t = 0.0f;
  889. else if (t > 1.0f)
  890. t = 1.0f;
  891. *point = *beforePoint + t * to;
  892. // if afterPoint wasn't visible, slide point backwards towards beforePoint until it is
  893. if (!visible)
  894. {
  895. const float sightStepSize = 25.0f;
  896. float dt = sightStepSize / length;
  897. Vector probe = *point + Vector( 0, 0, HalfHumanHeight );
  898. while( t > 0.0f && !IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ) )
  899. {
  900. t -= dt;
  901. *point = *beforePoint + t * to;
  902. }
  903. if (t <= 0.0f)
  904. *point = *beforePoint;
  905. }
  906. }
  907. // if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle
  908. if (!isCorner)
  909. {
  910. const float epsilon = 50.0f;
  911. Vector2D toPoint;
  912. toPoint.x = point->x - myOrigin.x;
  913. toPoint.y = point->y - myOrigin.y;
  914. if (DotProduct2D( toPoint, initDir.AsVector2D() ) < 0.0f || toPoint.IsLengthLessThan( epsilon ))
  915. {
  916. int i;
  917. for( i=startIndex; i<m_pathLength; ++i )
  918. {
  919. toPoint.x = m_path[i].pos.x - myOrigin.x;
  920. toPoint.y = m_path[i].pos.y - myOrigin.y;
  921. if (m_path[i].ladder || m_path[i].area->GetAttributes() & NAV_MESH_JUMP || toPoint.IsLengthGreaterThan( epsilon ))
  922. {
  923. *point = m_path[i].pos;
  924. startIndex = i;
  925. break;
  926. }
  927. }
  928. if (i == m_pathLength)
  929. {
  930. *point = GetPathEndpoint();
  931. startIndex = m_pathLength-1;
  932. }
  933. }
  934. }
  935. // m_pathIndex should always be the next point on the path, even if we're not moving directly towards it
  936. return startIndex;
  937. }
  938. //--------------------------------------------------------------------------------------------------------------
  939. /**
  940. * Set the current index along the path
  941. */
  942. void CCSBot::SetPathIndex( int newIndex )
  943. {
  944. m_pathIndex = MIN( newIndex, m_pathLength-1 );
  945. m_areaEnteredTimestamp = gpGlobals->curtime;
  946. if (m_path[ m_pathIndex ].ladder)
  947. {
  948. SetupLadderMovement();
  949. }
  950. else
  951. {
  952. // get our "encounter spots" for this leg of the path
  953. if (m_pathIndex < m_pathLength && m_pathIndex >= 2)
  954. m_spotEncounter = m_path[ m_pathIndex-1 ].area->GetSpotEncounter( m_path[ m_pathIndex-2 ].area, m_path[ m_pathIndex ].area );
  955. else
  956. m_spotEncounter = NULL;
  957. m_pathLadder = NULL;
  958. }
  959. }
  960. //--------------------------------------------------------------------------------------------------------------
  961. /**
  962. * Return true if nearing a jump in the path
  963. */
  964. bool CCSBot::IsNearJump( void ) const
  965. {
  966. if (m_pathIndex == 0 || m_pathIndex >= m_pathLength)
  967. return false;
  968. for( int i=m_pathIndex-1; i<m_pathIndex; ++i )
  969. {
  970. if (m_path[ i ].area->GetAttributes() & NAV_MESH_JUMP)
  971. {
  972. float dz = m_path[ i+1 ].pos.z - m_path[ i ].pos.z;
  973. if (dz > 0.0f)
  974. return true;
  975. }
  976. }
  977. return false;
  978. }
  979. //--------------------------------------------------------------------------------------------------------------
  980. /**
  981. * Return approximately how much damage will will take from the given fall height
  982. */
  983. float CCSBot::GetApproximateFallDamage( float height ) const
  984. {
  985. // empirically discovered height values
  986. const float slope = 0.2178f;
  987. const float intercept = 26.0f;
  988. float damage = slope * height - intercept;
  989. if (damage < 0.0f)
  990. return 0.0f;
  991. return damage;
  992. }
  993. //--------------------------------------------------------------------------------------------------------------
  994. /**
  995. * Return true if a friend is between us and the given position
  996. */
  997. bool CCSBot::IsFriendInTheWay( const Vector &goalPos )
  998. {
  999. // do this check less often to ease CPU burden
  1000. if (!m_avoidFriendTimer.IsElapsed())
  1001. {
  1002. return m_isFriendInTheWay;
  1003. }
  1004. const float avoidFriendInterval = 0.5f;
  1005. m_avoidFriendTimer.Start( avoidFriendInterval );
  1006. // compute ray along intended path
  1007. Vector myOrigin = GetCentroid( this );
  1008. Vector moveDir = goalPos - myOrigin;
  1009. // make it a unit vector
  1010. float length = moveDir.NormalizeInPlace();
  1011. m_isFriendInTheWay = false;
  1012. // check if any friends are overlapping this linear path
  1013. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  1014. {
  1015. CCSPlayer *player = static_cast<CCSPlayer *>( UTIL_PlayerByIndex( i ) );
  1016. if (player == NULL)
  1017. continue;
  1018. if (!player->IsAlive())
  1019. continue;
  1020. if (!player->InSameTeam( this ))
  1021. continue;
  1022. if (player->entindex() == entindex())
  1023. continue;
  1024. // compute vector from us to our friend
  1025. Vector toFriend = player->GetAbsOrigin() - GetAbsOrigin();
  1026. // check if friend is in our "personal space"
  1027. const float personalSpace = 100.0f;
  1028. if (toFriend.IsLengthGreaterThan( personalSpace ))
  1029. continue;
  1030. // find distance of friend along our movement path
  1031. float friendDistAlong = DotProduct( toFriend, moveDir );
  1032. // if friend is behind us, ignore him
  1033. if (friendDistAlong <= 0.0f)
  1034. continue;
  1035. // constrain point to be on path segment
  1036. Vector pos;
  1037. if (friendDistAlong >= length)
  1038. pos = goalPos;
  1039. else
  1040. pos = myOrigin + friendDistAlong * moveDir;
  1041. // check if friend overlaps our intended line of movement
  1042. const float friendRadius = 30.0f;
  1043. if ((pos - GetCentroid( player )).IsLengthLessThan( friendRadius ))
  1044. {
  1045. // friend is in our personal space and overlaps our intended line of movement
  1046. m_isFriendInTheWay = true;
  1047. break;
  1048. }
  1049. }
  1050. return m_isFriendInTheWay;
  1051. }
  1052. //--------------------------------------------------------------------------------------------------------------
  1053. /**
  1054. * Do reflex avoidance movements if our "feelers" are touched
  1055. */
  1056. void CCSBot::FeelerReflexAdjustment( Vector *goalPosition )
  1057. {
  1058. // if we are in a "precise" area, do not do feeler adjustments
  1059. if (m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_PRECISE)
  1060. return;
  1061. Vector dir( BotCOS( m_forwardAngle ), BotSIN( m_forwardAngle ), 0.0f );
  1062. Vector lat( -dir.y, dir.x, 0.0f );
  1063. const float feelerOffset = (IsCrouching()) ? 15.0f : 20.0f;
  1064. const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747)
  1065. const float feelerLengthWalk = 30.0f;
  1066. const float feelerHeight = StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it
  1067. float feelerLength = (IsRunning()) ? feelerLengthRun : feelerLengthWalk;
  1068. feelerLength = (IsCrouching()) ? 20.0f : feelerLength;
  1069. //
  1070. // Feelers must follow floor slope
  1071. //
  1072. float ground;
  1073. Vector normal;
  1074. Vector eye = EyePosition();
  1075. if (GetSimpleGroundHeightWithFloor( eye, &ground, &normal ) == false)
  1076. return;
  1077. // get forward vector along floor
  1078. dir = CrossProduct( lat, normal );
  1079. // correct the sideways vector
  1080. lat = CrossProduct( dir, normal );
  1081. Vector feet = GetAbsOrigin();
  1082. feet.z += feelerHeight;
  1083. Vector from = feet + feelerOffset * lat;
  1084. Vector to = from + feelerLength * dir;
  1085. bool leftClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
  1086. // avoid ledges, too
  1087. // use 'from' so it doesn't interfere with legitimate gap jumping (its at our feet)
  1088. /// @todo Rethink this - it causes lots of wiggling when bots jump down from vents, etc
  1089. /*
  1090. float ground;
  1091. if (GetSimpleGroundHeightWithFloor( &from, &ground ))
  1092. {
  1093. if (GetFeetZ() - ground > JumpHeight)
  1094. leftClear = false;
  1095. }
  1096. */
  1097. if ( ( cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.GetInt() == 10 )
  1098. {
  1099. if (leftClear)
  1100. UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 );
  1101. else
  1102. UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 );
  1103. }
  1104. from = feet - feelerOffset * lat;
  1105. to = from + feelerLength * dir;
  1106. bool rightClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
  1107. /*
  1108. // avoid ledges, too
  1109. if (GetSimpleGroundHeightWithFloor( &from, &ground ))
  1110. {
  1111. if (GetFeetZ() - ground > JumpHeight)
  1112. rightClear = false;
  1113. }
  1114. */
  1115. if ( ( cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.GetInt() == 10 )
  1116. {
  1117. if (rightClear)
  1118. UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 );
  1119. else
  1120. UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 );
  1121. }
  1122. const float avoidRange = (IsCrouching()) ? 150.0f : 300.0f; // 50, 300
  1123. if (!rightClear)
  1124. {
  1125. if (leftClear)
  1126. {
  1127. // right hit, left clear - veer left
  1128. *goalPosition = *goalPosition + avoidRange * lat;
  1129. }
  1130. }
  1131. else if (!leftClear)
  1132. {
  1133. // right clear, left hit - veer right
  1134. *goalPosition = *goalPosition - avoidRange * lat;
  1135. }
  1136. }
  1137. //--------------------------------------------------------------------------------------------------------------
  1138. /**
  1139. * Allows the current nav area to make us run/walk without messing with our state
  1140. */
  1141. bool CCSBot::IsRunning( void ) const
  1142. {
  1143. // if we've forced running, go with it
  1144. if ( !m_mustRunTimer.IsElapsed() )
  1145. {
  1146. return BaseClass::IsRunning();
  1147. }
  1148. if ( m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_RUN )
  1149. {
  1150. return true;
  1151. }
  1152. if ( m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_WALK )
  1153. {
  1154. return false;
  1155. }
  1156. return BaseClass::IsRunning();
  1157. }
  1158. //--------------------------------------------------------------------------------------------------------------
  1159. /**
  1160. * Move along the path. Return false if end of path reached.
  1161. */
  1162. CCSBot::PathResult CCSBot::UpdatePathMovement( bool allowSpeedChange )
  1163. {
  1164. VPROF_BUDGET( "CCSBot::UpdatePathMovement", VPROF_BUDGETGROUP_NPCS );
  1165. if (m_pathLength == 0)
  1166. return PATH_FAILURE;
  1167. if (cv_bot_walk.GetBool())
  1168. Walk();
  1169. //
  1170. // If we are navigating a ladder, it overrides all other path movement until complete
  1171. //
  1172. if (UpdateLadderMovement())
  1173. return PROGRESSING;
  1174. // ladder failure can destroy the path
  1175. if (m_pathLength == 0)
  1176. return PATH_FAILURE;
  1177. // we are not supposed to be on a ladder - if we are, jump off
  1178. if (IsOnLadder())
  1179. Jump( MUST_JUMP );
  1180. assert( m_pathIndex < m_pathLength );
  1181. //
  1182. // Stop path attribute
  1183. //
  1184. if (!IsUsingLadder())
  1185. {
  1186. // if the m_isStopping flag is set, clear our movement
  1187. // if the m_isStopping flag is set and movement is stopped, clear m_isStopping
  1188. if ( m_lastKnownArea && m_isStopping )
  1189. {
  1190. ResetStuckMonitor();
  1191. ClearMovement();
  1192. if ( GetAbsVelocity().LengthSqr() < 0.1f )
  1193. {
  1194. m_isStopping = false;
  1195. }
  1196. else
  1197. {
  1198. return PROGRESSING;
  1199. }
  1200. }
  1201. } // end stop logic
  1202. //
  1203. // Check if reached the end of the path
  1204. //
  1205. bool nearEndOfPath = false;
  1206. if (m_pathIndex >= m_pathLength-1)
  1207. {
  1208. Vector toEnd = GetPathEndpoint() - GetAbsOrigin();
  1209. Vector d = toEnd; // can't use 2D because path end may be below us (jump down)
  1210. const float walkRange = 200.0f;
  1211. // walk as we get close to the goal position to ensure we hit it
  1212. if (d.IsLengthLessThan( walkRange ))
  1213. {
  1214. // don't walk if crouching - too slow
  1215. if (allowSpeedChange && !IsCrouching())
  1216. Walk();
  1217. // note if we are near the end of the path
  1218. const float nearEndRange = 50.0f;
  1219. if (d.IsLengthLessThan( nearEndRange ))
  1220. nearEndOfPath = true;
  1221. const float closeEpsilon = 20.0f;
  1222. if (d.IsLengthLessThan( closeEpsilon ))
  1223. {
  1224. // reached goal position - path complete
  1225. DestroyPath();
  1226. /// @todo We should push and pop walk state here, in case we want to continue walking after reaching goal
  1227. if (allowSpeedChange)
  1228. Run();
  1229. return END_OF_PATH;
  1230. }
  1231. }
  1232. }
  1233. //
  1234. // To keep us moving smoothly, we will move towards
  1235. // a point farther ahead of us down our path.
  1236. //
  1237. int prevIndex = 0; // closest index on path just prior to where we are now
  1238. const float aheadRange = 300.0f;
  1239. int newIndex = FindPathPoint( aheadRange, &m_goalPosition, &prevIndex );
  1240. // BOTPORT: Why is prevIndex sometimes -1?
  1241. if (prevIndex < 0)
  1242. prevIndex = 0;
  1243. // if goal position is near to us, we must be about to go around a corner - so look ahead!
  1244. Vector myOrigin = GetCentroid( this );
  1245. const float nearCornerRange = 100.0f;
  1246. if (m_pathIndex < m_pathLength-1 && (m_goalPosition - myOrigin).IsLengthLessThan( nearCornerRange ))
  1247. {
  1248. if (!IsLookingAtSpot( PRIORITY_HIGH ))
  1249. {
  1250. ClearLookAt();
  1251. InhibitLookAround( 0.5f );
  1252. }
  1253. }
  1254. // if we moved to a new node on the path, setup movement
  1255. if (newIndex > m_pathIndex)
  1256. {
  1257. SetPathIndex( newIndex );
  1258. }
  1259. //
  1260. // Crouching
  1261. //
  1262. if (!IsUsingLadder())
  1263. {
  1264. // if we are approaching a crouch area, crouch
  1265. // if there are no crouch areas coming up, stand
  1266. const float crouchRange = 50.0f;
  1267. bool didCrouch = false;
  1268. for( int i=prevIndex; i<m_pathLength; ++i )
  1269. {
  1270. const CNavArea *to = m_path[i].area;
  1271. // if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump
  1272. // unless we are already higher than the jump area - we must've jumped already but not moved into next area
  1273. if (to->GetAttributes() & NAV_MESH_JUMP && to->GetCenter().z > GetFeetZ())
  1274. break;
  1275. Vector close;
  1276. to->GetClosestPointOnArea( myOrigin, &close );
  1277. if ((close - myOrigin).AsVector2D().IsLengthGreaterThan( crouchRange ))
  1278. break;
  1279. if (to->GetAttributes() & NAV_MESH_CROUCH)
  1280. {
  1281. Crouch();
  1282. didCrouch = true;
  1283. ResetStuckMonitor();
  1284. break;
  1285. }
  1286. }
  1287. if (!didCrouch && !IsJumping())
  1288. {
  1289. // no crouch areas coming up
  1290. StandUp();
  1291. }
  1292. } // end crouching logic
  1293. // compute our forward facing angle
  1294. m_forwardAngle = UTIL_VecToYaw( m_goalPosition - myOrigin );
  1295. //
  1296. // Look farther down the path to "lead" our view around corners
  1297. //
  1298. Vector toGoal;
  1299. bool isWaitingForLadder = false;
  1300. // if we are crouching, look towards where we are moving to negotiate tight corners
  1301. if (IsCrouching())
  1302. {
  1303. m_lookAheadAngle = m_forwardAngle;
  1304. }
  1305. else
  1306. {
  1307. if (m_pathIndex == 0)
  1308. {
  1309. toGoal = m_path[1].pos;
  1310. }
  1311. else if (m_pathIndex < m_pathLength)
  1312. {
  1313. toGoal = m_path[ m_pathIndex ].pos - myOrigin;
  1314. // actually aim our view farther down the path
  1315. const float lookAheadRange = 500.0f;
  1316. if (!m_path[ m_pathIndex ].ladder &&
  1317. !IsNearJump() &&
  1318. toGoal.AsVector2D().IsLengthLessThan( lookAheadRange ))
  1319. {
  1320. float along = toGoal.Length2D();
  1321. int i;
  1322. for( i=m_pathIndex+1; i<m_pathLength; ++i )
  1323. {
  1324. Vector delta = m_path[i].pos - m_path[i-1].pos;
  1325. float segmentLength = delta.Length2D();
  1326. if (along + segmentLength >= lookAheadRange)
  1327. {
  1328. // interpolate between points to keep look ahead point at fixed distance
  1329. float t = (lookAheadRange - along) / (segmentLength + along);
  1330. Vector target;
  1331. if (t <= 0.0f)
  1332. target = m_path[i-1].pos;
  1333. else if (t >= 1.0f)
  1334. target = m_path[i].pos;
  1335. else
  1336. target = m_path[i-1].pos + t * delta;
  1337. toGoal = target - myOrigin;
  1338. break;
  1339. }
  1340. // if we are coming up to a ladder or a jump, look at it
  1341. if (m_path[i].ladder ||
  1342. (m_path[i].area->GetAttributes() & NAV_MESH_JUMP) ||
  1343. (m_path[i].area->GetAttributes() & NAV_MESH_PRECISE) ||
  1344. (m_path[i].area->GetAttributes() & NAV_MESH_STOP))
  1345. {
  1346. toGoal = m_path[i].pos - myOrigin;
  1347. // if anyone is on the ladder, wait
  1348. if (m_path[i].ladder && m_path[i].ladder->IsInUse( this ))
  1349. {
  1350. isWaitingForLadder = true;
  1351. ResetStuckMonitor();
  1352. // if we are too close to the ladder, back off a bit
  1353. const float tooCloseRange = 100.0f;
  1354. Vector2D delta( m_path[i].ladder->m_top.x - myOrigin.x,
  1355. m_path[i].ladder->m_top.y - myOrigin.y );
  1356. if (delta.IsLengthLessThan( tooCloseRange ))
  1357. {
  1358. MoveAwayFromPosition( m_path[i].ladder->m_top );
  1359. }
  1360. }
  1361. break;
  1362. }
  1363. along += segmentLength;
  1364. }
  1365. if (i == m_pathLength)
  1366. toGoal = GetPathEndpoint() - myOrigin;
  1367. }
  1368. }
  1369. else
  1370. {
  1371. toGoal = GetPathEndpoint() - myOrigin;
  1372. }
  1373. m_lookAheadAngle = UTIL_VecToYaw( toGoal );
  1374. }
  1375. // initialize "adjusted" goal to current goal
  1376. Vector adjustedGoal = m_goalPosition;
  1377. //
  1378. // Use short "feelers" to veer away from close-range obstacles
  1379. // Feelers come from our ankles, just above StepHeight, so we avoid short walls, too
  1380. // Don't use feelers if very near the end of the path, or about to jump
  1381. //
  1382. /// @todo Consider having feelers at several heights to deal with overhangs, etc.
  1383. if (!nearEndOfPath && !IsNearJump() && !IsJumping())
  1384. {
  1385. FeelerReflexAdjustment( &adjustedGoal );
  1386. }
  1387. // draw debug visualization
  1388. if ( ( cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.GetInt() == 10 )
  1389. {
  1390. DrawPath();
  1391. const Vector *pos = &m_path[ m_pathIndex ].pos;
  1392. UTIL_DrawBeamPoints( *pos, *pos + Vector( 0, 0, 50 ), 1, 255, 255, 0 );
  1393. UTIL_DrawBeamPoints( adjustedGoal, adjustedGoal + Vector( 0, 0, 50 ), 1, 255, 0, 255 );
  1394. UTIL_DrawBeamPoints( myOrigin, adjustedGoal + Vector( 0, 0, 50 ), 1, 255, 0, 255 );
  1395. }
  1396. // dont use adjustedGoal, as it can vary wildly from the feeler adjustment
  1397. if (!IsAttacking() && IsFriendInTheWay( m_goalPosition ))
  1398. {
  1399. if (!m_isWaitingBehindFriend)
  1400. {
  1401. m_isWaitingBehindFriend = true;
  1402. const float politeDuration = 5.0f - 3.0f * GetProfile()->GetAggression();
  1403. m_politeTimer.Start( politeDuration );
  1404. }
  1405. else if (m_politeTimer.IsElapsed())
  1406. {
  1407. // we have run out of patience
  1408. m_isWaitingBehindFriend = false;
  1409. ResetStuckMonitor();
  1410. // repath to avoid clump of friends in the way
  1411. DestroyPath();
  1412. }
  1413. }
  1414. else if (m_isWaitingBehindFriend)
  1415. {
  1416. // we're done waiting for our friend to move
  1417. m_isWaitingBehindFriend = false;
  1418. ResetStuckMonitor();
  1419. }
  1420. //
  1421. // Move along our path if there are no friends blocking our way,
  1422. // or we have run out of patience
  1423. //
  1424. if (!isWaitingForLadder && (!m_isWaitingBehindFriend || m_politeTimer.IsElapsed()))
  1425. {
  1426. //
  1427. // Move along path
  1428. //
  1429. MoveTowardsPosition( adjustedGoal );
  1430. //
  1431. // Stuck check
  1432. //
  1433. if (m_isStuck && !IsJumping())
  1434. {
  1435. Wiggle();
  1436. }
  1437. }
  1438. // if our goal is high above us, we must have fallen
  1439. bool didFall = false;
  1440. if (m_goalPosition.z - GetFeetZ() > JumpCrouchHeight)
  1441. {
  1442. const float closeRange = 75.0f;
  1443. Vector2D to( myOrigin.x - m_goalPosition.x, myOrigin.y - m_goalPosition.y );
  1444. if (to.IsLengthLessThan( closeRange ))
  1445. {
  1446. // we can't reach the goal position
  1447. // check if we can reach the next node, in case this was a "jump down" situation
  1448. if (m_pathIndex < m_pathLength-1)
  1449. {
  1450. if (m_path[ m_pathIndex+1 ].pos.z - GetFeetZ() > JumpCrouchHeight)
  1451. {
  1452. // the next node is too high, too - we really did fall of the path
  1453. didFall = true;
  1454. for ( int i=m_pathIndex; i<=m_pathIndex+1; ++i )
  1455. {
  1456. if ( m_path[i].how == GO_LADDER_UP )
  1457. {
  1458. // if we're going up a ladder, and we're within reach of the ladder bottom, we haven't fallen
  1459. if ( m_path[i].pos.z - GetFeetZ() <= JumpCrouchHeight )
  1460. {
  1461. didFall = false;
  1462. break;
  1463. }
  1464. }
  1465. }
  1466. }
  1467. }
  1468. else
  1469. {
  1470. // fell trying to get to the last node in the path
  1471. didFall = true;
  1472. }
  1473. }
  1474. }
  1475. //
  1476. // This timeout check is needed if the bot somehow slips way off
  1477. // of its path and cannot progress, but also moves around
  1478. // enough that it never becomes "stuck"
  1479. //
  1480. const float giveUpDuration = 4.0f;
  1481. if (didFall || gpGlobals->curtime - m_areaEnteredTimestamp > giveUpDuration)
  1482. {
  1483. if (didFall)
  1484. {
  1485. PrintIfWatched( "I fell off!\n" );
  1486. if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
  1487. {
  1488. CBasePlayer *localPlayer = UTIL_GetListenServerHost();
  1489. CSingleUserRecipientFilter filter( localPlayer );
  1490. EmitSound( filter, localPlayer->entindex(), "Bot.FellOff" );
  1491. }
  1492. }
  1493. // if we havent made any progress in a long time, give up
  1494. if (m_pathIndex < m_pathLength-1)
  1495. {
  1496. PrintIfWatched( "Giving up trying to get to area #%d\n", m_path[ m_pathIndex ].area->GetID() );
  1497. }
  1498. else
  1499. {
  1500. PrintIfWatched( "Giving up trying to get to end of path\n" );
  1501. }
  1502. Run();
  1503. StandUp();
  1504. DestroyPath();
  1505. ClearLookAt();
  1506. // See if we should be on a different nav area
  1507. CNavArea *area = TheNavMesh->GetNearestNavArea( GetAbsOrigin(), false, 500.0f, true );
  1508. if (area && area != m_lastNavArea)
  1509. {
  1510. if (m_lastNavArea)
  1511. {
  1512. m_lastNavArea->DecrementPlayerCount( GetTeamNumber(), entindex() );
  1513. }
  1514. area->IncrementPlayerCount( GetTeamNumber(), entindex() );
  1515. m_lastNavArea = area;
  1516. if ( area->GetPlace() != UNDEFINED_PLACE )
  1517. {
  1518. const char *placeName = TheNavMesh->PlaceToName( area->GetPlace() );
  1519. if ( placeName && *placeName )
  1520. {
  1521. Q_strncpy( m_szLastPlaceName.GetForModify(), placeName, MAX_PLACE_NAME_LENGTH );
  1522. }
  1523. }
  1524. // generate event
  1525. //KeyValues *event = new KeyValues( "player_entered_area" );
  1526. //event->SetInt( "userid", GetUserID() );
  1527. //event->SetInt( "areaid", area->GetID() );
  1528. //gameeventmanager->FireEvent( event );
  1529. }
  1530. return PATH_FAILURE;
  1531. }
  1532. return PROGRESSING;
  1533. }
  1534. //--------------------------------------------------------------------------------------------------------------
  1535. /**
  1536. * Build trivial path to goal, assuming we are already in the same area
  1537. */
  1538. void CCSBot::BuildTrivialPath( const Vector &goal )
  1539. {
  1540. Vector myOrigin = GetCentroid( this );
  1541. m_pathIndex = 1;
  1542. m_pathLength = 2;
  1543. m_path[0].area = m_lastKnownArea;
  1544. m_path[0].pos = myOrigin;
  1545. m_path[0].pos.z = m_lastKnownArea->GetZ( myOrigin );
  1546. m_path[0].ladder = NULL;
  1547. m_path[0].how = NUM_TRAVERSE_TYPES;
  1548. m_path[1].area = m_lastKnownArea;
  1549. m_path[1].pos = goal;
  1550. m_path[1].pos.z = m_lastKnownArea->GetZ( goal );
  1551. m_path[1].ladder = NULL;
  1552. m_path[1].how = NUM_TRAVERSE_TYPES;
  1553. m_areaEnteredTimestamp = gpGlobals->curtime;
  1554. m_spotEncounter = NULL;
  1555. m_pathLadder = NULL;
  1556. m_goalPosition = goal;
  1557. }
  1558. //--------------------------------------------------------------------------------------------------------------
  1559. /**
  1560. * Compute shortest path to goal position via A* algorithm
  1561. * If 'goalArea' is NULL, path will get as close as it can.
  1562. */
  1563. bool CCSBot::ComputePath( const Vector &goal, RouteType route )
  1564. {
  1565. VPROF_BUDGET( "CCSBot::ComputePath", VPROF_BUDGETGROUP_NPCS );
  1566. //
  1567. // Throttle re-pathing
  1568. //
  1569. if (!m_repathTimer.IsElapsed())
  1570. return false;
  1571. // randomize to distribute CPU load
  1572. m_repathTimer.Start( RandomFloat( 0.4f, 0.6f ) );
  1573. DestroyPath();
  1574. m_pathLadder = NULL;
  1575. CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal );
  1576. CNavArea *startArea = m_lastKnownArea;
  1577. if (startArea == NULL)
  1578. return false;
  1579. // if we fell off a ledge onto an area off the mesh, we will path from the
  1580. // ledge above our heads, resulting in a path we can't follow.
  1581. Vector close;
  1582. startArea->GetClosestPointOnArea( EyePosition(), &close );
  1583. if (close.z - GetAbsOrigin().z > JumpCrouchHeight)
  1584. {
  1585. // we can't reach our last known area - find nearest area to us
  1586. PrintIfWatched( "Last known area is above my head - resetting to nearest area.\n" );
  1587. m_lastKnownArea = (CCSNavArea*)TheNavMesh->GetNearestNavArea( GetAbsOrigin(), false, 500.0f, true );
  1588. if (m_lastKnownArea == NULL)
  1589. {
  1590. return false;
  1591. }
  1592. startArea = m_lastKnownArea;
  1593. }
  1594. // note final specific position
  1595. Vector pathEndPosition = goal;
  1596. // make sure path end position is on the ground
  1597. if (goalArea)
  1598. pathEndPosition.z = goalArea->GetZ( pathEndPosition );
  1599. else
  1600. TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
  1601. // if we are already in the goal area, build trivial path
  1602. if (startArea == goalArea)
  1603. {
  1604. BuildTrivialPath( pathEndPosition );
  1605. return true;
  1606. }
  1607. //
  1608. // Compute shortest path to goal
  1609. //
  1610. CNavArea *closestArea = NULL;
  1611. PathCost cost( this, route );
  1612. bool pathToGoalExists = NavAreaBuildPath( startArea, goalArea, &goal, cost, &closestArea );
  1613. CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea;
  1614. //
  1615. // Build path by following parent links
  1616. //
  1617. // get count
  1618. int count = 0;
  1619. CNavArea *area;
  1620. for( area = effectiveGoalArea; area; area = area->GetParent() )
  1621. ++count;
  1622. // save room for endpoint
  1623. if (count > MAX_PATH_LENGTH-1)
  1624. count = MAX_PATH_LENGTH-1;
  1625. if (count == 0)
  1626. return false;
  1627. if (count == 1)
  1628. {
  1629. BuildTrivialPath( pathEndPosition );
  1630. return true;
  1631. }
  1632. // build path
  1633. m_pathLength = count;
  1634. for( area = effectiveGoalArea; count && area; area = area->GetParent() )
  1635. {
  1636. --count;
  1637. m_path[ count ].area = area;
  1638. m_path[ count ].how = area->GetParentHow();
  1639. }
  1640. // compute path positions
  1641. if (ComputePathPositions() == false)
  1642. {
  1643. PrintIfWatched( "Error building path\n" );
  1644. DestroyPath();
  1645. return false;
  1646. }
  1647. // append path end position
  1648. m_path[ m_pathLength ].area = effectiveGoalArea;
  1649. m_path[ m_pathLength ].pos = pathEndPosition;
  1650. m_path[ m_pathLength ].ladder = NULL;
  1651. m_path[ m_pathLength ].how = NUM_TRAVERSE_TYPES;
  1652. ++m_pathLength;
  1653. // do movement setup
  1654. m_pathIndex = 1;
  1655. m_areaEnteredTimestamp = gpGlobals->curtime;
  1656. m_spotEncounter = NULL;
  1657. m_goalPosition = m_path[1].pos;
  1658. if (m_path[1].ladder)
  1659. SetupLadderMovement();
  1660. else
  1661. m_pathLadder = NULL;
  1662. // find initial encounter area along this path, if we are in the early part of the round
  1663. if (IsSafe())
  1664. {
  1665. int myTeam = GetTeamNumber();
  1666. int enemyTeam = OtherTeam( myTeam );
  1667. int i;
  1668. for( i=0; i<m_pathLength; ++i )
  1669. {
  1670. if (m_path[i].area->GetEarliestOccupyTime( myTeam ) > m_path[i].area->GetEarliestOccupyTime( enemyTeam ))
  1671. {
  1672. break;
  1673. }
  1674. }
  1675. if (i < m_pathLength)
  1676. {
  1677. SetInitialEncounterArea( m_path[i].area );
  1678. }
  1679. else
  1680. {
  1681. SetInitialEncounterArea( NULL );
  1682. }
  1683. }
  1684. return true;
  1685. }
  1686. //--------------------------------------------------------------------------------------------------------------
  1687. /**
  1688. * Return estimated distance left to travel along path
  1689. */
  1690. float CCSBot::GetPathDistanceRemaining( void ) const
  1691. {
  1692. if (!HasPath())
  1693. return -1.0f;
  1694. int idx = (m_pathIndex < m_pathLength) ? m_pathIndex : m_pathLength-1;
  1695. float dist = 0.0f;
  1696. Vector prevCenter = m_path[m_pathIndex].area->GetCenter();
  1697. for( int i=idx+1; i<m_pathLength; ++i )
  1698. {
  1699. dist += (m_path[i].area->GetCenter() - prevCenter).Length();
  1700. prevCenter = m_path[i].area->GetCenter();
  1701. }
  1702. return dist;
  1703. }
  1704. //--------------------------------------------------------------------------------------------------------------
  1705. /**
  1706. * Draw a portion of our current path for debugging.
  1707. */
  1708. void CCSBot::DrawPath( void )
  1709. {
  1710. if (!HasPath())
  1711. return;
  1712. for( int i=1; i<m_pathLength; ++i )
  1713. {
  1714. UTIL_DrawBeamPoints( m_path[i-1].pos, m_path[i].pos, 2, 255, 75, 0 );
  1715. }
  1716. Vector close;
  1717. if (FindOurPositionOnPath( &close, true ) >= 0)
  1718. {
  1719. UTIL_DrawBeamPoints( close + Vector( 0, 0, 25 ), close, 1, 0, 255, 0 );
  1720. UTIL_DrawBeamPoints( close + Vector( 25, 0, 0 ), close + Vector( -25, 0, 0 ), 1, 0, 255, 0 );
  1721. UTIL_DrawBeamPoints( close + Vector( 0, 25, 0 ), close + Vector( 0, -25, 0 ), 1, 0, 255, 0 );
  1722. }
  1723. }