Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2097 lines
54 KiB

  1. //========= Copyright � 1996-2005, 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, stair, 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 | NAV_MESH_STAIRS)))
  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 | NAV_MESH_STAIRS)))
  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. // if path crosses damaging areas (ie: fire), stop and wait for it to go away
  830. if ( GetTimeSinceBurnedByFlames() > 1.0f && m_path[i].area->IsDamaging() && rangeSoFar < 100.0f )
  831. {
  832. Wait( RandomFloat( 0.5f, 1.5f ) );
  833. --i;
  834. break;
  835. }
  836. // don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc)
  837. if (DotProduct( dir, initDir ) < 0.0f) // -0.25f
  838. {
  839. --i;
  840. break;
  841. }
  842. // if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc
  843. if (DotProduct( dir, prevDir ) < 0.5f)
  844. {
  845. isCorner = true;
  846. --i;
  847. break;
  848. }
  849. prevDir = dir;
  850. // don't use points we cant see
  851. Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
  852. if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ))
  853. {
  854. // presumably, the previous point is visible, so we will interpolate
  855. visible = false;
  856. break;
  857. }
  858. // if we encounter a ladder, stairs, or jump area, we must stop
  859. if (i < m_pathLength &&
  860. (m_path[ i ].ladder || m_path[ i ].area->GetAttributes() & (NAV_MESH_JUMP | NAV_MESH_STOP | NAV_MESH_STAIRS)))
  861. break;
  862. // Check straight-line path from our current position to this position
  863. // Test for un-jumpable height change, or unrecoverable fall
  864. if (!IsStraightLinePathWalkable( pos ))
  865. {
  866. --i;
  867. break;
  868. }
  869. Vector along = (i == startIndex) ? (pos - feet) : (pos - m_path[i-1].pos);
  870. rangeSoFar += along.Length2D();
  871. // stop if we have gone farther than aheadRange
  872. if (rangeSoFar >= aheadRange)
  873. break;
  874. }
  875. if (i < startIndex)
  876. afterIndex = startIndex;
  877. else if (i < m_pathLength)
  878. afterIndex = i;
  879. else
  880. afterIndex = m_pathLength-1;
  881. // compute point on the path at aheadRange
  882. if (afterIndex == 0)
  883. {
  884. *point = m_path[0].pos;
  885. }
  886. else
  887. {
  888. // interpolate point along path segment
  889. const Vector *afterPoint = &m_path[ afterIndex ].pos;
  890. const Vector *beforePoint = &m_path[ afterIndex-1 ].pos;
  891. Vector to = *afterPoint - *beforePoint;
  892. float length = to.Length2D();
  893. float t = 1.0f - ((rangeSoFar - aheadRange) / length);
  894. if (t < 0.0f)
  895. t = 0.0f;
  896. else if (t > 1.0f)
  897. t = 1.0f;
  898. *point = *beforePoint + t * to;
  899. // if afterPoint wasn't visible, slide point backwards towards beforePoint until it is
  900. if (!visible)
  901. {
  902. const float sightStepSize = 25.0f;
  903. float dt = sightStepSize / length;
  904. Vector probe = *point + Vector( 0, 0, HalfHumanHeight );
  905. while( t > 0.0f && !IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ) )
  906. {
  907. t -= dt;
  908. *point = *beforePoint + t * to;
  909. }
  910. if (t <= 0.0f)
  911. *point = *beforePoint;
  912. }
  913. }
  914. // if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle
  915. if (!isCorner)
  916. {
  917. const float epsilon = 50.0f;
  918. Vector2D toPoint;
  919. toPoint.x = point->x - myOrigin.x;
  920. toPoint.y = point->y - myOrigin.y;
  921. if (DotProduct2D( toPoint, initDir.AsVector2D() ) < 0.0f || toPoint.IsLengthLessThan( epsilon ))
  922. {
  923. int i;
  924. for( i=startIndex; i<m_pathLength; ++i )
  925. {
  926. toPoint.x = m_path[i].pos.x - myOrigin.x;
  927. toPoint.y = m_path[i].pos.y - myOrigin.y;
  928. if (m_path[i].ladder || m_path[i].area->GetAttributes() & (NAV_MESH_JUMP | NAV_MESH_STOP | NAV_MESH_STAIRS) || toPoint.IsLengthGreaterThan( epsilon ))
  929. {
  930. *point = m_path[i].pos;
  931. startIndex = i;
  932. break;
  933. }
  934. }
  935. if (i == m_pathLength)
  936. {
  937. *point = GetPathEndpoint();
  938. startIndex = m_pathLength-1;
  939. }
  940. }
  941. }
  942. // m_pathIndex should always be the next point on the path, even if we're not moving directly towards it
  943. return startIndex;
  944. }
  945. //--------------------------------------------------------------------------------------------------------------
  946. /**
  947. * Set the current index along the path
  948. */
  949. void CCSBot::SetPathIndex( int newIndex )
  950. {
  951. m_pathIndex = MIN( newIndex, m_pathLength-1 );
  952. m_areaEnteredTimestamp = gpGlobals->curtime;
  953. if (m_path[ m_pathIndex ].ladder)
  954. {
  955. SetupLadderMovement();
  956. }
  957. else
  958. {
  959. // get our "encounter spots" for this leg of the path
  960. if (m_pathIndex < m_pathLength && m_pathIndex >= 2)
  961. m_spotEncounter = m_path[ m_pathIndex-1 ].area->GetSpotEncounter( m_path[ m_pathIndex-2 ].area, m_path[ m_pathIndex ].area );
  962. else
  963. m_spotEncounter = NULL;
  964. m_pathLadder = NULL;
  965. }
  966. }
  967. //--------------------------------------------------------------------------------------------------------------
  968. /**
  969. * Return true if nearing a jump in the path
  970. */
  971. bool CCSBot::IsNearJump( void ) const
  972. {
  973. if (m_pathIndex == 0 || m_pathIndex >= m_pathLength)
  974. return false;
  975. for( int i=m_pathIndex-1; i<m_pathIndex; ++i )
  976. {
  977. if (m_path[ i ].area->GetAttributes() & NAV_MESH_JUMP)
  978. {
  979. float dz = m_path[ i+1 ].pos.z - m_path[ i ].pos.z;
  980. if (dz > 0.0f)
  981. return true;
  982. }
  983. }
  984. return false;
  985. }
  986. //--------------------------------------------------------------------------------------------------------------
  987. /**
  988. * Return approximately how much damage will will take from the given fall height
  989. */
  990. float CCSBot::GetApproximateFallDamage( float height ) const
  991. {
  992. // empirically discovered height values
  993. const float slope = 0.2178f;
  994. const float intercept = 26.0f;
  995. float damage = slope * height - intercept;
  996. if (damage < 0.0f)
  997. return 0.0f;
  998. return damage;
  999. }
  1000. //--------------------------------------------------------------------------------------------------------------
  1001. /**
  1002. * Return true if a friend is between us and the given position
  1003. */
  1004. bool CCSBot::IsFriendInTheWay( const Vector &goalPos )
  1005. {
  1006. if ( !CSGameRules()->IsTeammateSolid() )
  1007. {
  1008. // we can pass right thru teammates in the mode - no waiting
  1009. return false;
  1010. }
  1011. // do this check less often to ease CPU burden
  1012. if (!m_avoidFriendTimer.IsElapsed())
  1013. {
  1014. return m_isFriendInTheWay;
  1015. }
  1016. const float avoidFriendInterval = 0.5f;
  1017. m_avoidFriendTimer.Start( avoidFriendInterval );
  1018. // compute ray along intended path
  1019. Vector myOrigin = GetCentroid( this );
  1020. Vector moveDir = goalPos - myOrigin;
  1021. // make it a unit vector
  1022. float length = moveDir.NormalizeInPlace();
  1023. m_isFriendInTheWay = false;
  1024. // check if any friends are overlapping this linear path
  1025. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  1026. {
  1027. CCSPlayer *player = static_cast<CCSPlayer *>( UTIL_PlayerByIndex( i ) );
  1028. if (player == NULL)
  1029. continue;
  1030. if (!player->IsAlive())
  1031. continue;
  1032. if ( IsOtherEnemy( player ) )
  1033. continue;
  1034. if (player->entindex() == entindex())
  1035. continue;
  1036. // compute vector from us to our friend
  1037. Vector toFriend = player->GetAbsOrigin() - GetAbsOrigin();
  1038. // check if friend is in our "personal space"
  1039. const float personalSpace = 100.0f;
  1040. if (toFriend.IsLengthGreaterThan( personalSpace ))
  1041. continue;
  1042. // find distance of friend along our movement path
  1043. float friendDistAlong = DotProduct( toFriend, moveDir );
  1044. // if friend is behind us, ignore him
  1045. if (friendDistAlong <= 0.0f)
  1046. continue;
  1047. // constrain point to be on path segment
  1048. Vector pos;
  1049. if (friendDistAlong >= length)
  1050. pos = goalPos;
  1051. else
  1052. pos = myOrigin + friendDistAlong * moveDir;
  1053. // check if friend overlaps our intended line of movement
  1054. const float friendRadius = 30.0f;
  1055. if ((pos - GetCentroid( player )).IsLengthLessThan( friendRadius ))
  1056. {
  1057. // friend is in our personal space and overlaps our intended line of movement
  1058. m_isFriendInTheWay = true;
  1059. break;
  1060. }
  1061. }
  1062. return m_isFriendInTheWay;
  1063. }
  1064. //--------------------------------------------------------------------------------------------------------------
  1065. /**
  1066. * Do reflex avoidance movements if our "feelers" are touched
  1067. */
  1068. void CCSBot::FeelerReflexAdjustment( Vector *goalPosition )
  1069. {
  1070. // if we are in a "precise" area, do not do feeler adjustments
  1071. if (m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_PRECISE)
  1072. return;
  1073. Vector dir( BotCOS( m_forwardAngle ), BotSIN( m_forwardAngle ), 0.0f );
  1074. Vector lat( -dir.y, dir.x, 0.0f );
  1075. const float feelerOffset = (IsCrouching()) ? 5.0f : 10.0f;
  1076. const float feelerLengthRun = 25.0f; // 50
  1077. const float feelerLengthWalk = 15.0f;
  1078. const float feelerHeight = StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it
  1079. float feelerLength = (IsRunning()) ? feelerLengthRun : feelerLengthWalk;
  1080. feelerLength = (IsCrouching()) ? 20.0f : feelerLength;
  1081. //
  1082. // Feelers must follow floor slope
  1083. //
  1084. float ground;
  1085. Vector normal;
  1086. Vector eye = EyePosition();
  1087. if (GetSimpleGroundHeightWithFloor( eye, &ground, &normal ) == false)
  1088. return;
  1089. // get forward vector along floor
  1090. dir = CrossProduct( lat, normal );
  1091. // correct the sideways vector
  1092. lat = CrossProduct( dir, normal );
  1093. Vector feet = GetAbsOrigin();
  1094. feet.z += feelerHeight;
  1095. Vector from = feet + feelerOffset * lat;
  1096. Vector to = from + feelerLength * dir;
  1097. const float hullSize = 10.0f;
  1098. Vector mins( -hullSize, -hullSize, 0.0f );
  1099. Vector maxs( hullSize, hullSize, HalfHumanHeight - feelerHeight );
  1100. bool leftClear = IsWalkableTraceHullClear( from, to, mins, maxs, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
  1101. // avoid ledges, too
  1102. // use 'from' so it doesn't interfere with legitimate gap jumping (its at our feet)
  1103. /// @todo Rethink this - it causes lots of wiggling when bots jump down from vents, etc
  1104. /*
  1105. float ground;
  1106. if (GetSimpleGroundHeightWithFloor( &from, &ground ))
  1107. {
  1108. if (GetFeetZ() - ground > JumpHeight)
  1109. leftClear = false;
  1110. }
  1111. */
  1112. if (cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() || cv_bot_traceview.GetInt() == 10)
  1113. {
  1114. if (leftClear)
  1115. NDebugOverlay::SweptBox( from, to, mins, maxs, vec3_angle, 0, 255, 0, 255, 0.1f );
  1116. else
  1117. NDebugOverlay::SweptBox( from, to, mins, maxs, vec3_angle, 255, 0, 0, 255, 0.1f );
  1118. }
  1119. from = feet - feelerOffset * lat;
  1120. to = from + feelerLength * dir;
  1121. bool rightClear = IsWalkableTraceHullClear( from, to, mins, maxs, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
  1122. /*
  1123. // avoid ledges, too
  1124. if (GetSimpleGroundHeightWithFloor( &from, &ground ))
  1125. {
  1126. if (GetFeetZ() - ground > JumpHeight)
  1127. rightClear = false;
  1128. }
  1129. */
  1130. if (cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() || cv_bot_traceview.GetInt() == 10)
  1131. {
  1132. if (rightClear)
  1133. NDebugOverlay::SweptBox( from, to, mins, maxs, vec3_angle, 0, 255, 0, 255, 0.1f );
  1134. else
  1135. NDebugOverlay::SweptBox( from, to, mins, maxs, vec3_angle, 255, 0, 0, 255, 0.1f );
  1136. }
  1137. const float avoidRange = (IsCrouching()) ? 150.0f : 300.0f; // 50, 300
  1138. if (!rightClear)
  1139. {
  1140. if (leftClear)
  1141. {
  1142. // right hit, left clear - veer left
  1143. *goalPosition = *goalPosition + avoidRange * lat;
  1144. }
  1145. }
  1146. else if (!leftClear)
  1147. {
  1148. // right clear, left hit - veer right
  1149. *goalPosition = *goalPosition - avoidRange * lat;
  1150. }
  1151. }
  1152. //--------------------------------------------------------------------------------------------------------------
  1153. /**
  1154. * Allows the current nav area to make us run/walk without messing with our state
  1155. */
  1156. bool CCSBot::IsRunning( void ) const
  1157. {
  1158. // if we've forced running, go with it
  1159. if ( !m_mustRunTimer.IsElapsed() )
  1160. {
  1161. return BaseClass::IsRunning();
  1162. }
  1163. if ( m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_RUN )
  1164. {
  1165. return true;
  1166. }
  1167. if ( m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_WALK )
  1168. {
  1169. return false;
  1170. }
  1171. return BaseClass::IsRunning();
  1172. }
  1173. //--------------------------------------------------------------------------------------------------------------
  1174. /**
  1175. * Move along the path. Return false if end of path reached.
  1176. */
  1177. CCSBot::PathResult CCSBot::UpdatePathMovement( bool allowSpeedChange )
  1178. {
  1179. VPROF_BUDGET( "CCSBot::UpdatePathMovement", VPROF_BUDGETGROUP_NPCS );
  1180. if (m_pathLength == 0)
  1181. return PATH_FAILURE;
  1182. if (cv_bot_walk.GetBool())
  1183. Walk();
  1184. //
  1185. // If we are navigating a ladder, it overrides all other path movement until complete
  1186. //
  1187. if (UpdateLadderMovement())
  1188. return PROGRESSING;
  1189. // ladder failure can destroy the path
  1190. if (m_pathLength == 0)
  1191. return PATH_FAILURE;
  1192. // we are not supposed to be on a ladder - if we are, jump off
  1193. if (IsOnLadder())
  1194. Jump( MUST_JUMP );
  1195. assert( m_pathIndex < m_pathLength );
  1196. //
  1197. // Stop path attribute
  1198. //
  1199. if (!IsUsingLadder())
  1200. {
  1201. // if the m_isStopping flag is set, clear our movement
  1202. // if the m_isStopping flag is set and movement is stopped, clear m_isStopping
  1203. if ( m_lastKnownArea && m_isStopping )
  1204. {
  1205. ResetStuckMonitor();
  1206. ClearMovement();
  1207. if ( GetAbsVelocity().LengthSqr() < 0.1f )
  1208. {
  1209. m_isStopping = false;
  1210. }
  1211. else
  1212. {
  1213. return PROGRESSING;
  1214. }
  1215. }
  1216. } // end stop logic
  1217. //
  1218. // Check if reached the end of the path
  1219. //
  1220. bool nearEndOfPath = false;
  1221. if (m_pathIndex >= m_pathLength-1)
  1222. {
  1223. Vector toEnd = GetPathEndpoint() - GetAbsOrigin();
  1224. Vector d = toEnd; // can't use 2D because path end may be below us (jump down)
  1225. const float walkRange = 200.0f;
  1226. // walk as we get close to the goal position to ensure we hit it
  1227. if (d.IsLengthLessThan( walkRange ))
  1228. {
  1229. // don't walk if crouching - too slow
  1230. if (allowSpeedChange && !IsCrouching())
  1231. Walk();
  1232. // note if we are near the end of the path
  1233. const float nearEndRange = 50.0f;
  1234. if (d.IsLengthLessThan( nearEndRange ))
  1235. nearEndOfPath = true;
  1236. const float closeEpsilon = 20.0f;
  1237. if (d.IsLengthLessThan( closeEpsilon ))
  1238. {
  1239. // reached goal position - path complete
  1240. DestroyPath();
  1241. /// @todo We should push and pop walk state here, in case we want to continue walking after reaching goal
  1242. if (allowSpeedChange)
  1243. Run();
  1244. return END_OF_PATH;
  1245. }
  1246. }
  1247. }
  1248. //
  1249. // To keep us moving smoothly, we will move towards
  1250. // a point farther ahead of us down our path.
  1251. //
  1252. int prevIndex = 0; // closest index on path just prior to where we are now
  1253. const float aheadRange = 300.0f;
  1254. int newIndex = FindPathPoint( aheadRange, &m_goalPosition, &prevIndex );
  1255. // BOTPORT: Why is prevIndex sometimes -1?
  1256. if (prevIndex < 0)
  1257. prevIndex = 0;
  1258. // if goal position is near to us, we must be about to go around a corner - so look ahead!
  1259. Vector myOrigin = GetCentroid( this );
  1260. const float nearCornerRange = 100.0f;
  1261. if (m_pathIndex < m_pathLength-1 && (m_goalPosition - myOrigin).IsLengthLessThan( nearCornerRange ))
  1262. {
  1263. if (!IsLookingAtSpot( PRIORITY_HIGH ))
  1264. {
  1265. ClearLookAt();
  1266. InhibitLookAround( 0.5f );
  1267. }
  1268. }
  1269. // if we moved to a new node on the path, setup movement
  1270. if (newIndex > m_pathIndex)
  1271. {
  1272. SetPathIndex( newIndex );
  1273. }
  1274. //
  1275. // Crouching
  1276. //
  1277. if (!IsUsingLadder())
  1278. {
  1279. // if we are approaching a crouch area, crouch
  1280. // if there are no crouch areas coming up, stand
  1281. const float crouchRange = 50.0f;
  1282. bool didCrouch = false;
  1283. for( int i=prevIndex; i<m_pathLength; ++i )
  1284. {
  1285. const CNavArea *to = m_path[i].area;
  1286. // if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump
  1287. // unless we are already higher than the jump area - we must've jumped already but not moved into next area
  1288. if (to->GetAttributes() & NAV_MESH_JUMP && to->GetCenter().z > GetFeetZ())
  1289. break;
  1290. Vector close;
  1291. to->GetClosestPointOnArea( myOrigin, &close );
  1292. if ((close - myOrigin).AsVector2D().IsLengthGreaterThan( crouchRange ))
  1293. break;
  1294. if (to->GetAttributes() & NAV_MESH_CROUCH)
  1295. {
  1296. Crouch();
  1297. didCrouch = true;
  1298. ResetStuckMonitor();
  1299. break;
  1300. }
  1301. }
  1302. if (!didCrouch && !IsJumping())
  1303. {
  1304. // no crouch areas coming up
  1305. StandUp();
  1306. }
  1307. } // end crouching logic
  1308. // compute our forward facing angle
  1309. m_forwardAngle = UTIL_VecToYaw( m_goalPosition - myOrigin );
  1310. //
  1311. // Look farther down the path to "lead" our view around corners
  1312. //
  1313. Vector toGoal;
  1314. bool isWaitingForLadder = false;
  1315. // if we are crouching, look towards where we are moving to negotiate tight corners
  1316. if (IsCrouching())
  1317. {
  1318. m_lookAheadAngle = m_forwardAngle;
  1319. }
  1320. else
  1321. {
  1322. if (m_pathIndex == 0)
  1323. {
  1324. toGoal = m_path[1].pos;
  1325. }
  1326. else if (m_pathIndex < m_pathLength)
  1327. {
  1328. toGoal = m_path[ m_pathIndex ].pos - myOrigin;
  1329. // actually aim our view farther down the path
  1330. const float lookAheadRange = 500.0f;
  1331. if (!m_path[ m_pathIndex ].ladder &&
  1332. !IsNearJump() &&
  1333. toGoal.AsVector2D().IsLengthLessThan( lookAheadRange ))
  1334. {
  1335. float along = toGoal.Length2D();
  1336. int i;
  1337. for( i=m_pathIndex+1; i<m_pathLength; ++i )
  1338. {
  1339. Vector delta = m_path[i].pos - m_path[i-1].pos;
  1340. float segmentLength = delta.Length2D();
  1341. if (along + segmentLength >= lookAheadRange)
  1342. {
  1343. // interpolate between points to keep look ahead point at fixed distance
  1344. float t = (lookAheadRange - along) / (segmentLength + along);
  1345. Vector target;
  1346. if (t <= 0.0f)
  1347. target = m_path[i-1].pos;
  1348. else if (t >= 1.0f)
  1349. target = m_path[i].pos;
  1350. else
  1351. target = m_path[i-1].pos + t * delta;
  1352. toGoal = target - myOrigin;
  1353. break;
  1354. }
  1355. // if we are coming up to a ladder or a jump, look at it
  1356. if (m_path[i].ladder ||
  1357. (m_path[i].area->GetAttributes() & NAV_MESH_JUMP) ||
  1358. (m_path[i].area->GetAttributes() & NAV_MESH_PRECISE) ||
  1359. (m_path[i].area->GetAttributes() & NAV_MESH_STOP))
  1360. {
  1361. toGoal = m_path[i].pos - myOrigin;
  1362. // if anyone is on the ladder, wait
  1363. if (m_path[i].ladder && m_path[i].ladder->IsInUse( this ))
  1364. {
  1365. isWaitingForLadder = true;
  1366. ResetStuckMonitor();
  1367. // if we are too close to the ladder, back off a bit
  1368. const float tooCloseRange = 100.0f;
  1369. Vector2D delta( m_path[i].ladder->m_top.x - myOrigin.x,
  1370. m_path[i].ladder->m_top.y - myOrigin.y );
  1371. if (delta.IsLengthLessThan( tooCloseRange ))
  1372. {
  1373. MoveAwayFromPosition( m_path[i].ladder->m_top );
  1374. }
  1375. }
  1376. break;
  1377. }
  1378. along += segmentLength;
  1379. }
  1380. if (i == m_pathLength)
  1381. toGoal = GetPathEndpoint() - myOrigin;
  1382. }
  1383. }
  1384. else
  1385. {
  1386. toGoal = GetPathEndpoint() - myOrigin;
  1387. }
  1388. m_lookAheadAngle = UTIL_VecToYaw( toGoal );
  1389. }
  1390. // initialize "adjusted" goal to current goal
  1391. Vector adjustedGoal = m_goalPosition;
  1392. //
  1393. // Use short "feelers" to veer away from close-range obstacles
  1394. // Feelers come from our ankles, just above StepHeight, so we avoid short walls, too
  1395. // Don't use feelers if very near the end of the path, or about to jump
  1396. //
  1397. /// @todo Consider having feelers at several heights to deal with overhangs, etc.
  1398. if (!nearEndOfPath && !IsNearJump() && !IsJumping())
  1399. {
  1400. FeelerReflexAdjustment( &adjustedGoal );
  1401. }
  1402. // draw debug visualization
  1403. if (cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() || cv_bot_traceview.GetInt() == 10)
  1404. {
  1405. DrawPath();
  1406. const Vector *pos = &m_path[ m_pathIndex ].pos;
  1407. UTIL_DrawBeamPoints( *pos, *pos + Vector( 0, 0, 50 ), 1, 255, 255, 0 );
  1408. UTIL_DrawBeamPoints( adjustedGoal, adjustedGoal + Vector( 0, 0, 50 ), 1, 255, 0, 255 );
  1409. UTIL_DrawBeamPoints( myOrigin, adjustedGoal + Vector( 0, 0, 50 ), 1, 255, 0, 255 );
  1410. }
  1411. // dont use adjustedGoal, as it can vary wildly from the feeler adjustment
  1412. if (!IsAttacking() && IsFriendInTheWay( m_goalPosition ))
  1413. {
  1414. if (!m_isWaitingBehindFriend)
  1415. {
  1416. m_isWaitingBehindFriend = true;
  1417. const float politeDuration = 5.0f - 3.0f * GetProfile()->GetAggression();
  1418. m_politeTimer.Start( politeDuration );
  1419. }
  1420. else if (m_politeTimer.IsElapsed())
  1421. {
  1422. // we have run out of patience
  1423. m_isWaitingBehindFriend = false;
  1424. ResetStuckMonitor();
  1425. // repath to avoid clump of friends in the way
  1426. DestroyPath();
  1427. }
  1428. }
  1429. else if (m_isWaitingBehindFriend)
  1430. {
  1431. // we're done waiting for our friend to move
  1432. m_isWaitingBehindFriend = false;
  1433. ResetStuckMonitor();
  1434. }
  1435. //
  1436. // Move along our path if there are no friends blocking our way,
  1437. // or we have run out of patience
  1438. //
  1439. if (!isWaitingForLadder && (!m_isWaitingBehindFriend || m_politeTimer.IsElapsed()))
  1440. {
  1441. //
  1442. // Move along path
  1443. //
  1444. MoveTowardsPosition( adjustedGoal );
  1445. //
  1446. // Stuck check
  1447. //
  1448. if (m_isStuck && !IsJumping())
  1449. {
  1450. Wiggle();
  1451. }
  1452. }
  1453. // if our goal is high above us, we must have fallen
  1454. bool didFall = false;
  1455. if (m_goalPosition.z - GetFeetZ() > JumpCrouchHeight)
  1456. {
  1457. const float closeRange = 75.0f;
  1458. Vector2D to( myOrigin.x - m_goalPosition.x, myOrigin.y - m_goalPosition.y );
  1459. if (to.IsLengthLessThan( closeRange ))
  1460. {
  1461. // we can't reach the goal position
  1462. // check if we can reach the next node, in case this was a "jump down" situation
  1463. if (m_pathIndex < m_pathLength-1)
  1464. {
  1465. if (m_path[ m_pathIndex+1 ].pos.z - GetFeetZ() > JumpCrouchHeight)
  1466. {
  1467. // the next node is too high, too - we really did fall of the path
  1468. didFall = true;
  1469. for ( int i=m_pathIndex; i<=m_pathIndex+1; ++i )
  1470. {
  1471. if ( m_path[i].how == GO_LADDER_UP )
  1472. {
  1473. // if we're going up a ladder, and we're within reach of the ladder bottom, we haven't fallen
  1474. if ( m_path[i].pos.z - GetFeetZ() <= JumpCrouchHeight )
  1475. {
  1476. didFall = false;
  1477. break;
  1478. }
  1479. }
  1480. }
  1481. }
  1482. }
  1483. else
  1484. {
  1485. // fell trying to get to the last node in the path
  1486. didFall = true;
  1487. }
  1488. }
  1489. }
  1490. //
  1491. // This timeout check is needed if the bot somehow slips way off
  1492. // of its path and cannot progress, but also moves around
  1493. // enough that it never becomes "stuck"
  1494. //
  1495. const float giveUpDuration = 4.0f;
  1496. if (didFall || gpGlobals->curtime - m_areaEnteredTimestamp > giveUpDuration)
  1497. {
  1498. if (didFall)
  1499. {
  1500. PrintIfWatched( "I fell off!\n" );
  1501. if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
  1502. {
  1503. CBasePlayer *localPlayer = UTIL_GetListenServerHost();
  1504. CSingleUserRecipientFilter filter( localPlayer );
  1505. EmitSound( filter, localPlayer->entindex(), "Bot.FellOff" );
  1506. }
  1507. }
  1508. // if we havent made any progress in a long time, give up
  1509. if (m_pathIndex < m_pathLength-1)
  1510. {
  1511. PrintIfWatched( "Giving up trying to get to area #%d\n", m_path[ m_pathIndex ].area->GetID() );
  1512. }
  1513. else
  1514. {
  1515. PrintIfWatched( "Giving up trying to get to end of path\n" );
  1516. }
  1517. Run();
  1518. StandUp();
  1519. DestroyPath();
  1520. ClearLookAt();
  1521. // See if we should be on a different nav area
  1522. CNavArea *area = TheNavMesh->GetNearestNavArea( GetAbsOrigin(), false, 500.0f, true );
  1523. if (area && area != m_lastNavArea)
  1524. {
  1525. if (m_lastNavArea)
  1526. {
  1527. m_lastNavArea->DecrementPlayerCount( GetTeamNumber(), entindex() );
  1528. }
  1529. area->IncrementPlayerCount( GetTeamNumber(), entindex() );
  1530. m_lastNavArea = area;
  1531. if ( area->GetPlace() != UNDEFINED_PLACE )
  1532. {
  1533. const char *placeName = TheNavMesh->PlaceToName( area->GetPlace() );
  1534. if ( placeName && *placeName )
  1535. {
  1536. Q_strncpy( m_szLastPlaceName.GetForModify(), placeName, MAX_PLACE_NAME_LENGTH );
  1537. }
  1538. }
  1539. // generate event
  1540. //KeyValues *event = new KeyValues( "player_entered_area" );
  1541. //event->SetInt( "userid", GetUserID() );
  1542. //event->SetInt( "areaid", area->GetID() );
  1543. //gameeventmanager->FireEvent( event );
  1544. }
  1545. return PATH_FAILURE;
  1546. }
  1547. return PROGRESSING;
  1548. }
  1549. //--------------------------------------------------------------------------------------------------------------
  1550. /**
  1551. * Build trivial path to goal, assuming we are already in the same area
  1552. */
  1553. void CCSBot::BuildTrivialPath( const Vector &goal )
  1554. {
  1555. Vector myOrigin = GetCentroid( this );
  1556. m_pathIndex = 1;
  1557. m_pathLength = 2;
  1558. m_path[0].area = m_lastKnownArea;
  1559. m_path[0].pos = myOrigin;
  1560. m_path[0].pos.z = m_lastKnownArea->GetZ( myOrigin );
  1561. m_path[0].ladder = NULL;
  1562. m_path[0].how = NUM_TRAVERSE_TYPES;
  1563. m_path[1].area = m_lastKnownArea;
  1564. m_path[1].pos = goal;
  1565. m_path[1].pos.z = m_lastKnownArea->GetZ( goal );
  1566. m_path[1].ladder = NULL;
  1567. m_path[1].how = NUM_TRAVERSE_TYPES;
  1568. m_areaEnteredTimestamp = gpGlobals->curtime;
  1569. m_spotEncounter = NULL;
  1570. m_pathLadder = NULL;
  1571. m_goalPosition = goal;
  1572. }
  1573. //--------------------------------------------------------------------------------------------------------------
  1574. /**
  1575. * Compute shortest path to goal position via A* algorithm
  1576. * If 'goalArea' is NULL, path will get as close as it can.
  1577. */
  1578. bool CCSBot::ComputePath( const Vector &goal, RouteType route )
  1579. {
  1580. VPROF_BUDGET( "CCSBot::ComputePath", VPROF_BUDGETGROUP_NPCS );
  1581. SNPROF( "CCSBot::ComputePath");
  1582. //
  1583. // Throttle re-pathing
  1584. //
  1585. if (!m_repathTimer.IsElapsed())
  1586. return false;
  1587. // randomize to distribute CPU load
  1588. m_repathTimer.Start( RandomFloat( 0.4f, 0.6f ) );
  1589. DestroyPath();
  1590. m_pathLadder = NULL;
  1591. CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal );
  1592. CNavArea *startArea = m_lastKnownArea;
  1593. if (startArea == NULL)
  1594. return false;
  1595. // if we fell off a ledge onto an area off the mesh, we will path from the
  1596. // ledge above our heads, resulting in a path we can't follow.
  1597. Vector close;
  1598. startArea->GetClosestPointOnArea( EyePosition(), &close );
  1599. if (close.z - GetAbsOrigin().z > JumpCrouchHeight)
  1600. {
  1601. // we can't reach our last known area - find nearest area to us
  1602. PrintIfWatched( "Last known area is above my head - resetting to nearest area.\n" );
  1603. m_lastKnownArea = (CCSNavArea*)TheNavMesh->GetNearestNavArea( GetAbsOrigin(), false, 500.0f, true );
  1604. if (m_lastKnownArea == NULL)
  1605. {
  1606. return false;
  1607. }
  1608. startArea = m_lastKnownArea;
  1609. }
  1610. // note final specific position
  1611. Vector pathEndPosition = goal;
  1612. // make sure path end position is on the ground
  1613. if (goalArea)
  1614. pathEndPosition.z = goalArea->GetZ( pathEndPosition );
  1615. else
  1616. TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
  1617. // if we are already in the goal area, build trivial path
  1618. if (startArea == goalArea)
  1619. {
  1620. BuildTrivialPath( pathEndPosition );
  1621. return true;
  1622. }
  1623. TheCSBots()->OnExpensiveBotOperation();
  1624. //
  1625. // Compute shortest path to goal
  1626. //
  1627. CNavArea *closestArea = NULL;
  1628. PathCost cost( this, route );
  1629. bool pathToGoalExists = NavAreaBuildPath( startArea, goalArea, &goal, cost, &closestArea );
  1630. CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea;
  1631. //
  1632. // Build path by following parent links
  1633. //
  1634. // get count
  1635. int count = 0;
  1636. CNavArea *area;
  1637. for( area = effectiveGoalArea; area; area = area->GetParent() )
  1638. ++count;
  1639. // save room for endpoint
  1640. if (count > MAX_PATH_LENGTH-1)
  1641. count = MAX_PATH_LENGTH-1;
  1642. if (count == 0)
  1643. return false;
  1644. if (count == 1)
  1645. {
  1646. BuildTrivialPath( pathEndPosition );
  1647. return true;
  1648. }
  1649. // build path
  1650. m_pathLength = count;
  1651. for( area = effectiveGoalArea; count && area; area = area->GetParent() )
  1652. {
  1653. --count;
  1654. m_path[ count ].area = area;
  1655. m_path[ count ].how = area->GetParentHow();
  1656. }
  1657. // compute path positions
  1658. if (ComputePathPositions() == false)
  1659. {
  1660. PrintIfWatched( "Error building path\n" );
  1661. DestroyPath();
  1662. return false;
  1663. }
  1664. // append path end position
  1665. m_path[ m_pathLength ].area = effectiveGoalArea;
  1666. m_path[ m_pathLength ].pos = pathEndPosition;
  1667. m_path[ m_pathLength ].ladder = NULL;
  1668. m_path[ m_pathLength ].how = NUM_TRAVERSE_TYPES;
  1669. ++m_pathLength;
  1670. // do movement setup
  1671. m_pathIndex = 1;
  1672. m_areaEnteredTimestamp = gpGlobals->curtime;
  1673. m_spotEncounter = NULL;
  1674. m_goalPosition = m_path[1].pos;
  1675. if (m_path[1].ladder)
  1676. SetupLadderMovement();
  1677. else
  1678. m_pathLadder = NULL;
  1679. // find initial encounter area along this path, if we are in the early part of the round
  1680. if (IsSafe())
  1681. {
  1682. int myTeam = GetTeamNumber();
  1683. int enemyTeam = OtherTeam( myTeam );
  1684. int i;
  1685. for( i=0; i<m_pathLength; ++i )
  1686. {
  1687. if (m_path[i].area->GetEarliestOccupyTime( myTeam ) > m_path[i].area->GetEarliestOccupyTime( enemyTeam ))
  1688. {
  1689. break;
  1690. }
  1691. }
  1692. if (i < m_pathLength)
  1693. {
  1694. SetInitialEncounterArea( m_path[i].area );
  1695. }
  1696. else
  1697. {
  1698. SetInitialEncounterArea( NULL );
  1699. }
  1700. }
  1701. return true;
  1702. }
  1703. //--------------------------------------------------------------------------------------------------------------
  1704. /**
  1705. * Return estimated distance left to travel along path
  1706. */
  1707. float CCSBot::GetPathDistanceRemaining( void ) const
  1708. {
  1709. if (!HasPath())
  1710. return -1.0f;
  1711. int idx = (m_pathIndex < m_pathLength) ? m_pathIndex : m_pathLength-1;
  1712. float dist = 0.0f;
  1713. Vector prevCenter = m_path[m_pathIndex].area->GetCenter();
  1714. for( int i=idx+1; i<m_pathLength; ++i )
  1715. {
  1716. dist += (m_path[i].area->GetCenter() - prevCenter).Length();
  1717. prevCenter = m_path[i].area->GetCenter();
  1718. }
  1719. return dist;
  1720. }
  1721. //--------------------------------------------------------------------------------------------------------------
  1722. /**
  1723. * Draw a portion of our current path for debugging.
  1724. */
  1725. void CCSBot::DrawPath( void )
  1726. {
  1727. if (!HasPath())
  1728. return;
  1729. for( int i=1; i<m_pathLength; ++i )
  1730. {
  1731. UTIL_DrawBeamPoints( m_path[i-1].pos, m_path[i].pos, 2, 255, 75, 0 );
  1732. }
  1733. Vector close;
  1734. if (FindOurPositionOnPath( &close, true ) >= 0)
  1735. {
  1736. UTIL_DrawBeamPoints( close + Vector( 0, 0, 25 ), close, 1, 0, 255, 0 );
  1737. UTIL_DrawBeamPoints( close + Vector( 25, 0, 0 ), close + Vector( -25, 0, 0 ), 1, 0, 255, 0 );
  1738. UTIL_DrawBeamPoints( close + Vector( 0, 25, 0 ), close + Vector( 0, -25, 0 ), 1, 0, 255, 0 );
  1739. }
  1740. }