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.

1442 lines
38 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // NextBotGroundLocomotion.cpp
  3. // Basic ground-based movement for NextBotCombatCharacters
  4. // Author: Michael Booth, February 2009
  5. // Note: This is a refactoring of ZombieBotLocomotion from L4D
  6. #include "cbase.h"
  7. #include "func_break.h"
  8. #include "func_breakablesurf.h"
  9. #include "activitylist.h"
  10. #include "BasePropDoor.h"
  11. #include "nav.h"
  12. #include "NextBot.h"
  13. #include "NextBotGroundLocomotion.h"
  14. #include "NextBotUtil.h"
  15. #include "functorutils.h"
  16. #include "SharedFunctorUtils.h"
  17. #include "tier0/vprof.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. #pragma warning( disable : 4355 ) // warning 'this' used in base member initializer list - we're using it safely
  21. //----------------------------------------------------------------------------------------------------------
  22. NextBotGroundLocomotion::NextBotGroundLocomotion( INextBot *bot ) : ILocomotion( bot )
  23. {
  24. m_nextBot = NULL;
  25. m_ladder = NULL;
  26. m_desiredLean.x = 0.0f;
  27. m_desiredLean.y = 0.0f;
  28. m_desiredLean.z = 0.0f;
  29. m_bRecomputePostureOnCollision = false;
  30. m_ignorePhysicsPropTimer.Invalidate();
  31. }
  32. //----------------------------------------------------------------------------------------------------------
  33. NextBotGroundLocomotion::~NextBotGroundLocomotion()
  34. {
  35. }
  36. //----------------------------------------------------------------------------------------------------------
  37. /**
  38. * Reset locomotor to initial state
  39. */
  40. void NextBotGroundLocomotion::Reset( void )
  41. {
  42. BaseClass::Reset();
  43. m_bRecomputePostureOnCollision = false;
  44. m_ignorePhysicsPropTimer.Invalidate();
  45. m_nextBot = static_cast< NextBotCombatCharacter * >( GetBot()->GetEntity() );
  46. m_desiredSpeed = 0.0f;
  47. m_velocity = vec3_origin;
  48. m_acceleration = vec3_origin;
  49. m_desiredLean.x = 0.0f;
  50. m_desiredLean.y = 0.0f;
  51. m_desiredLean.z = 0.0f;
  52. m_ladder = NULL;
  53. m_isJumping = false;
  54. m_isJumpingAcrossGap = false;
  55. m_ground = NULL;
  56. m_groundNormal = Vector( 0, 0, 1.0f );
  57. m_isClimbingUpToLedge = false;
  58. m_isUsingFullFeetTrace = false;
  59. m_moveVector = Vector( 1, 0, 0 );
  60. m_priorPos = m_nextBot->GetPosition();
  61. m_lastValidPos = m_nextBot->GetPosition();
  62. m_inhibitObstacleAvoidanceTimer.Invalidate();
  63. m_accumApproachVectors = vec3_origin;
  64. m_accumApproachWeights = 0.0f;
  65. }
  66. //----------------------------------------------------------------------------------------------------------
  67. /**
  68. * Move the bot along a ladder
  69. */
  70. bool NextBotGroundLocomotion::TraverseLadder( void )
  71. {
  72. // not climbing a ladder right now
  73. return false;
  74. }
  75. //----------------------------------------------------------------------------------------------------------
  76. /**
  77. * Update internal state
  78. */
  79. void NextBotGroundLocomotion::Update( void )
  80. {
  81. VPROF_BUDGET( "NextBotGroundLocomotion::Update", "NextBot" );
  82. BaseClass::Update();
  83. const float deltaT = GetUpdateInterval();
  84. // apply accumulated position changes
  85. ApplyAccumulatedApproach();
  86. // need to do this first thing, because ground constraints, etc, can change it
  87. Vector origPos = GetFeet();
  88. IBody *body = GetBot()->GetBodyInterface();
  89. if ( TraverseLadder() )
  90. {
  91. // bot is climbing a ladder
  92. return;
  93. }
  94. if ( !body->IsPostureMobile() )
  95. {
  96. // sitting/lying on the ground - no slip
  97. m_acceleration.x = 0.0f;
  98. m_acceleration.y = 0.0f;
  99. m_velocity.x = 0.0f;
  100. m_velocity.y = 0.0f;
  101. }
  102. bool wasOnGround = IsOnGround();
  103. if ( !body->HasActivityType( IBody::MOTION_CONTROLLED_Z ) )
  104. {
  105. // fall if in the air
  106. if ( !IsOnGround() )
  107. {
  108. // no ground below us - fall
  109. m_acceleration.z -= GetGravity();
  110. }
  111. if ( !IsClimbingOrJumping() || m_velocity.z <= 0.0f )
  112. {
  113. // keep us on the ground
  114. UpdateGroundConstraint();
  115. }
  116. }
  117. Vector newPos = GetFeet();
  118. //
  119. // Update position physics
  120. //
  121. Vector right( m_moveVector.y, -m_moveVector.x, 0.0f );
  122. if ( IsOnGround() ) // || m_isClimbingUpToLedge )
  123. {
  124. if ( IsAttemptingToMove() )
  125. {
  126. float forwardSpeed = DotProduct( m_velocity, m_moveVector );
  127. Vector forwardVelocity = forwardSpeed * m_moveVector;
  128. Vector sideVelocity = DotProduct( m_velocity, right ) * right;
  129. Vector frictionAccel = vec3_origin;
  130. // only apply friction along forward direction if we are sliding backwards
  131. if ( forwardSpeed < 0.0f )
  132. {
  133. frictionAccel = -GetFrictionForward() * forwardVelocity;
  134. }
  135. // always apply lateral friction to counteract sideslip
  136. frictionAccel += -GetFrictionSideways() * sideVelocity;
  137. m_acceleration.x += frictionAccel.x;
  138. m_acceleration.y += frictionAccel.y;
  139. }
  140. else
  141. {
  142. // come to a stop if we haven't been told to move
  143. m_acceleration = vec3_origin;
  144. m_velocity = vec3_origin;
  145. }
  146. }
  147. // compute new position, taking into account MOTION_CONTROLLED animations in progress
  148. if ( body->HasActivityType( IBody::MOTION_CONTROLLED_XY ) )
  149. {
  150. m_acceleration.x = 0.0f;
  151. m_acceleration.y = 0.0f;
  152. m_velocity.x = GetBot()->GetEntity()->GetAbsVelocity().x;
  153. m_velocity.y = GetBot()->GetEntity()->GetAbsVelocity().y;
  154. }
  155. else
  156. {
  157. // euler integration
  158. m_velocity.x += m_acceleration.x * deltaT;
  159. m_velocity.y += m_acceleration.y * deltaT;
  160. // euler integration
  161. newPos.x += m_velocity.x * deltaT;
  162. newPos.y += m_velocity.y * deltaT;
  163. }
  164. if ( body->HasActivityType( IBody::MOTION_CONTROLLED_Z ) )
  165. {
  166. m_acceleration.z = 0.0f;
  167. m_velocity.z = GetBot()->GetEntity()->GetAbsVelocity().z;
  168. }
  169. else
  170. {
  171. // euler integration
  172. m_velocity.z += m_acceleration.z * deltaT;
  173. // euler integration
  174. newPos.z += m_velocity.z * deltaT;
  175. }
  176. // move bot to new position, resolving collisions along the way
  177. UpdatePosition( newPos );
  178. // set actual velocity based on position change after collision resolution step
  179. Vector adjustedVelocity = ( GetFeet() - origPos ) / deltaT;
  180. if ( !body->HasActivityType( IBody::MOTION_CONTROLLED_XY ) )
  181. {
  182. m_velocity.x = adjustedVelocity.x;
  183. m_velocity.y = adjustedVelocity.y;
  184. }
  185. if ( !body->HasActivityType( IBody::MOTION_CONTROLLED_Z ) )
  186. {
  187. m_velocity.z = adjustedVelocity.z;
  188. }
  189. // collision resolution may create very high instantaneous velocities, limit it
  190. Vector2D groundVel = m_velocity.AsVector2D();
  191. m_actualSpeed = groundVel.NormalizeInPlace();
  192. if ( IsOnGround() )
  193. {
  194. if ( m_actualSpeed > GetRunSpeed() )
  195. {
  196. m_actualSpeed = GetRunSpeed();
  197. m_velocity.x = m_actualSpeed * groundVel.x;
  198. m_velocity.y = m_actualSpeed * groundVel.y;
  199. }
  200. // remove downward velocity when landing on the ground
  201. if ( !wasOnGround )
  202. {
  203. m_velocity.z = 0.0f;
  204. m_acceleration.z = 0.0f;
  205. }
  206. }
  207. else
  208. {
  209. // we're falling. if our velocity has become zero for any reason, shove it forward
  210. const float epsilon = 1.0f;
  211. if ( m_velocity.IsLengthLessThan( epsilon ) )
  212. {
  213. m_velocity = GetRunSpeed() * GetGroundMotionVector();
  214. }
  215. }
  216. // update entity velocity to that of locomotor
  217. m_nextBot->SetAbsVelocity( m_velocity );
  218. #ifdef LEANING
  219. // lean sideways proportional to lateral acceleration
  220. QAngle lean = GetDesiredLean();
  221. float sideAccel = DotProduct( right, m_acceleration );
  222. float slide = sideAccel / GetMaxAcceleration();
  223. // max lean depends on how fast we're actually moving
  224. float maxLeanAngle = NextBotLeanMaxAngle.GetFloat() * m_actualSpeed / GetRunSpeed();
  225. // actual lean angle is proportional to lateral acceleration (sliding)
  226. float desiredSideLean = -maxLeanAngle * slide;
  227. lean.y += ( desiredSideLean - lean.y ) * NextBotLeanRate.GetFloat() * deltaT;
  228. SetDesiredLean( lean );
  229. #endif // _DEBUG
  230. // reset acceleration accumulation
  231. m_acceleration = vec3_origin;
  232. // debug display
  233. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  234. {
  235. // track position over time
  236. if ( IsOnGround() )
  237. {
  238. NDebugOverlay::Cross3D( GetFeet(), 1.0f, 0, 255, 0, true, 15.0f );
  239. }
  240. else
  241. {
  242. NDebugOverlay::Cross3D( GetFeet(), 1.0f, 0, 255, 255, true, 15.0f );
  243. }
  244. }
  245. }
  246. //----------------------------------------------------------------------------------------------------------
  247. /**
  248. * Move directly towards given position.
  249. * We need to do this in-air as well to land jumps.
  250. */
  251. void NextBotGroundLocomotion::Approach( const Vector &rawPos, float goalWeight )
  252. {
  253. BaseClass::Approach( rawPos );
  254. m_accumApproachVectors += ( rawPos - GetFeet() ) * goalWeight;
  255. m_accumApproachWeights += goalWeight;
  256. m_bRecomputePostureOnCollision = true;
  257. }
  258. //----------------------------------------------------------------------------------------------------------
  259. void NextBotGroundLocomotion::ApplyAccumulatedApproach( void )
  260. {
  261. VPROF_BUDGET( "NextBotGroundLocomotion::ApplyAccumulatedApproach", "NextBot" );
  262. Vector rawPos = GetFeet();
  263. const float deltaT = GetUpdateInterval();
  264. if ( deltaT <= 0.0f )
  265. return;
  266. if ( m_accumApproachWeights > 0.0f )
  267. {
  268. Vector approachDelta = m_accumApproachVectors / m_accumApproachWeights;
  269. // limit total movement to our max speed
  270. float maxMove = GetRunSpeed() * deltaT;
  271. float desiredMove = approachDelta.NormalizeInPlace();
  272. if ( desiredMove > maxMove )
  273. {
  274. desiredMove = maxMove;
  275. }
  276. rawPos += desiredMove * approachDelta;
  277. m_accumApproachVectors = vec3_origin;
  278. m_accumApproachWeights = 0.0f;
  279. }
  280. // can only move in 2D - geometry moves us up and down
  281. Vector pos( rawPos.x, rawPos.y, GetFeet().z );
  282. if ( !GetBot()->GetBodyInterface()->IsPostureMobile() )
  283. {
  284. // body is not in a movable state right now
  285. return;
  286. }
  287. Vector currentPos = m_nextBot->GetPosition();
  288. // compute unit vector to goal position
  289. m_moveVector = pos - currentPos;
  290. m_moveVector.z = 0.0f;
  291. float change = m_moveVector.NormalizeInPlace();
  292. const float epsilon = 0.001f;
  293. if ( change < epsilon )
  294. {
  295. // no motion
  296. m_forwardLean = 0.0f;
  297. m_sideLean = 0.0f;
  298. return;
  299. }
  300. /*
  301. // lean forward/backward based on acceleration
  302. float desiredLean = m_acceleration / NextBotLeanForwardAccel.GetFloat();
  303. QAngle lean = GetDesiredLean();
  304. lean.x = NextBotLeanMaxAngle.GetFloat() * clamp( desiredLean, -1.0f, 1.0f );
  305. SetDesiredLean( lean );
  306. */
  307. Vector newPos;
  308. // if we just started a jump, don't snap to the ground - let us get in the air first
  309. if ( DidJustJump() || !IsOnGround() )
  310. {
  311. if ( false && m_isClimbingUpToLedge ) // causes bots to hang in air stuck against edges
  312. {
  313. // drive towards the approach position in XY to help reach ledge
  314. m_moveVector = m_ledgeJumpGoalPos - currentPos;
  315. m_moveVector.z = 0.0f;
  316. m_moveVector.NormalizeInPlace();
  317. m_acceleration += GetMaxAcceleration() * m_moveVector;
  318. }
  319. }
  320. else if ( IsOnGround() )
  321. {
  322. // on the ground - move towards the approach position
  323. m_isClimbingUpToLedge = false;
  324. // snap forward movement vector along floor
  325. const Vector &groundNormal = GetGroundNormal();
  326. Vector left( -m_moveVector.y, m_moveVector.x, 0.0f );
  327. m_moveVector = CrossProduct( left, groundNormal );
  328. m_moveVector.NormalizeInPlace();
  329. // limit maximum forward speed from self-acceleration
  330. float forwardSpeed = DotProduct( m_velocity, m_moveVector );
  331. float maxSpeed = MIN( m_desiredSpeed, GetSpeedLimit() );
  332. if ( forwardSpeed < maxSpeed )
  333. {
  334. float ratio = ( forwardSpeed <= 0.0f ) ? 0.0f : ( forwardSpeed / maxSpeed );
  335. float governor = 1.0f - ( ratio * ratio * ratio * ratio );
  336. // accelerate towards goal
  337. m_acceleration += governor * GetMaxAcceleration() * m_moveVector;
  338. }
  339. }
  340. }
  341. //----------------------------------------------------------------------------------------------------------
  342. /**
  343. * Move the bot to the precise given position immediately,
  344. */
  345. void NextBotGroundLocomotion::DriveTo( const Vector &pos )
  346. {
  347. BaseClass::DriveTo( pos );
  348. m_bRecomputePostureOnCollision = true;
  349. UpdatePosition( pos );
  350. }
  351. //--------------------------------------------------------------------------------------------
  352. /*
  353. * Trace filter solely for use with DetectCollision() below.
  354. */
  355. class GroundLocomotionCollisionTraceFilter : public CTraceFilterSimple
  356. {
  357. public:
  358. GroundLocomotionCollisionTraceFilter( INextBot *me, const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup )
  359. {
  360. m_me = me;
  361. }
  362. virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
  363. {
  364. if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
  365. {
  366. CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
  367. // don't collide with ourself
  368. if ( entity && m_me->IsSelf( entity ) )
  369. return false;
  370. return m_me->GetLocomotionInterface()->ShouldCollideWith( entity );
  371. }
  372. return false;
  373. }
  374. INextBot *m_me;
  375. };
  376. //----------------------------------------------------------------------------------------------------------
  377. /**
  378. * Check for collisions during move and attempt to resolve them
  379. */
  380. bool NextBotGroundLocomotion::DetectCollision( trace_t *pTrace, int &recursionLimit, const Vector &from, const Vector &to, const Vector &vecMins, const Vector &vecMaxs )
  381. {
  382. IBody *body = GetBot()->GetBodyInterface();
  383. CBaseEntity *ignore = m_ignorePhysicsPropTimer.IsElapsed() ? NULL : m_ignorePhysicsProp;
  384. GroundLocomotionCollisionTraceFilter filter( GetBot(), ignore, body->GetCollisionGroup() );
  385. TraceHull( from, to, vecMins, vecMaxs, body->GetSolidMask(), &filter, pTrace );
  386. if ( !pTrace->DidHit() )
  387. return false;
  388. //
  389. // A collision occurred - resolve it
  390. //
  391. // bust through "flimsy" breakables and keep on going
  392. if ( pTrace->DidHitNonWorldEntity() && pTrace->m_pEnt != NULL )
  393. {
  394. CBaseEntity *other = pTrace->m_pEnt;
  395. if ( !other->MyCombatCharacterPointer() && IsEntityTraversable( other, IMMEDIATELY ) /*&& IsFlimsy( other )*/ )
  396. {
  397. if ( recursionLimit <= 0 )
  398. return true;
  399. --recursionLimit;
  400. // break the weak breakable we collided with
  401. CTakeDamageInfo damageInfo( GetBot()->GetEntity(), GetBot()->GetEntity(), 100.0f, DMG_CRUSH );
  402. CalculateExplosiveDamageForce( &damageInfo, GetMotionVector(), pTrace->endpos );
  403. other->TakeDamage( damageInfo );
  404. // retry trace now that the breakable is out of the way
  405. return DetectCollision( pTrace, recursionLimit, from, to, vecMins, vecMaxs );
  406. }
  407. }
  408. /// @todo Only invoke OnContact() and Touch() once per collision pair
  409. // inform other components of collision
  410. if ( GetBot()->ShouldTouch( pTrace->m_pEnt ) )
  411. {
  412. GetBot()->OnContact( pTrace->m_pEnt, pTrace );
  413. }
  414. INextBot *them = dynamic_cast< INextBot * >( pTrace->m_pEnt );
  415. if ( them && them->ShouldTouch( m_nextBot ) )
  416. {
  417. /// @todo construct mirror of trace
  418. them->OnContact( m_nextBot );
  419. }
  420. else
  421. {
  422. pTrace->m_pEnt->Touch( GetBot()->GetEntity() );
  423. }
  424. return true;
  425. }
  426. //----------------------------------------------------------------------------------------------------------
  427. Vector NextBotGroundLocomotion::ResolveCollision( const Vector &from, const Vector &to, int recursionLimit )
  428. {
  429. VPROF_BUDGET( "NextBotGroundLocomotion::ResolveCollision", "NextBotExpensive" );
  430. IBody *body = GetBot()->GetBodyInterface();
  431. if ( body == NULL || recursionLimit < 0 )
  432. {
  433. Assert( !m_bRecomputePostureOnCollision );
  434. return to;
  435. }
  436. // Only bother to recompute posture if we're currently standing or crouching
  437. if ( m_bRecomputePostureOnCollision )
  438. {
  439. if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
  440. {
  441. m_bRecomputePostureOnCollision = false;
  442. }
  443. }
  444. // get bounding limits, ignoring step-upable height
  445. bool bPerformCrouchTest = false;
  446. Vector mins;
  447. Vector maxs;
  448. if ( m_isUsingFullFeetTrace )
  449. {
  450. mins = body->GetHullMins();
  451. }
  452. else
  453. {
  454. mins = body->GetHullMins() + Vector( 0, 0, GetStepHeight() );
  455. }
  456. if ( !m_bRecomputePostureOnCollision )
  457. {
  458. maxs = body->GetHullMaxs();
  459. if ( mins.z >= maxs.z )
  460. {
  461. // if mins.z is greater than maxs.z, the engine will Assert
  462. // in UTIL_TraceHull, and it won't work as advertised.
  463. mins.z = maxs.z - 2.0f;
  464. }
  465. }
  466. else
  467. {
  468. const float halfSize = body->GetHullWidth() / 2.0f;
  469. maxs.Init( halfSize, halfSize, body->GetStandHullHeight() );
  470. bPerformCrouchTest = true;
  471. }
  472. trace_t trace;
  473. Vector desiredGoal = to;
  474. Vector resolvedGoal;
  475. IBody::PostureType nPosture = IBody::STAND;
  476. while( true )
  477. {
  478. bool bCollided = DetectCollision( &trace, recursionLimit, from, desiredGoal, mins, maxs );
  479. if ( !bCollided )
  480. {
  481. resolvedGoal = desiredGoal;
  482. break;
  483. }
  484. // If we hit really close to our target, then stop
  485. if ( !trace.startsolid && desiredGoal.DistToSqr( trace.endpos ) < 1.0f )
  486. {
  487. resolvedGoal = trace.endpos;
  488. break;
  489. }
  490. // Check for crouch test, if it's necessary
  491. // Don't bother about checking for crouch if we hit an actor
  492. // Also don't bother checking for crouch if we hit a plane that pushes us upwards
  493. if ( bPerformCrouchTest )
  494. {
  495. // Don't do this work twice
  496. bPerformCrouchTest = false;
  497. nPosture = body->GetDesiredPosture();
  498. if ( !trace.m_pEnt->MyNextBotPointer() && !trace.m_pEnt->IsPlayer() )
  499. {
  500. // Here, our standing trace hit the world or something non-breakable
  501. // If we're not currently crouching, then see if we could travel
  502. // the entire distance if we were crouched
  503. if ( nPosture != IBody::CROUCH )
  504. {
  505. trace_t crouchTrace;
  506. NextBotTraversableTraceFilter crouchFilter( GetBot(), ILocomotion::IMMEDIATELY );
  507. Vector vecCrouchMax( maxs.x, maxs.y, body->GetCrouchHullHeight() );
  508. TraceHull( from, desiredGoal, mins, vecCrouchMax, body->GetSolidMask(), &crouchFilter, &crouchTrace );
  509. if ( crouchTrace.fraction >= 1.0f && !crouchTrace.startsolid )
  510. {
  511. nPosture = IBody::CROUCH;
  512. }
  513. }
  514. }
  515. else if ( nPosture == IBody::CROUCH )
  516. {
  517. // Here, our standing trace hit an actor
  518. // NOTE: This test occurs almost never, based on my tests
  519. // Converts from crouch to stand in the case where the player
  520. // is currently crouching, *and* his first trace (with the standing hull)
  521. // hits an actor *and* if he didn't hit that actor, he could have
  522. // moved standing the entire way to his desired endpoint
  523. trace_t standTrace;
  524. NextBotTraversableTraceFilter standFilter( GetBot(), ILocomotion::IMMEDIATELY );
  525. TraceHull( from, desiredGoal, mins, maxs, body->GetSolidMask(), &standFilter, &standTrace );
  526. if ( standTrace.fraction >= 1.0f && !standTrace.startsolid )
  527. {
  528. nPosture = IBody::STAND;
  529. }
  530. }
  531. // Our first trace was based on the standing hull.
  532. // If we need be crouched, the trace was bogus; we need to do another
  533. if ( nPosture == IBody::CROUCH )
  534. {
  535. maxs.z = body->GetCrouchHullHeight();
  536. continue;
  537. }
  538. }
  539. if ( trace.startsolid )
  540. {
  541. // stuck inside solid; don't move
  542. if ( trace.m_pEnt && !trace.m_pEnt->IsWorld() )
  543. {
  544. // only ignore physics props that are not doors
  545. if ( dynamic_cast< CPhysicsProp * >( trace.m_pEnt ) != NULL && dynamic_cast< CBasePropDoor * >( trace.m_pEnt ) == NULL )
  546. {
  547. IPhysicsObject *physics = trace.m_pEnt->VPhysicsGetObject();
  548. if ( physics && physics->IsMoveable() )
  549. {
  550. // we've intersected a (likely moving) physics prop - ignore it for awhile so we can move out of it
  551. m_ignorePhysicsProp = trace.m_pEnt;
  552. m_ignorePhysicsPropTimer.Start( 1.0f );
  553. }
  554. }
  555. }
  556. // return to last known non-interpenetrating position
  557. resolvedGoal = m_lastValidPos;
  558. break;
  559. }
  560. if ( --recursionLimit <= 0 )
  561. {
  562. // reached recursion limit, no more adjusting allowed
  563. resolvedGoal = trace.endpos;
  564. break;
  565. }
  566. // never slide downwards/concave to avoid getting stuck in the ground
  567. if ( trace.plane.normal.z < 0.0f )
  568. {
  569. trace.plane.normal.z = 0.0f;
  570. trace.plane.normal.NormalizeInPlace();
  571. }
  572. // slide off of surface we hit
  573. Vector fullMove = desiredGoal - from;
  574. Vector leftToMove = fullMove * ( 1.0f - trace.fraction );
  575. // obey climbing slope limit
  576. if ( !body->HasActivityType( IBody::MOTION_CONTROLLED_Z ) &&
  577. trace.plane.normal.z < GetTraversableSlopeLimit() &&
  578. fullMove.z > 0.0f )
  579. {
  580. fullMove.z = 0.0f;
  581. trace.plane.normal.z = 0.0f;
  582. trace.plane.normal.NormalizeInPlace();
  583. }
  584. float blocked = DotProduct( trace.plane.normal, leftToMove );
  585. Vector unconstrained = fullMove - blocked * trace.plane.normal;
  586. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  587. {
  588. NDebugOverlay::Line( trace.endpos,
  589. trace.endpos + 20.0f * trace.plane.normal,
  590. 255, 0, 150, true, 15.0f );
  591. }
  592. // check for collisions along remainder of move
  593. // But don't bother if we're not going to deflect much
  594. Vector remainingMove = from + unconstrained;
  595. if ( remainingMove.DistToSqr( trace.endpos ) < 1.0f )
  596. {
  597. resolvedGoal = trace.endpos;
  598. break;
  599. }
  600. desiredGoal = remainingMove;
  601. }
  602. if ( !trace.startsolid )
  603. {
  604. m_lastValidPos = resolvedGoal;
  605. }
  606. if ( m_bRecomputePostureOnCollision )
  607. {
  608. m_bRecomputePostureOnCollision = false;
  609. if ( !body->IsActualPosture( nPosture ) )
  610. {
  611. body->SetDesiredPosture( nPosture );
  612. }
  613. }
  614. return resolvedGoal;
  615. }
  616. //--------------------------------------------------------------------------------------------------------
  617. /**
  618. * Collect the closest actors
  619. */
  620. class ClosestActorsScan
  621. {
  622. public:
  623. ClosestActorsScan( const Vector &spot, int team, float maxRange = 0.0f, CBaseCombatCharacter *ignore = NULL )
  624. {
  625. m_spot = spot;
  626. m_team = team;
  627. m_close = NULL;
  628. if ( maxRange > 0.0f )
  629. {
  630. m_closeRangeSq = maxRange * maxRange;
  631. }
  632. else
  633. {
  634. m_closeRangeSq = 999999999.9f;
  635. }
  636. m_ignore = ignore;
  637. }
  638. bool operator() ( CBaseCombatCharacter *actor )
  639. {
  640. if (actor == m_ignore)
  641. return true;
  642. if (actor->IsAlive() && (m_team == TEAM_ANY || actor->GetTeamNumber() == m_team))
  643. {
  644. Vector to = actor->WorldSpaceCenter() - m_spot;
  645. float rangeSq = to.LengthSqr();
  646. if (rangeSq < m_closeRangeSq)
  647. {
  648. m_closeRangeSq = rangeSq;
  649. m_close = actor;
  650. }
  651. }
  652. return true;
  653. }
  654. CBaseCombatCharacter *GetActor( void ) const
  655. {
  656. return m_close;
  657. }
  658. bool IsCloserThan( float range )
  659. {
  660. return (m_closeRangeSq < (range * range));
  661. }
  662. bool IsFartherThan( float range )
  663. {
  664. return (m_closeRangeSq > (range * range));
  665. }
  666. Vector m_spot;
  667. int m_team;
  668. CBaseCombatCharacter *m_close;
  669. float m_closeRangeSq;
  670. CBaseCombatCharacter *m_ignore;
  671. };
  672. #ifdef SKIPME
  673. //----------------------------------------------------------------------------------------------------------
  674. /**
  675. * Push away zombies that are interpenetrating
  676. */
  677. Vector NextBotGroundLocomotion::ResolveZombieCollisions( const Vector &pos )
  678. {
  679. Vector adjustedNewPos = pos;
  680. Infected *me = m_nextBot->MyInfectedPointer();
  681. const float hullWidth = me->GetBodyInterface()->GetHullWidth();
  682. // only avoid if we're actually trying to move somewhere, and are enraged
  683. if ( me != NULL && !IsUsingLadder() && !IsClimbingOrJumping() && IsOnGround() && m_nextBot->IsAlive() && IsAttemptingToMove() /*&& GetBot()->GetBodyInterface()->IsArousal( IBody::INTENSE )*/ )
  684. {
  685. VPROF_BUDGET( "NextBotGroundLocomotion::ResolveZombieCollisions", "NextBot" );
  686. const CUtlVector< CHandle< Infected > > &neighbors = me->GetNeighbors();
  687. Vector avoid = vec3_origin;
  688. float avoidWeight = 0.0f;
  689. FOR_EACH_VEC( neighbors, it )
  690. {
  691. Infected *them = neighbors[ it ];
  692. if ( them )
  693. {
  694. Vector toThem = them->GetAbsOrigin() - me->GetAbsOrigin();
  695. toThem.z = 0.0f;
  696. float range = toThem.NormalizeInPlace();
  697. if ( range < hullWidth )
  698. {
  699. // these two infected are in contact
  700. me->Touch( them );
  701. // move out of contact
  702. float penetration = hullWidth - range;
  703. float weight = 1.0f + ( 2.0f * penetration/hullWidth );
  704. avoid += -weight * toThem;
  705. avoidWeight += weight;
  706. }
  707. }
  708. }
  709. if ( avoidWeight > 0.0f )
  710. {
  711. adjustedNewPos += 3.0f * ( avoid / avoidWeight );
  712. }
  713. }
  714. return adjustedNewPos;
  715. }
  716. #endif // _DEBUG
  717. //----------------------------------------------------------------------------------------------------------
  718. /**
  719. * Move to newPos, resolving any collisions along the way
  720. */
  721. void NextBotGroundLocomotion::UpdatePosition( const Vector &newPos )
  722. {
  723. VPROF_BUDGET( "NextBotGroundLocomotion::UpdatePosition", "NextBot" );
  724. if ( NextBotStop.GetBool() || (m_nextBot->GetFlags() & FL_FROZEN) != 0 || newPos == m_nextBot->GetPosition() )
  725. {
  726. return;
  727. }
  728. // avoid very nearby Actors to simulate "mushy" collisions between actors in contact with each other
  729. //Vector adjustedNewPos = ResolveZombieCollisions( newPos );
  730. Vector adjustedNewPos = newPos;
  731. // check for collisions during move and resolve them
  732. const int recursionLimit = 3;
  733. Vector safePos = ResolveCollision( m_nextBot->GetPosition(), adjustedNewPos, recursionLimit );
  734. // set the bot's position
  735. if ( GetBot()->GetIntentionInterface()->IsPositionAllowed( GetBot(), safePos ) != ANSWER_NO )
  736. {
  737. m_nextBot->SetPosition( safePos );
  738. }
  739. }
  740. //----------------------------------------------------------------------------------------------------------
  741. /**
  742. * Prevent bot from sliding through floor, and snap to the ground if we're very near it
  743. */
  744. void NextBotGroundLocomotion::UpdateGroundConstraint( void )
  745. {
  746. VPROF_BUDGET( "NextBotGroundLocomotion::UpdateGroundConstraint", "NextBotExpensive" );
  747. // if we're up on the upward arc of our jump, don't interfere by snapping to ground
  748. // don't do ground constraint if we're climbing a ladder
  749. if ( DidJustJump() || IsAscendingOrDescendingLadder() )
  750. {
  751. m_isUsingFullFeetTrace = false;
  752. return;
  753. }
  754. IBody *body = GetBot()->GetBodyInterface();
  755. if ( body == NULL )
  756. {
  757. return;
  758. }
  759. float halfWidth = body->GetHullWidth()/2.0f;
  760. // since we only care about ground collisions, keep hull short to avoid issues with low ceilings
  761. /// @TODO: We need to also check actual hull height to avoid interpenetrating the world
  762. float hullHeight = GetStepHeight();
  763. // always need tolerance even when jumping/falling to make sure we detect ground penetration
  764. // must be at least step height to avoid 'falling' down stairs
  765. const float stickToGroundTolerance = GetStepHeight() + 0.01f;
  766. trace_t ground;
  767. NextBotTraceFilterIgnoreActors filter( m_nextBot, body->GetCollisionGroup() );
  768. TraceHull( m_nextBot->GetPosition() + Vector( 0, 0, GetStepHeight() + 0.001f ),
  769. m_nextBot->GetPosition() + Vector( 0, 0, -stickToGroundTolerance ),
  770. Vector( -halfWidth, -halfWidth, 0 ),
  771. Vector( halfWidth, halfWidth, hullHeight ),
  772. body->GetSolidMask(), &filter, &ground );
  773. if ( ground.startsolid )
  774. {
  775. // we're inside the ground - bad news
  776. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) && !( gpGlobals->framecount % 60 ) )
  777. {
  778. DevMsg( "%3.2f: Inside ground, ( %.0f, %.0f, %.0f )\n", gpGlobals->curtime, m_nextBot->GetPosition().x, m_nextBot->GetPosition().y, m_nextBot->GetPosition().z );
  779. }
  780. return;
  781. }
  782. if ( ground.fraction < 1.0f )
  783. {
  784. // there is ground below us
  785. m_groundNormal = ground.plane.normal;
  786. m_isUsingFullFeetTrace = false;
  787. // zero velocity normal to the ground
  788. float normalVel = DotProduct( m_groundNormal, m_velocity );
  789. m_velocity -= normalVel * m_groundNormal;
  790. // check slope limit
  791. if ( ground.plane.normal.z < GetTraversableSlopeLimit() )
  792. {
  793. // too steep to stand here
  794. // too steep to be ground - treat it like a wall hit
  795. if ( ( m_velocity.x * ground.plane.normal.x + m_velocity.y * ground.plane.normal.y ) <= 0.0f )
  796. {
  797. GetBot()->OnContact( ground.m_pEnt, &ground );
  798. }
  799. // we're contacting some kind of ground
  800. // zero accelerations normal to the ground
  801. float normalAccel = DotProduct( m_groundNormal, m_acceleration );
  802. m_acceleration -= normalAccel * m_groundNormal;
  803. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  804. {
  805. DevMsg( "%3.2f: NextBotGroundLocomotion - Too steep to stand here\n", gpGlobals->curtime );
  806. NDebugOverlay::Line( GetFeet(), GetFeet() + 20.0f * ground.plane.normal, 255, 150, 0, true, 5.0f );
  807. }
  808. // clear out upward velocity so we don't walk up lightpoles
  809. m_velocity.z = MIN( 0, m_velocity.z );
  810. m_acceleration.z = MIN( 0, m_acceleration.z );
  811. return;
  812. }
  813. // inform other components of collision if we didn't land on the 'world'
  814. if ( ground.m_pEnt && !ground.m_pEnt->IsWorld() )
  815. {
  816. GetBot()->OnContact( ground.m_pEnt, &ground );
  817. }
  818. // snap us to the ground
  819. m_nextBot->SetPosition( ground.endpos );
  820. if ( !IsOnGround() )
  821. {
  822. // just landed
  823. m_nextBot->SetGroundEntity( ground.m_pEnt );
  824. m_ground = ground.m_pEnt;
  825. // landing stops any jump in progress
  826. m_isJumping = false;
  827. m_isJumpingAcrossGap = false;
  828. GetBot()->OnLandOnGround( ground.m_pEnt );
  829. }
  830. }
  831. else
  832. {
  833. // not on the ground
  834. if ( IsOnGround() )
  835. {
  836. GetBot()->OnLeaveGround( m_nextBot->GetGroundEntity() );
  837. if ( !IsClimbingUpToLedge() && !IsJumpingAcrossGap() )
  838. {
  839. m_isUsingFullFeetTrace = true; // We're in the air and there's space below us, so use the full trace
  840. m_acceleration.z -= GetGravity(); // start our gravity now
  841. }
  842. }
  843. }
  844. }
  845. //----------------------------------------------------------------------------------------------------------
  846. /*
  847. void NextBotGroundLocomotion::StandUp( void )
  848. {
  849. // make sure there is room to stand
  850. trace_t result;
  851. const float halfSize = GetHullWidth()/3.0f;
  852. Vector standHullMin( -halfSize, -halfSize, GetStepHeight() + 0.1f );
  853. Vector standHullMax( halfSize, halfSize, GetStandHullHeight() );
  854. TraceHull( GetFeet(), GetFeet(), standHullMin, standHullMax, MASK_NPCSOLID, m_nextBot, MASK_DEFAULTPLAYERSOLID, &result );
  855. if ( result.fraction >= 1.0f && !result.startsolid )
  856. {
  857. m_isCrouching = false;
  858. }
  859. }
  860. */
  861. //----------------------------------------------------------------------------------------------------------
  862. /**
  863. * Initiate a climb to an adjacent high ledge
  864. */
  865. bool NextBotGroundLocomotion::ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle )
  866. {
  867. return false;
  868. }
  869. //----------------------------------------------------------------------------------------------------------
  870. /**
  871. * Initiate a jump across an empty volume of space to far side
  872. */
  873. void NextBotGroundLocomotion::JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward )
  874. {
  875. // can only jump if we're on the ground
  876. if ( !IsOnGround() )
  877. {
  878. return;
  879. }
  880. IBody *body = GetBot()->GetBodyInterface();
  881. if ( !body->StartActivity( ACT_JUMP ) )
  882. {
  883. // body can't jump right now
  884. return;
  885. }
  886. // scale impulse to land on target
  887. Vector toGoal = landingGoal - GetFeet();
  888. // equation doesn't work if we're jumping upwards
  889. float height = toGoal.z;
  890. toGoal.z = 0.0f;
  891. float range = toGoal.NormalizeInPlace();
  892. // jump out at 45 degree angle
  893. const float cos45 = 0.7071f;
  894. // avoid division by zero
  895. if ( height > 0.9f * range )
  896. {
  897. height = 0.9f * range;
  898. }
  899. // ballistic equation to find initial velocity assuming 45 degree inclination and landing at give range and height
  900. float launchVel = ( range / cos45 ) / sqrt( ( 2.0f * ( range - height ) ) / GetGravity() );
  901. Vector up( 0, 0, 1 );
  902. Vector ahead = up + toGoal;
  903. ahead.NormalizeInPlace();
  904. //m_velocity = cos45 * launchVel * ahead;
  905. m_velocity = launchVel * ahead;
  906. m_acceleration = vec3_origin;
  907. m_isJumping = true;
  908. m_isJumpingAcrossGap = true;
  909. m_isClimbingUpToLedge = false;
  910. GetBot()->OnLeaveGround( m_nextBot->GetGroundEntity() );
  911. }
  912. //----------------------------------------------------------------------------------------------------------
  913. /**
  914. * Initiate a simple undirected jump in the air
  915. */
  916. void NextBotGroundLocomotion::Jump( void )
  917. {
  918. // can only jump if we're on the ground
  919. if ( !IsOnGround() )
  920. {
  921. return;
  922. }
  923. IBody *body = GetBot()->GetBodyInterface();
  924. if ( !body->StartActivity( ACT_JUMP ) )
  925. {
  926. // body can't jump right now
  927. return;
  928. }
  929. // jump straight up
  930. m_velocity.z = sqrt( 2.0f * GetGravity() * GetMaxJumpHeight() );
  931. m_isJumping = true;
  932. m_isClimbingUpToLedge = false;
  933. GetBot()->OnLeaveGround( m_nextBot->GetGroundEntity() );
  934. }
  935. //----------------------------------------------------------------------------------------------------------
  936. /**
  937. * Set movement speed to running
  938. */
  939. void NextBotGroundLocomotion::Run( void )
  940. {
  941. m_desiredSpeed = GetRunSpeed();
  942. }
  943. //----------------------------------------------------------------------------------------------------------
  944. /**
  945. * Set movement speed to walking
  946. */
  947. void NextBotGroundLocomotion::Walk( void )
  948. {
  949. m_desiredSpeed = GetWalkSpeed();
  950. }
  951. //----------------------------------------------------------------------------------------------------------
  952. /**
  953. * Set movement speed to stopeed
  954. */
  955. void NextBotGroundLocomotion::Stop( void )
  956. {
  957. m_desiredSpeed = 0.0f;
  958. }
  959. //----------------------------------------------------------------------------------------------------------
  960. /**
  961. * Return true if standing on something
  962. */
  963. bool NextBotGroundLocomotion::IsOnGround( void ) const
  964. {
  965. return (m_nextBot->GetGroundEntity() != NULL);
  966. }
  967. //----------------------------------------------------------------------------------------------------------
  968. /**
  969. * Invoked when bot leaves ground for any reason
  970. */
  971. void NextBotGroundLocomotion::OnLeaveGround( CBaseEntity *ground )
  972. {
  973. m_nextBot->SetGroundEntity( NULL );
  974. m_ground = NULL;
  975. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  976. {
  977. DevMsg( "%3.2f: NextBotGroundLocomotion::OnLeaveGround\n", gpGlobals->curtime );
  978. }
  979. }
  980. //----------------------------------------------------------------------------------------------------------
  981. /**
  982. * Invoked when bot lands on the ground after being in the air
  983. */
  984. void NextBotGroundLocomotion::OnLandOnGround( CBaseEntity *ground )
  985. {
  986. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  987. {
  988. DevMsg( "%3.2f: NextBotGroundLocomotion::GetBot()->OnLandOnGround\n", gpGlobals->curtime );
  989. }
  990. }
  991. //----------------------------------------------------------------------------------------------------------
  992. /**
  993. * Get maximum speed bot can reach, regardless of desired speed
  994. */
  995. float NextBotGroundLocomotion::GetSpeedLimit( void ) const
  996. {
  997. // if we're crouched, move at reduced speed
  998. if ( !GetBot()->GetBodyInterface()->IsActualPosture( IBody::STAND ) )
  999. {
  1000. return 0.75f * GetRunSpeed();
  1001. }
  1002. // no limit
  1003. return 99999999.9f;
  1004. }
  1005. //----------------------------------------------------------------------------------------------------------
  1006. /**
  1007. * Climb the given ladder to the top and dismount
  1008. */
  1009. void NextBotGroundLocomotion::ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
  1010. {
  1011. // if we're already climbing this ladder, don't restart
  1012. if ( m_ladder == ladder && m_isGoingUpLadder )
  1013. {
  1014. return;
  1015. }
  1016. m_ladder = ladder;
  1017. m_ladderDismountGoal = dismountGoal;
  1018. m_isGoingUpLadder = true;
  1019. IBody *body = GetBot()->GetBodyInterface();
  1020. if ( body )
  1021. {
  1022. // line them up to climb in XY
  1023. Vector mountSpot = m_ladder->m_bottom + m_ladder->GetNormal() * (0.75f * body->GetHullWidth());
  1024. mountSpot.z = GetBot()->GetPosition().z;
  1025. UpdatePosition( mountSpot );
  1026. body->StartActivity( ACT_CLIMB_UP, IBody::MOTION_CONTROLLED_Z );
  1027. }
  1028. }
  1029. //----------------------------------------------------------------------------------------------------------
  1030. /**
  1031. * Descend the given ladder to the bottom and dismount
  1032. */
  1033. void NextBotGroundLocomotion::DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
  1034. {
  1035. // if we're already descending this ladder, don't restart
  1036. if ( m_ladder == ladder && !m_isGoingUpLadder )
  1037. {
  1038. return;
  1039. }
  1040. m_ladder = ladder;
  1041. m_ladderDismountGoal = dismountGoal;
  1042. m_isGoingUpLadder = false;
  1043. IBody *body = GetBot()->GetBodyInterface();
  1044. if ( body )
  1045. {
  1046. // line them up to climb in XY
  1047. Vector mountSpot = m_ladder->m_top + m_ladder->GetNormal() * (0.75f * body->GetHullWidth());
  1048. mountSpot.z = GetBot()->GetPosition().z;
  1049. UpdatePosition( mountSpot );
  1050. float ladderYaw = UTIL_VecToYaw( -m_ladder->GetNormal() );
  1051. QAngle angles = m_nextBot->GetLocalAngles();
  1052. angles.y = ladderYaw;
  1053. m_nextBot->SetLocalAngles( angles );
  1054. body->StartActivity( ACT_CLIMB_DOWN, IBody::MOTION_CONTROLLED_Z );
  1055. }
  1056. }
  1057. //----------------------------------------------------------------------------------------------------------
  1058. bool NextBotGroundLocomotion::IsUsingLadder( void ) const
  1059. {
  1060. return ( m_ladder != NULL );
  1061. }
  1062. //----------------------------------------------------------------------------------------------------------
  1063. /**
  1064. * We are actually on the ladder right now, either climbing up or down
  1065. */
  1066. bool NextBotGroundLocomotion::IsAscendingOrDescendingLadder( void ) const
  1067. {
  1068. return IsUsingLadder();
  1069. }
  1070. //----------------------------------------------------------------------------------------------------------
  1071. /**
  1072. * Return position of "feet" - point below centroid of bot at feet level
  1073. */
  1074. const Vector &NextBotGroundLocomotion::GetFeet( void ) const
  1075. {
  1076. return m_nextBot->GetPosition();
  1077. }
  1078. //----------------------------------------------------------------------------------------------------------
  1079. const Vector & NextBotGroundLocomotion::GetAcceleration( void ) const
  1080. {
  1081. return m_acceleration;
  1082. }
  1083. //----------------------------------------------------------------------------------------------------------
  1084. void NextBotGroundLocomotion::SetAcceleration( const Vector &accel )
  1085. {
  1086. m_acceleration = accel;
  1087. }
  1088. //----------------------------------------------------------------------------------------------------------
  1089. void NextBotGroundLocomotion::SetVelocity( const Vector &vel )
  1090. {
  1091. m_velocity = vel;
  1092. }
  1093. //----------------------------------------------------------------------------------------------------------
  1094. /**
  1095. * Return current world space velocity
  1096. */
  1097. const Vector &NextBotGroundLocomotion::GetVelocity( void ) const
  1098. {
  1099. return m_velocity;
  1100. }
  1101. //----------------------------------------------------------------------------------------------------------
  1102. /**
  1103. * Invoked when an bot reaches its MoveTo goal
  1104. */
  1105. void NextBotGroundLocomotion::OnMoveToSuccess( const Path *path )
  1106. {
  1107. // stop
  1108. m_velocity = vec3_origin;
  1109. m_acceleration = vec3_origin;
  1110. }
  1111. //----------------------------------------------------------------------------------------------------------
  1112. /**
  1113. * Invoked when an bot fails to reach a MoveTo goal
  1114. */
  1115. void NextBotGroundLocomotion::OnMoveToFailure( const Path *path, MoveToFailureType reason )
  1116. {
  1117. // stop
  1118. m_velocity = vec3_origin;
  1119. m_acceleration = vec3_origin;
  1120. }
  1121. //----------------------------------------------------------------------------------------------------------
  1122. bool NextBotGroundLocomotion::DidJustJump( void ) const
  1123. {
  1124. return IsClimbingOrJumping() && (m_nextBot->GetAbsVelocity().z > 0.0f);
  1125. }
  1126. //----------------------------------------------------------------------------------------------------------
  1127. /**
  1128. * Rotate body to face towards "target"
  1129. */
  1130. void NextBotGroundLocomotion::FaceTowards( const Vector &target )
  1131. {
  1132. const float deltaT = GetUpdateInterval();
  1133. QAngle angles = m_nextBot->GetLocalAngles();
  1134. float desiredYaw = UTIL_VecToYaw( target - GetFeet() );
  1135. float angleDiff = UTIL_AngleDiff( desiredYaw, angles.y );
  1136. float deltaYaw = GetMaxYawRate() * deltaT;
  1137. if (angleDiff < -deltaYaw)
  1138. {
  1139. angles.y -= deltaYaw;
  1140. }
  1141. else if (angleDiff > deltaYaw)
  1142. {
  1143. angles.y += deltaYaw;
  1144. }
  1145. else
  1146. {
  1147. angles.y += angleDiff;
  1148. }
  1149. m_nextBot->SetLocalAngles( angles );
  1150. }