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.

1438 lines
38 KiB

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