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.

1909 lines
52 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "movevars_shared.h"
  8. #include "ai_blended_movement.h"
  9. #include "ai_route.h"
  10. #include "ai_navigator.h"
  11. #include "ai_moveprobe.h"
  12. #include "keyvalues.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. //-----------------------------------------------------------------------------
  16. //
  17. // class CAI_BlendedMotor
  18. //
  19. BEGIN_SIMPLE_DATADESC( CAI_BlendedMotor )
  20. // DEFINE_FIELD( m_bDeceleratingToGoal, FIELD_BOOLEAN ),
  21. // DEFINE_FIELD( m_iPrimaryLayer, FIELD_INTEGER ),
  22. // DEFINE_FIELD( m_iSecondaryLayer, FIELD_INTEGER ),
  23. // DEFINE_FIELD( m_nPrimarySequence, FIELD_INTEGER ),
  24. // DEFINE_FIELD( m_nSecondarySequence, FIELD_INTEGER ),
  25. // DEFINE_FIELD( m_flSecondaryWeight, FIELD_FLOAT ),
  26. // DEFINE_CUSTOM_FIELD( m_nSavedGoalActivity, ActivityDataOps() ),
  27. // DEFINE_CUSTOM_FIELD( m_nSavedTranslatedGoalActivity, ActivityDataOps() ),
  28. // DEFINE_FIELD( m_nGoalSequence, FIELD_INTEGER ),
  29. // DEFINE_FIELD( m_nPrevMovementSequence, FIELD_INTEGER ),
  30. // DEFINE_FIELD( m_nInteriorSequence, FIELD_INTEGER ),
  31. // DEFINE_FIELD( m_flCurrRate, FIELD_FLOAT ),
  32. // DEFINE_FIELD( m_flStartCycle, FIELD_FLOAT ),
  33. // m_scriptMove
  34. // m_scriptTurn
  35. // DEFINE_FIELD( m_flNextTurnGesture, FIELD_TIME ),
  36. // DEFINE_FIELD( m_prevYaw, FIELD_FLOAT ),
  37. // DEFINE_FIELD( m_doTurn, FIELD_FLOAT ),
  38. // DEFINE_FIELD( m_doLeft, FIELD_FLOAT ),
  39. // DEFINE_FIELD( m_doRight, FIELD_FLOAT ),
  40. // DEFINE_FIELD( m_flNextTurnAct, FIELD_TIME ),
  41. // DEFINE_FIELD( m_flPredictiveSpeedAdjust, FIELD_FLOAT ),
  42. // DEFINE_FIELD( m_flReactiveSpeedAdjust, FIELD_FLOAT ),
  43. // DEFINE_FIELD( m_vecPrevOrigin1, FIELD_POSITION ),
  44. // DEFINE_FIELD( m_vecPrevOrigin2, FIELD_POSITION ),
  45. END_DATADESC()
  46. //-------------------------------------
  47. void CAI_BlendedMotor::ResetMoveCalculations()
  48. {
  49. BaseClass::ResetMoveCalculations();
  50. m_scriptMove.RemoveAll();
  51. m_scriptTurn.RemoveAll();
  52. }
  53. //-------------------------------------
  54. void CAI_BlendedMotor::MoveStart()
  55. {
  56. AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveStart);
  57. if (m_nPrimarySequence == -1)
  58. {
  59. m_nPrimarySequence = GetSequence();
  60. m_flStartCycle = GetCycle();
  61. m_flCurrRate = 0.4;
  62. // Assert( !GetOuter()->HasMovement( m_nStartSequence ) );
  63. m_nSecondarySequence = -1;
  64. m_iPrimaryLayer = AddLayeredSequence( m_nPrimarySequence, 0 );
  65. SetLayerWeight( m_iPrimaryLayer, 0.0 );
  66. SetLayerPlaybackRate( m_iPrimaryLayer, 0.0 );
  67. SetLayerNoRestore( m_iPrimaryLayer, true );
  68. SetLayerCycle( m_iPrimaryLayer, m_flStartCycle, m_flStartCycle );
  69. SetLayerNoEvents( m_iPrimaryLayer, true );
  70. m_flSecondaryWeight = 0.0;
  71. }
  72. else
  73. {
  74. // suspect that MoveStop() wasn't called when the previous route finished
  75. // Assert( 0 );
  76. }
  77. if (m_nGoalSequence == ACT_INVALID)
  78. {
  79. ResetGoalSequence();
  80. }
  81. m_vecPrevOrigin2 = GetAbsOrigin();
  82. m_vecPrevOrigin1 = GetAbsOrigin();
  83. m_bDeceleratingToGoal = false;
  84. }
  85. //-----------------------------------------------------------------------------
  86. // Purpose:
  87. //-----------------------------------------------------------------------------
  88. void CAI_BlendedMotor::ResetGoalSequence( void )
  89. {
  90. m_nSavedGoalActivity = GetNavigator()->GetArrivalActivity( );
  91. if (m_nSavedGoalActivity == ACT_INVALID)
  92. {
  93. m_nSavedGoalActivity = GetOuter()->GetStoppedActivity();
  94. }
  95. m_nSavedTranslatedGoalActivity = GetOuter()->NPC_TranslateActivity( m_nSavedGoalActivity );
  96. m_nGoalSequence = GetNavigator()->GetArrivalSequence( m_nPrimarySequence );
  97. // Msg("Start %s end %s\n", GetOuter()->GetSequenceName( m_nPrimarySequence ), GetOuter()->GetSequenceName( m_nGoalSequence ) );
  98. m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
  99. Assert( m_nGoalSequence != ACT_INVALID );
  100. }
  101. //-----------------------------------------------------------------------------
  102. // Purpose:
  103. //-----------------------------------------------------------------------------
  104. void CAI_BlendedMotor::MoveStop()
  105. {
  106. AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveStop);
  107. CAI_Motor::MoveStop();
  108. if (m_iPrimaryLayer != -1)
  109. {
  110. RemoveLayer( m_iPrimaryLayer, 0.2, 0.1 );
  111. m_iPrimaryLayer = -1;
  112. }
  113. if (m_iSecondaryLayer != -1)
  114. {
  115. RemoveLayer( m_iSecondaryLayer, 0.2, 0.1 );
  116. m_iSecondaryLayer = -1;
  117. }
  118. m_nPrimarySequence = ACT_INVALID;
  119. m_nSecondarySequence = ACT_INVALID;
  120. m_nPrevMovementSequence = ACT_INVALID;
  121. m_nInteriorSequence = ACT_INVALID;
  122. // int nNextSequence = FindTransitionSequence(GetSequence(), m_nIdealSequence, NULL);
  123. }
  124. void CAI_BlendedMotor::MovePaused()
  125. {
  126. CAI_Motor::MovePaused();
  127. SetMoveScriptAnim( 0.0 );
  128. }
  129. void CAI_BlendedMotor::MoveContinue()
  130. {
  131. AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveContinue);
  132. m_nPrimarySequence = GetInteriorSequence( ACT_INVALID );
  133. m_nGoalSequence = m_nPrimarySequence;
  134. Assert( m_nPrimarySequence != ACT_INVALID );
  135. if (m_nPrimarySequence == ACT_INVALID)
  136. return;
  137. m_flStartCycle = 0.0;
  138. m_iPrimaryLayer = AddLayeredSequence( m_nPrimarySequence, 0 );
  139. SetLayerWeight( m_iPrimaryLayer, 0.0 );
  140. SetLayerPlaybackRate( m_iPrimaryLayer, 0.0 );
  141. SetLayerNoRestore( m_iPrimaryLayer, true );
  142. SetLayerCycle( m_iPrimaryLayer, m_flStartCycle, m_flStartCycle );
  143. SetLayerNoEvents( m_iPrimaryLayer, true );
  144. m_bDeceleratingToGoal = false;
  145. }
  146. //-----------------------------------------------------------------------------
  147. // Purpose: for the MoveInterval, interpolate desired speed, calc actual distance traveled
  148. //-----------------------------------------------------------------------------
  149. float CAI_BlendedMotor::GetMoveScriptDist( float &flNewSpeed )
  150. {
  151. AI_PROFILE_SCOPE(CAI_BlendedMotor_GetMoveScriptDist);
  152. int i;
  153. float flTotalDist = 0;
  154. float t = GetMoveInterval();
  155. Assert( m_scriptMove.Count() > 1);
  156. flNewSpeed = 0;
  157. for (i = 0; i < m_scriptMove.Count()-1; i++)
  158. {
  159. if (t < m_scriptMove[i].flTime)
  160. {
  161. // get new velocity
  162. float a = t / m_scriptMove[i].flTime;
  163. flNewSpeed = m_scriptMove[i].flMaxVelocity * (1 - a) + m_scriptMove[i+1].flMaxVelocity * a;
  164. // get distance traveled over this entry
  165. flTotalDist += (m_scriptMove[i].flMaxVelocity + flNewSpeed) * 0.5 * t;
  166. break;
  167. }
  168. else
  169. {
  170. // used all of entries time, get entries total movement
  171. flNewSpeed = m_scriptMove[i+1].flMaxVelocity;
  172. flTotalDist += m_scriptMove[i].flDist;
  173. t -= m_scriptMove[i].flTime;
  174. }
  175. }
  176. return flTotalDist;
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose: return the total time that the move script covers
  180. //-----------------------------------------------------------------------------
  181. float CAI_BlendedMotor::GetMoveScriptTotalTime()
  182. {
  183. float flDist = GetNavigator()->GetArrivalDistance();
  184. int i = m_scriptMove.Count() - 1;
  185. if (i < 0)
  186. return -1;
  187. while (i > 0 && flDist > 1)
  188. {
  189. flDist -= m_scriptMove[i].flDist;
  190. i--;
  191. }
  192. return m_scriptMove[i].flElapsedTime;
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose: for the MoveInterval, interpolate desired angle
  196. //-----------------------------------------------------------------------------
  197. float CAI_BlendedMotor::GetMoveScriptYaw( void )
  198. {
  199. int i;
  200. // interpolate desired angle
  201. float flNewYaw = GetAbsAngles().y;
  202. float t = GetMoveInterval();
  203. for (i = 0; i < m_scriptTurn.Count()-1; i++)
  204. {
  205. if (t < m_scriptTurn[i].flTime)
  206. {
  207. // get new direction
  208. float a = t / m_scriptTurn[i].flTime;
  209. float deltaYaw = UTIL_AngleDiff( m_scriptTurn[i+1].flYaw, m_scriptTurn[i].flYaw );
  210. flNewYaw = UTIL_AngleMod( m_scriptTurn[i].flYaw + a * deltaYaw );
  211. break;
  212. }
  213. else
  214. {
  215. t -= m_scriptTurn[i].flTime;
  216. }
  217. }
  218. return flNewYaw;
  219. }
  220. //-----------------------------------------------------------------------------
  221. // Purpose: blend in the "idle" or "arrival" animation depending on speed
  222. //-----------------------------------------------------------------------------
  223. void CAI_BlendedMotor::SetMoveScriptAnim( float flNewSpeed )
  224. {
  225. AI_PROFILE_SCOPE(CAI_BlendedMotor_SetMoveScriptAnim);
  226. // don't bother if the npc is dead
  227. if (!GetOuter()->IsAlive())
  228. return;
  229. // insert ideal layers
  230. // FIXME: needs full transitions, as well as starting vs stopping sequences, leaning, etc.
  231. CAI_Navigator *pNavigator = GetNavigator();
  232. SetPlaybackRate( m_flCurrRate );
  233. // calc weight of idle animation layer that suppresses the run animation
  234. float flWeight = 0.0;
  235. if (GetIdealSpeed() > 0.0)
  236. {
  237. flWeight = 1.0 - (flNewSpeed / (GetIdealSpeed() * GetPlaybackRate()));
  238. }
  239. if (flWeight < 0.0)
  240. {
  241. m_flCurrRate = flNewSpeed / GetIdealSpeed();
  242. m_flCurrRate = clamp( m_flCurrRate, 0.0, 1.0 );
  243. SetPlaybackRate( m_flCurrRate );
  244. flWeight = 0.0;
  245. }
  246. // Msg("weight %.3f rate %.3f\n", flWeight, m_flCurrRate );
  247. m_flCurrRate = MIN( m_flCurrRate + (1.0 - m_flCurrRate) * 0.8, 1.0 );
  248. if (m_nSavedGoalActivity == ACT_INVALID)
  249. {
  250. ResetGoalSequence();
  251. }
  252. // detect state change
  253. Activity activity = GetOuter()->NPC_TranslateActivity( m_nSavedGoalActivity );
  254. if ( activity != m_nSavedTranslatedGoalActivity )
  255. {
  256. m_nSavedTranslatedGoalActivity = activity;
  257. m_nInteriorSequence = ACT_INVALID;
  258. m_nGoalSequence = pNavigator->GetArrivalSequence( m_nPrimarySequence );
  259. }
  260. if (m_bDeceleratingToGoal)
  261. {
  262. // find that sequence to play when at goal
  263. m_nGoalSequence = pNavigator->GetArrivalSequence( m_nPrimarySequence );
  264. if (m_nGoalSequence == ACT_INVALID)
  265. {
  266. m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
  267. }
  268. Assert( m_nGoalSequence != ACT_INVALID );
  269. }
  270. if (m_flSecondaryWeight == 1.0 || (m_iSecondaryLayer != -1 && m_nPrimarySequence == m_nSecondarySequence))
  271. {
  272. // secondary layer at full strength last time, delete the primary and shift down
  273. RemoveLayer( m_iPrimaryLayer, 0.0, 0.0 );
  274. m_iPrimaryLayer = m_iSecondaryLayer;
  275. m_nPrimarySequence = m_nSecondarySequence;
  276. m_iSecondaryLayer = -1;
  277. m_nSecondarySequence = ACT_INVALID;
  278. m_flSecondaryWeight = 0.0;
  279. }
  280. // look for transition sequence if needed
  281. if (m_nSecondarySequence == ACT_INVALID)
  282. {
  283. if (!m_bDeceleratingToGoal && m_nGoalSequence != GetInteriorSequence( m_nPrimarySequence ))
  284. {
  285. // strob interior sequence in case it changed
  286. m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
  287. }
  288. if (m_nGoalSequence != ACT_INVALID && m_nPrimarySequence != m_nGoalSequence)
  289. {
  290. // Msg("From %s to %s\n", GetOuter()->GetSequenceName( m_nPrimarySequence ), GetOuter()->GetSequenceName( m_nGoalSequence ) );
  291. m_nSecondarySequence = GetOuter()->FindTransitionSequence(m_nPrimarySequence, m_nGoalSequence, NULL);
  292. if (m_nSecondarySequence == ACT_INVALID)
  293. m_nSecondarySequence = m_nGoalSequence;
  294. }
  295. }
  296. // set blending for
  297. if (m_nSecondarySequence != ACT_INVALID)
  298. {
  299. if (m_iSecondaryLayer == -1)
  300. {
  301. m_iSecondaryLayer = AddLayeredSequence( m_nSecondarySequence, 0 );
  302. SetLayerWeight( m_iSecondaryLayer, 0.0 );
  303. if (m_nSecondarySequence == m_nGoalSequence)
  304. {
  305. SetLayerPlaybackRate( m_iSecondaryLayer, 0.0 );
  306. }
  307. else
  308. {
  309. SetLayerPlaybackRate( m_iSecondaryLayer, 1.0 );
  310. }
  311. SetLayerNoRestore( m_iSecondaryLayer, true );
  312. SetLayerNoEvents( m_iSecondaryLayer, true );
  313. m_flSecondaryWeight = 0.0;
  314. }
  315. m_flSecondaryWeight = MIN( m_flSecondaryWeight + 0.3, 1.0 );
  316. if (m_flSecondaryWeight < 1.0)
  317. {
  318. SetLayerWeight( m_iPrimaryLayer, (flWeight - m_flSecondaryWeight * flWeight) / (1.0f - m_flSecondaryWeight * flWeight) );
  319. SetLayerWeight( m_iSecondaryLayer, flWeight * m_flSecondaryWeight );
  320. }
  321. else
  322. {
  323. SetLayerWeight( m_iPrimaryLayer, 0.0f );
  324. SetLayerWeight( m_iSecondaryLayer, flWeight );
  325. }
  326. }
  327. else
  328. {
  329. // recreate layer if missing
  330. if (m_iPrimaryLayer == -1)
  331. {
  332. MoveContinue();
  333. }
  334. // try to catch a stale layer
  335. if (m_iSecondaryLayer != -1)
  336. {
  337. // secondary layer at full strength last time, delete the primary and shift down
  338. RemoveLayer( m_iSecondaryLayer, 0.0, 0.0 );
  339. m_iSecondaryLayer = -1;
  340. m_nSecondarySequence = ACT_INVALID;
  341. m_flSecondaryWeight = 0.0;
  342. }
  343. // debounce
  344. // flWeight = flWeight * 0.5 + 0.5 * GetOuter()->GetLayerWeight( m_iPrimaryLayer );
  345. SetLayerWeight( m_iPrimaryLayer, flWeight );
  346. }
  347. }
  348. //-----------------------------------------------------------------------------
  349. // Purpose: get the "idle" animation to play as the compliment to the movement animation
  350. //-----------------------------------------------------------------------------
  351. int CAI_BlendedMotor::GetInteriorSequence( int fromSequence )
  352. {
  353. AI_PROFILE_SCOPE(CAI_BlendedMotor_GetInteriorSequence);
  354. // FIXME: add interior activity to path, just like arrival activity.
  355. int sequence = GetNavigator()->GetMovementSequence();
  356. if (m_nInteriorSequence != ACT_INVALID && sequence == m_nPrevMovementSequence)
  357. {
  358. return m_nInteriorSequence;
  359. }
  360. m_nPrevMovementSequence = sequence;
  361. KeyValues *seqKeyValues = GetOuter()->GetSequenceKeyValues( sequence );
  362. KeyValues::AutoDelete autodelete_key( seqKeyValues );
  363. // Msg("sequence %d : %s (%d)\n", sequence, GetOuter()->GetSequenceName( sequence ), seqKeyValues != NULL );
  364. if (seqKeyValues)
  365. {
  366. KeyValues *pkvInterior = seqKeyValues->FindKey("interior");
  367. if (pkvInterior)
  368. {
  369. const char *szActivity = pkvInterior->GetString();
  370. Activity activity = ( Activity )GetOuter()->LookupActivity( szActivity );
  371. if ( activity != ACT_INVALID )
  372. {
  373. m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
  374. }
  375. else
  376. {
  377. activity = (Activity)GetOuter()->GetActivityID( szActivity );
  378. if ( activity != ACT_INVALID )
  379. {
  380. m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
  381. }
  382. }
  383. if (activity == ACT_INVALID || m_nInteriorSequence == ACT_INVALID)
  384. {
  385. m_nInteriorSequence = GetOuter()->LookupSequence( szActivity );
  386. }
  387. }
  388. }
  389. if (m_nInteriorSequence == ACT_INVALID)
  390. {
  391. Activity activity = GetNavigator()->GetMovementActivity();
  392. if (activity == ACT_WALK_AIM || activity == ACT_RUN_AIM)
  393. {
  394. activity = ACT_IDLE_ANGRY;
  395. }
  396. else
  397. {
  398. activity = ACT_IDLE;
  399. }
  400. m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
  401. Assert( m_nInteriorSequence != ACT_INVALID );
  402. }
  403. return m_nInteriorSequence;
  404. }
  405. //-----------------------------------------------------------------------------
  406. // Purpose: Move the npc to the next location on its route.
  407. //-----------------------------------------------------------------------------
  408. AIMotorMoveResult_t CAI_BlendedMotor::MoveGroundExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
  409. {
  410. AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveGroundExecute);
  411. if ( move.curExpectedDist < 0.001 )
  412. {
  413. AIMotorMoveResult_t result = BaseClass::MoveGroundExecute( move, pTraceResult );
  414. // Msg(" BaseClass::MoveGroundExecute() - remaining %.2f\n", GetMoveInterval() );
  415. SetMoveScriptAnim( 0.0 );
  416. return result;
  417. }
  418. BuildMoveScript( move, pTraceResult );
  419. float flNewSpeed = GetCurSpeed();
  420. float flTotalDist = GetMoveScriptDist( flNewSpeed );
  421. Assert( move.maxDist < 0.01 || flTotalDist > 0.0 );
  422. // --------------------------------------------
  423. // turn in the direction of movement
  424. // --------------------------------------------
  425. float flNewYaw = GetMoveScriptYaw( );
  426. // get facing based on movement yaw
  427. AILocalMoveGoal_t move2 = move;
  428. move2.facing = UTIL_YawToVector( flNewYaw );
  429. // turn in the direction needed
  430. MoveFacing( move2 );
  431. // reset actual "sequence" ground speed based current movement sequence, orientation
  432. // FIXME: this should be based on
  433. GetOuter()->m_flGroundSpeed = GetSequenceGroundSpeed( GetSequence());
  434. /*
  435. if (1 || flNewSpeed > GetIdealSpeed())
  436. {
  437. // DevMsg( "%6.2f : Speed %.1f : %.1f (%.1f) : %d\n", gpGlobals->curtime, flNewSpeed, move.maxDist, move.transitionDist, GetOuter()->m_pHintNode != NULL );
  438. // DevMsg( "%6.2f : Speed %.1f : %.1f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed() );
  439. }
  440. */
  441. SetMoveScriptAnim( flNewSpeed );
  442. /*
  443. if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  444. {
  445. DevMsg( "%6.2f : Speed %.1f : %.1f : %.2f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed(), flNewSpeed / GetIdealSpeed() );
  446. }
  447. */
  448. AIMotorMoveResult_t result = MoveGroundExecuteWalk( move, flNewSpeed, flTotalDist, pTraceResult );
  449. return result;
  450. }
  451. AIMotorMoveResult_t CAI_BlendedMotor::MoveFlyExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
  452. {
  453. AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveFlyExecute);
  454. if ( move.curExpectedDist < 0.001 )
  455. return BaseClass::MoveFlyExecute( move, pTraceResult );
  456. BuildMoveScript( move, pTraceResult );
  457. float flNewSpeed = GetCurSpeed();
  458. float flTotalDist = GetMoveScriptDist( flNewSpeed );
  459. Assert( move.maxDist < 0.01 || flTotalDist > 0.0 );
  460. // --------------------------------------------
  461. // turn in the direction of movement
  462. // --------------------------------------------
  463. float flNewYaw = GetMoveScriptYaw( );
  464. // get facing based on movement yaw
  465. AILocalMoveGoal_t move2 = move;
  466. move2.facing = UTIL_YawToVector( flNewYaw );
  467. // turn in the direction needed
  468. MoveFacing( move2 );
  469. GetOuter()->m_flGroundSpeed = GetSequenceGroundSpeed( GetSequence());
  470. SetMoveScriptAnim( flNewSpeed );
  471. // DevMsg( "%6.2f : Speed %.1f : %.1f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed() );
  472. // reset actual "sequence" ground speed based current movement sequence, orientation
  473. // FIXME: the above is redundant with MoveGroundExecute, and the below is a mix of MoveGroundExecuteWalk and MoveFlyExecute
  474. bool bReachingLocalGoal = ( flTotalDist > move.maxDist );
  475. // can I move farther in this interval than I'm supposed to?
  476. if ( bReachingLocalGoal )
  477. {
  478. if ( !(move.flags & AILMG_CONSUME_INTERVAL) )
  479. {
  480. // only use a portion of the time interval
  481. SetMoveInterval( GetMoveInterval() * (1 - move.maxDist / flTotalDist) );
  482. }
  483. else
  484. SetMoveInterval( 0 );
  485. flTotalDist = move.maxDist;
  486. }
  487. else
  488. {
  489. // use all the time
  490. SetMoveInterval( 0 );
  491. }
  492. SetMoveVel( move.dir * flNewSpeed );
  493. // orig
  494. Vector vecStart, vecEnd;
  495. vecStart = GetLocalOrigin();
  496. VectorMA( vecStart, flTotalDist, move.dir, vecEnd );
  497. AIMoveTrace_t moveTrace;
  498. GetMoveProbe()->MoveLimit( NAV_FLY, vecStart, vecEnd, GetOuter()->GetAITraceMask(), NULL, &moveTrace );
  499. if ( pTraceResult )
  500. *pTraceResult = moveTrace;
  501. // Check for total blockage
  502. if (fabs(moveTrace.flDistObstructed - flTotalDist) <= 1e-1)
  503. {
  504. // But if we bumped into our target, then we succeeded!
  505. if ( move.pMoveTarget && (moveTrace.pObstruction == move.pMoveTarget) )
  506. return AIM_PARTIAL_HIT_TARGET;
  507. return AIM_FAILED;
  508. }
  509. // The true argument here causes it to touch all triggers
  510. // in the volume swept from the previous position to the current position
  511. UTIL_SetOrigin(GetOuter(), moveTrace.vEndPosition, true);
  512. return (IsMoveBlocked(moveTrace.fStatus)) ? AIM_PARTIAL_HIT_WORLD : AIM_SUCCESS;
  513. }
  514. float CAI_BlendedMotor::OverrideMaxYawSpeed( Activity activity )
  515. {
  516. // Don't do this is we're locked
  517. if ( IsYawLocked() )
  518. return 0.0f;
  519. switch( activity )
  520. {
  521. case ACT_TURN_LEFT:
  522. case ACT_TURN_RIGHT:
  523. return 45;
  524. break;
  525. default:
  526. if (GetOuter()->IsMoving())
  527. {
  528. return 15;
  529. }
  530. return 45; // too fast?
  531. break;
  532. }
  533. return -1;
  534. }
  535. void CAI_BlendedMotor::UpdateYaw( int speed )
  536. {
  537. // Don't do this is we're locked
  538. if ( IsYawLocked() )
  539. return;
  540. GetOuter()->UpdateTurnGesture( );
  541. BaseClass::UpdateYaw( speed );
  542. }
  543. void CAI_BlendedMotor::RecalculateYawSpeed()
  544. {
  545. // Don't do this is we're locked
  546. if ( IsYawLocked() )
  547. {
  548. SetYawSpeed( 0.0f );
  549. return;
  550. }
  551. if (GetOuter()->HasMemory( bits_MEMORY_TURNING ))
  552. return;
  553. SetYawSpeed( CalcYawSpeed() );
  554. }
  555. //-------------------------------------
  556. void CAI_BlendedMotor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw )
  557. {
  558. // TODO: merge transitions with movement script
  559. if (m_iPrimaryLayer != -1)
  560. {
  561. SetLayerWeight( m_iPrimaryLayer, 0 );
  562. }
  563. if (m_iSecondaryLayer != -1)
  564. {
  565. SetLayerWeight( m_iSecondaryLayer, 0 );
  566. }
  567. BaseClass::MoveClimbStart( climbDest, climbDir, climbDist, yaw );
  568. }
  569. //-------------------------------------
  570. void CAI_BlendedMotor::MoveJumpStart( const Vector &velocity )
  571. {
  572. // TODO: merge transitions with movement script
  573. if (m_iPrimaryLayer != -1)
  574. {
  575. SetLayerWeight( m_iPrimaryLayer, 0 );
  576. }
  577. if (m_iSecondaryLayer != -1)
  578. {
  579. SetLayerWeight( m_iSecondaryLayer, 0 );
  580. }
  581. BaseClass::MoveJumpStart( velocity );
  582. }
  583. //-------------------------------------
  584. void CAI_BlendedMotor::BuildMoveScript( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
  585. {
  586. m_scriptMove.RemoveAll();
  587. m_scriptTurn.RemoveAll();
  588. BuildVelocityScript( move );
  589. BuildTurnScript( move );
  590. /*
  591. if (GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
  592. {
  593. int i;
  594. #if 1
  595. for (i = 1; i < m_scriptMove.Count(); i++)
  596. {
  597. NDebugOverlay::Line( m_scriptMove[i-1].vecLocation, m_scriptMove[i].vecLocation, 255,255,255, true, 0.1 );
  598. NDebugOverlay::Box( m_scriptMove[i].vecLocation, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0,255,255, 0, 0.1 );
  599. //NDebugOverlay::Line( m_scriptMove[i].vecLocation, m_scriptMove[i].vecLocation + Vector( 0,0,m_scriptMove[i].flMaxVelocity), 0,255,255, true, 0.1 );
  600. Vector vecMidway = m_scriptMove[i].vecLocation + ((m_scriptMove[i-1].vecLocation - m_scriptMove[i].vecLocation) * 0.5);
  601. NDebugOverlay::Text( vecMidway, UTIL_VarArgs( "%d", i ), false, 0.1 );
  602. }
  603. #endif
  604. #if 0
  605. for (i = 1; i < m_scriptTurn.Count(); i++)
  606. {
  607. NDebugOverlay::Line( m_scriptTurn[i-1].vecLocation, m_scriptTurn[i].vecLocation, 255,255,255, true, 0.1 );
  608. NDebugOverlay::Box( m_scriptTurn[i].vecLocation, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,0, 0, 0.1 );
  609. NDebugOverlay::Line( m_scriptTurn[i].vecLocation + Vector( 0,0,1), m_scriptTurn[i].vecLocation + Vector( 0,0,1) + UTIL_YawToVector( m_scriptTurn[i].flYaw ) * 32, 255,0,0, true, 0.1 );
  610. }
  611. #endif
  612. }
  613. */
  614. }
  615. #define YAWSPEED 150
  616. void CAI_BlendedMotor::BuildTurnScript( const AILocalMoveGoal_t &move )
  617. {
  618. AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildTurnScript);
  619. int i;
  620. AI_Movementscript_t script;
  621. script.Init();
  622. // current location
  623. script.vecLocation = GetAbsOrigin();
  624. script.flYaw = GetAbsAngles().y;
  625. m_scriptTurn.AddToTail( script );
  626. //-------------------------
  627. // insert default turn parameters, try to turn 80% to goal at all corners before getting there
  628. int prev = 0;
  629. for (i = 0; i < m_scriptMove.Count(); i++)
  630. {
  631. AI_Waypoint_t *pCurWaypoint = m_scriptMove[i].pWaypoint;
  632. if (pCurWaypoint)
  633. {
  634. script.Init();
  635. script.vecLocation = pCurWaypoint->vecLocation;
  636. script.pWaypoint = pCurWaypoint;
  637. script.flElapsedTime = m_scriptMove[i].flElapsedTime;
  638. m_scriptTurn[prev].flTime = script.flElapsedTime - m_scriptTurn[prev].flElapsedTime;
  639. if (pCurWaypoint->GetNext())
  640. {
  641. Vector d1 = pCurWaypoint->GetNext()->vecLocation - script.vecLocation;
  642. Vector d2 = script.vecLocation - m_scriptTurn[prev].vecLocation;
  643. d1.z = 0;
  644. VectorNormalize( d1 );
  645. d2.z = 0;
  646. VectorNormalize( d2 );
  647. float y1 = UTIL_VecToYaw( d1 );
  648. float y2 = UTIL_VecToYaw( d2 );
  649. float deltaYaw = fabs( UTIL_AngleDiff( y1, y2 ) );
  650. if (deltaYaw > 0.1)
  651. {
  652. // turn to 80% of goal
  653. script.flYaw = UTIL_ApproachAngle( y1, y2, deltaYaw * 0.8 );
  654. m_scriptTurn.AddToTail( script );
  655. // DevMsg("turn waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
  656. prev++;
  657. }
  658. }
  659. else
  660. {
  661. Vector vecDir = GetNavigator()->GetArrivalDirection();
  662. script.flYaw = UTIL_VecToYaw( vecDir );
  663. m_scriptTurn.AddToTail( script );
  664. // DevMsg("turn waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
  665. prev++;
  666. }
  667. }
  668. }
  669. // propagate ending facing back over any nearby nodes
  670. // FIXME: this needs to minimize total turning, not just local/end turning.
  671. // depending on waypoint spacing, complexity, it may turn the wrong way!
  672. for (i = m_scriptTurn.Count()-1; i > 1; i--)
  673. {
  674. float deltaYaw = UTIL_AngleDiff( m_scriptTurn[i-1].flYaw, m_scriptTurn[i].flYaw );
  675. float maxYaw = YAWSPEED * m_scriptTurn[i-1].flTime;
  676. if (fabs(deltaYaw) > maxYaw)
  677. {
  678. m_scriptTurn[i-1].flYaw = UTIL_ApproachAngle( m_scriptTurn[i-1].flYaw, m_scriptTurn[i].flYaw, maxYaw );
  679. }
  680. }
  681. for (i = 0; i < m_scriptTurn.Count() - 1; )
  682. {
  683. i = i + BuildTurnScript( i, i + 1 ) + 1;
  684. }
  685. //-------------------------
  686. }
  687. int CAI_BlendedMotor::BuildTurnScript( int i, int j )
  688. {
  689. AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildTurnScript2);
  690. int k;
  691. Vector vecDir = m_scriptTurn[j].vecLocation - m_scriptTurn[i].vecLocation;
  692. float interiorYaw = UTIL_VecToYaw( vecDir );
  693. float deltaYaw;
  694. deltaYaw = fabs( UTIL_AngleDiff( interiorYaw, m_scriptTurn[i].flYaw ) );
  695. float t1 = deltaYaw / YAWSPEED;
  696. deltaYaw = fabs( UTIL_AngleDiff( m_scriptTurn[j].flYaw, interiorYaw ) );
  697. float t2 = deltaYaw / YAWSPEED;
  698. float totalTime = m_scriptTurn[j].flElapsedTime - m_scriptTurn[i].flElapsedTime;
  699. Assert( totalTime > 0 );
  700. if (t1 < 0.01)
  701. {
  702. if (t2 > totalTime * 0.8)
  703. {
  704. // too close, nothing to do
  705. return 0;
  706. }
  707. // go ahead and force yaw
  708. m_scriptTurn[i].flYaw = interiorYaw;
  709. // we're already aiming close enough to the interior yaw, set the point where we need to blend out
  710. k = BuildInsertNode( i, totalTime - t2 );
  711. m_scriptTurn[k].flYaw = interiorYaw;
  712. return 1;
  713. }
  714. else if (t2 < 0.01)
  715. {
  716. if (t1 > totalTime * 0.8)
  717. {
  718. // too close, nothing to do
  719. return 0;
  720. }
  721. // we'll finish up aiming close enough to the interior yaw, set the point where we need to blend in
  722. k = BuildInsertNode( i, t1 );
  723. m_scriptTurn[k].flYaw = interiorYaw;
  724. return 1;
  725. }
  726. else if (t1 + t2 > totalTime)
  727. {
  728. // don't bother with interior node
  729. return 0;
  730. // waypoints need to much turning, ignore interior yaw
  731. float a = (t1 / (t1 + t2));
  732. t1 = a * totalTime;
  733. k = BuildInsertNode( i, t1 );
  734. deltaYaw = UTIL_AngleDiff( m_scriptTurn[j].flYaw, m_scriptTurn[i].flYaw );
  735. m_scriptTurn[k].flYaw = UTIL_ApproachAngle( m_scriptTurn[j].flYaw, m_scriptTurn[i].flYaw, deltaYaw * (1 - a) );
  736. return 1;
  737. }
  738. else if (t1 + t2 < totalTime * 0.8)
  739. {
  740. // turn to face interior, run a ways, then turn away
  741. k = BuildInsertNode( i, t1 );
  742. m_scriptTurn[k].flYaw = interiorYaw;
  743. k = BuildInsertNode( i, t2 );
  744. m_scriptTurn[k].flYaw = interiorYaw;
  745. return 2;
  746. }
  747. return 0;
  748. }
  749. int CAI_BlendedMotor::BuildInsertNode( int i, float flTime )
  750. {
  751. AI_Movementscript_t script;
  752. script.Init();
  753. Assert( flTime > 0.0 );
  754. for (i; i < m_scriptTurn.Count() - 1; i++)
  755. {
  756. if (m_scriptTurn[i].flTime < flTime)
  757. {
  758. flTime -= m_scriptTurn[i].flTime;
  759. }
  760. else
  761. {
  762. float a = flTime / m_scriptTurn[i].flTime;
  763. script.flTime = (m_scriptTurn[i].flTime - flTime);
  764. m_scriptTurn[i].flTime = flTime;
  765. script.flElapsedTime = m_scriptTurn[i].flElapsedTime * (1 - a) + m_scriptTurn[i+1].flElapsedTime * a;
  766. script.vecLocation = m_scriptTurn[i].vecLocation * (1 - a) + m_scriptTurn[i+1].vecLocation * a;
  767. m_scriptTurn.InsertAfter( i, script );
  768. return i + 1;
  769. }
  770. }
  771. Assert( 0 );
  772. return 0;
  773. }
  774. ConVar ai_path_insert_pause_at_obstruction( "ai_path_insert_pause_at_obstruction", "1" );
  775. ConVar ai_path_adjust_speed_on_immediate_turns( "ai_path_adjust_speed_on_immediate_turns", "1" );
  776. ConVar ai_path_insert_pause_at_est_end( "ai_path_insert_pause_at_est_end", "1" );
  777. #define MIN_VELOCITY 0.0f
  778. #define MIN_STEER_DOT 0.0f
  779. void CAI_BlendedMotor::BuildVelocityScript( const AILocalMoveGoal_t &move )
  780. {
  781. AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildVelocityScript);
  782. int i;
  783. float a;
  784. float idealVelocity = GetIdealSpeed();
  785. if (idealVelocity == 0)
  786. {
  787. idealVelocity = 50;
  788. }
  789. float idealAccel = GetIdealAccel();
  790. if (idealAccel == 0)
  791. {
  792. idealAccel = 100;
  793. }
  794. AI_Movementscript_t script;
  795. // set current location as start of script
  796. script.vecLocation = GetAbsOrigin();
  797. script.flMaxVelocity = GetCurSpeed();
  798. m_scriptMove.AddToTail( script );
  799. //-------------------------
  800. extern ConVar npc_height_adjust;
  801. if (npc_height_adjust.GetBool() && move.bHasTraced && move.directTrace.flTotalDist != move.thinkTrace.flTotalDist)
  802. {
  803. float flDist = (move.directTrace.vEndPosition - m_scriptMove[0].vecLocation).Length2D();
  804. float flHeight = move.directTrace.vEndPosition.z - m_scriptMove[0].vecLocation.z;
  805. float flDelta;
  806. if (flDist > 0)
  807. {
  808. flDelta = flHeight / flDist;
  809. }
  810. else
  811. {
  812. flDelta = 0;
  813. }
  814. m_flPredictiveSpeedAdjust = 1.1 - fabs( flDelta );
  815. m_flPredictiveSpeedAdjust = clamp( m_flPredictiveSpeedAdjust, (flHeight > 0.0) ? 0.5 : 0.8, 1.0 );
  816. /*
  817. if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  818. {
  819. Msg("m_flPredictiveSpeedAdjust %.3f %.1f %.1f\n", m_flPredictiveSpeedAdjust, flHeight, flDist );
  820. NDebugOverlay::Box( move.directTrace.vEndPosition, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0,255,255, 0, 0.12 );
  821. }
  822. */
  823. }
  824. if (npc_height_adjust.GetBool())
  825. {
  826. float flDist = (move.thinkTrace.vEndPosition - m_vecPrevOrigin2).Length2D();
  827. float flHeight = move.thinkTrace.vEndPosition.z - m_vecPrevOrigin2.z;
  828. float flDelta;
  829. if (flDist > 0)
  830. {
  831. flDelta = flHeight / flDist;
  832. }
  833. else
  834. {
  835. flDelta = 0;
  836. }
  837. float newSpeedAdjust = 1.1 - fabs( flDelta );
  838. newSpeedAdjust = clamp( newSpeedAdjust, (flHeight > 0.0) ? 0.5 : 0.8, 1.0 );
  839. // debounce speed adjust
  840. if (newSpeedAdjust < m_flReactiveSpeedAdjust)
  841. {
  842. m_flReactiveSpeedAdjust = m_flReactiveSpeedAdjust * 0.2 + newSpeedAdjust * 0.8;
  843. }
  844. else
  845. {
  846. m_flReactiveSpeedAdjust = m_flReactiveSpeedAdjust * 0.5 + newSpeedAdjust * 0.5;
  847. }
  848. // filter through origins
  849. m_vecPrevOrigin2 = m_vecPrevOrigin1;
  850. m_vecPrevOrigin1 = GetAbsOrigin();
  851. /*
  852. if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  853. {
  854. NDebugOverlay::Box( m_vecPrevOrigin2, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,255, 0, 0.12 );
  855. NDebugOverlay::Box( move.thinkTrace.vEndPosition, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,255, 0, 0.12 );
  856. Msg("m_flReactiveSpeedAdjust %.3f %.1f %.1f\n", m_flReactiveSpeedAdjust, flHeight, flDist );
  857. }
  858. */
  859. }
  860. idealVelocity = idealVelocity * MIN( m_flReactiveSpeedAdjust, m_flPredictiveSpeedAdjust );
  861. //-------------------------
  862. bool bAddedExpected = false;
  863. // add all waypoint locations and velocities
  864. AI_Waypoint_t *pCurWaypoint = GetNavigator()->GetPath()->GetCurWaypoint();
  865. // there has to be at least one waypoint
  866. Assert( pCurWaypoint );
  867. while (pCurWaypoint && (pCurWaypoint->NavType() == NAV_GROUND || pCurWaypoint->NavType() == NAV_FLY) /*&& flTotalDist / idealVelocity < 3.0*/) // limit lookahead to 3 seconds
  868. {
  869. script.Init();
  870. AI_Waypoint_t *pNext = pCurWaypoint->GetNext();
  871. if (ai_path_adjust_speed_on_immediate_turns.GetBool() && !bAddedExpected)
  872. {
  873. // hack in next expected immediate location for move
  874. script.vecLocation = GetAbsOrigin() + move.dir * move.curExpectedDist;
  875. bAddedExpected = true;
  876. pNext = pCurWaypoint;
  877. }
  878. else
  879. {
  880. script.vecLocation = pCurWaypoint->vecLocation;
  881. script.pWaypoint = pCurWaypoint;
  882. }
  883. //DevMsg("waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
  884. if (pNext)
  885. {
  886. switch( pNext->NavType())
  887. {
  888. case NAV_GROUND:
  889. case NAV_FLY:
  890. {
  891. Vector d1 = pNext->vecLocation - script.vecLocation;
  892. Vector d2 = script.vecLocation - m_scriptMove[m_scriptMove.Count()-1].vecLocation;
  893. // remove very short, non terminal ground links
  894. // FIXME: is this safe? Maybe just check for co-located ground points?
  895. if (d1.Length2D() < 1.0)
  896. {
  897. /*
  898. if (m_scriptMove.Count() > 1)
  899. {
  900. int i = m_scriptMove.Count() - 1;
  901. m_scriptMove[i].vecLocation = pCurWaypoint->vecLocation;
  902. m_scriptMove[i].pWaypoint = pCurWaypoint;
  903. }
  904. */
  905. pCurWaypoint = pNext;
  906. continue;
  907. }
  908. d1.z = 0;
  909. VectorNormalize( d1 );
  910. d2.z = 0;
  911. VectorNormalize( d2 );
  912. // figure velocity
  913. float dot = (DotProduct( d1, d2 ) + 0.2);
  914. if (dot > 0)
  915. {
  916. dot = clamp( dot, 0.0f, 1.0f );
  917. script.flMaxVelocity = idealVelocity * dot;
  918. }
  919. else
  920. {
  921. script.flMaxVelocity = 0;
  922. }
  923. }
  924. break;
  925. case NAV_JUMP:
  926. // FIXME: information about what the jump should look like isn't stored in the waypoints
  927. // this'll need to call
  928. // GetMoveProbe()->MoveLimit( NAV_JUMP, GetLocalOrigin(), GetPath()->CurWaypointPos(), GetOuter()->GetAITraceMask(), GetNavTargetEntity(), &moveTrace );
  929. // to get how far/fast the jump will be, but this is also stateless, so it'd call it per frame.
  930. // So far it's not clear that the moveprobe doesn't also call this.....
  931. {
  932. float minJumpHeight = 0;
  933. float maxHorzVel = MAX( GetCurSpeed(), 100 );
  934. float gravity = sv_gravity.GetFloat() * GetOuter()->GetGravity();
  935. Vector vecApex;
  936. Vector rawJumpVel = GetMoveProbe()->CalcJumpLaunchVelocity(script.vecLocation, pNext->vecLocation, gravity, &minJumpHeight, maxHorzVel, &vecApex );
  937. script.flMaxVelocity = rawJumpVel.Length2D();
  938. // Msg("%.1f\n", script.flMaxVelocity );
  939. }
  940. break;
  941. case NAV_CLIMB:
  942. {
  943. /*
  944. CAI_Node *pClimbNode = GetNavigator()->GetNetwork()->GetNode(pNext->iNodeID);
  945. check: pClimbNode->m_eNodeInfo
  946. bits_NODE_CLIMB_BOTTOM,
  947. bits_NODE_CLIMB_ON,
  948. bits_NODE_CLIMB_OFF_FORWARD,
  949. bits_NODE_CLIMB_OFF_LEFT,
  950. bits_NODE_CLIMB_OFF_RIGHT
  951. */
  952. script.flMaxVelocity = 0;
  953. }
  954. break;
  955. /*
  956. case NAV_FLY:
  957. // FIXME: can there be a NAV_GROUND -> NAV_FLY transition?
  958. script.flMaxVelocity = 0;
  959. break;
  960. */
  961. }
  962. }
  963. else
  964. {
  965. script.flMaxVelocity = GetNavigator()->GetArrivalSpeed();
  966. // Assert( script.flMaxVelocity == 0 );
  967. }
  968. m_scriptMove.AddToTail( script );
  969. pCurWaypoint = pNext;
  970. }
  971. //-------------------------
  972. // update distances
  973. float flTotalDist = 0;
  974. for (i = 0; i < m_scriptMove.Count() - 1; i++ )
  975. {
  976. flTotalDist += m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
  977. }
  978. //-------------------------
  979. if ( !m_bDeceleratingToGoal && m_scriptMove.Count() && flTotalDist > 0 )
  980. {
  981. float flNeededAccel = DeltaV( m_scriptMove[0].flMaxVelocity, m_scriptMove[m_scriptMove.Count() - 1].flMaxVelocity, flTotalDist );
  982. m_bDeceleratingToGoal = (flNeededAccel < -idealAccel);
  983. //Assert( flNeededAccel != idealAccel);
  984. }
  985. //-------------------------
  986. // insert slowdown points due to blocking
  987. if (ai_path_insert_pause_at_obstruction.GetBool() && move.directTrace.pObstruction)
  988. {
  989. float distToObstruction = (move.directTrace.vEndPosition - m_scriptMove[0].vecLocation).Length2D();
  990. // HACK move obstruction out "stepsize" to account for it being based on stand position and not a trace
  991. distToObstruction = distToObstruction + 16;
  992. InsertSlowdown( distToObstruction, idealAccel, false );
  993. }
  994. if (ai_path_insert_pause_at_est_end.GetBool() && GetNavigator()->GetArrivalDistance() > 0.0)
  995. {
  996. InsertSlowdown( flTotalDist - GetNavigator()->GetArrivalDistance(), idealAccel, true );
  997. }
  998. // calc initial velocity based on immediate direction changes
  999. if ( ai_path_adjust_speed_on_immediate_turns.GetBool() && m_scriptMove.Count() > 1)
  1000. {
  1001. /*
  1002. if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  1003. {
  1004. Vector tmp = m_scriptMove[1].vecLocation - m_scriptMove[0].vecLocation;
  1005. VectorNormalize( tmp );
  1006. NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[0].vecLocation + tmp * 32 + Vector( 0, 0, 10 ), 255,255,255, true, 0.1 );
  1007. NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[1].vecLocation + Vector( 0, 0, 10 ), 255,0,0, true, 0.1 );
  1008. tmp = GetCurVel();
  1009. VectorNormalize( tmp );
  1010. NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[0].vecLocation + tmp * 32 + Vector( 0, 0, 10 ), 0,0,255, true, 0.1 );
  1011. }
  1012. */
  1013. Vector d1 = m_scriptMove[1].vecLocation - m_scriptMove[0].vecLocation;
  1014. d1.z = 0;
  1015. VectorNormalize( d1 );
  1016. Vector d2 = GetCurVel();
  1017. d2.z = 0;
  1018. VectorNormalize( d2 );
  1019. float dot = (DotProduct( d1, d2 ) + MIN_STEER_DOT);
  1020. dot = clamp( dot, 0.0f, 1.0f );
  1021. m_scriptMove[0].flMaxVelocity = m_scriptMove[0].flMaxVelocity * dot;
  1022. }
  1023. // clamp forward velocities
  1024. for (i = 0; i < m_scriptMove.Count() - 1; i++ )
  1025. {
  1026. // find needed acceleration
  1027. float dv = m_scriptMove[i+1].flMaxVelocity - m_scriptMove[i].flMaxVelocity;
  1028. if (dv > 0.0)
  1029. {
  1030. // find time, distance to accel to next MAX vel
  1031. float t1 = dv / idealAccel;
  1032. float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
  1033. // is there enough distance
  1034. if (d1 > m_scriptMove[i].flDist)
  1035. {
  1036. float r1, r2;
  1037. // clamp the next velocity to the possible accel in the given distance
  1038. if (SolveQuadratic( 0.5 * idealAccel, m_scriptMove[i].flMaxVelocity, -m_scriptMove[i].flDist, r1, r2 ))
  1039. {
  1040. m_scriptMove[i+1].flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * r1;
  1041. }
  1042. }
  1043. }
  1044. }
  1045. // clamp decel velocities
  1046. for (i = m_scriptMove.Count() - 1; i > 0; i-- )
  1047. {
  1048. // find needed deceleration
  1049. float dv = m_scriptMove[i].flMaxVelocity - m_scriptMove[i-1].flMaxVelocity;
  1050. if (dv < 0.0)
  1051. {
  1052. // find time, distance to decal to next MAX vel
  1053. float t1 = -dv / idealAccel;
  1054. float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
  1055. // is there enough distance
  1056. if (d1 > m_scriptMove[i-1].flDist)
  1057. {
  1058. float r1, r2;
  1059. // clamp the next velocity to the possible decal in the given distance
  1060. if (SolveQuadratic( 0.5 * idealAccel, m_scriptMove[i].flMaxVelocity, -m_scriptMove[i-1].flDist, r1, r2 ))
  1061. {
  1062. m_scriptMove[i-1].flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * r1;
  1063. }
  1064. }
  1065. }
  1066. }
  1067. /*
  1068. for (i = 0; i < m_scriptMove.Count(); i++)
  1069. {
  1070. NDebugOverlay::Text( m_scriptMove[i].vecLocation, (const char *)CFmtStr( "%.2f ", m_scriptMove[i].flMaxVelocity ), false, 0.1 );
  1071. // DevMsg("%.2f ", m_scriptMove[i].flMaxVelocity );
  1072. }
  1073. // DevMsg("\n");
  1074. */
  1075. // insert intermediate ideal velocities
  1076. for (i = 0; i < m_scriptMove.Count() - 1;)
  1077. {
  1078. // accel to ideal
  1079. float t1 = (idealVelocity - m_scriptMove[i].flMaxVelocity) / idealAccel;
  1080. float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
  1081. // decel from ideal
  1082. float t2 = (idealVelocity - m_scriptMove[i+1].flMaxVelocity) / idealAccel;
  1083. float d2 = m_scriptMove[i+1].flMaxVelocity * t2 + 0.5 * (idealAccel) * t2 * t2;
  1084. m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
  1085. // is it possible to accel and decal to idealVelocity between next two nodes
  1086. if (d1 + d2 < m_scriptMove[i].flDist)
  1087. {
  1088. Vector start = m_scriptMove[i].vecLocation;
  1089. Vector end = m_scriptMove[i+1].vecLocation;
  1090. float dist = m_scriptMove[i].flDist;
  1091. // insert the two points needed to end accel and start decel
  1092. if (d1 > 1.0 && t1 > 0.1)
  1093. {
  1094. a = d1 / dist;
  1095. script.Init();
  1096. script.vecLocation = end * a + start * (1 - a);
  1097. script.flMaxVelocity = idealVelocity;
  1098. m_scriptMove.InsertAfter( i, script );
  1099. i++;
  1100. }
  1101. if (dist - d2 > 1.0 && t2 > 0.1)
  1102. {
  1103. // DevMsg("%.2f : ", a );
  1104. a = (dist - d2) / dist;
  1105. script.Init();
  1106. script.vecLocation = end * a + start * (1 - a);
  1107. script.flMaxVelocity = idealVelocity;
  1108. m_scriptMove.InsertAfter( i, script );
  1109. i++;
  1110. }
  1111. i++;
  1112. }
  1113. else
  1114. {
  1115. // check to see if the amount of change needed to reach target is less than the ideal acceleration
  1116. float flNeededAccel = fabs( DeltaV( m_scriptMove[i].flMaxVelocity, m_scriptMove[i+1].flMaxVelocity, m_scriptMove[i].flDist ) );
  1117. if (flNeededAccel < idealAccel)
  1118. {
  1119. // if so, they it's possible to get a bit towards the ideal velocity
  1120. float v1 = m_scriptMove[i].flMaxVelocity;
  1121. float v2 = m_scriptMove[i+1].flMaxVelocity;
  1122. float dist = m_scriptMove[i].flDist;
  1123. // based on solving:
  1124. // v1+A*t1-v2-A*t2=0
  1125. // v1*t1+0.5*A*t1*t1+v2*t2+0.5*A*t2*t2-D=0
  1126. float tmp = idealAccel*dist+0.5*v1*v1+0.5*v2*v2;
  1127. Assert( tmp >= 0 );
  1128. t1 = (-v1+sqrt( tmp )) / idealAccel;
  1129. t2 = (v1+idealAccel*t1-v2)/idealAccel;
  1130. // if this assert hits, write down the v1, v2, dist, and idealAccel numbers and send them to me (Ken).
  1131. // go ahead the comment it out, it's safe, but I'd like to know a test case where it's happening
  1132. //Assert( t1 > 0 && t2 > 0 );
  1133. // check to make sure it's really worth it
  1134. if (t1 > 0.0 && t2 > 0.0)
  1135. {
  1136. d1 = v1 * t1 + 0.5 * idealAccel * t1 * t1;
  1137. /*
  1138. d2 = v2 * t2 + 0.5 * idealAccel * t2 * t2;
  1139. Assert( fabs( d1 + d2 - dist ) < 0.001 );
  1140. */
  1141. float a = d1 / m_scriptMove[i].flDist;
  1142. script.Init();
  1143. script.vecLocation = m_scriptMove[i+1].vecLocation * a + m_scriptMove[i].vecLocation * (1 - a);
  1144. script.flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * t1;
  1145. if (script.flMaxVelocity < idealVelocity)
  1146. {
  1147. // DevMsg("insert %.2f %.2f %.2f\n", m_scriptMove[i].flMaxVelocity, script.flMaxVelocity, m_scriptMove[i+1].flMaxVelocity );
  1148. m_scriptMove.InsertAfter( i, script );
  1149. i += 1;
  1150. }
  1151. }
  1152. }
  1153. i += 1;
  1154. }
  1155. }
  1156. // clamp MIN velocities
  1157. for (i = 0; i < m_scriptMove.Count(); i++)
  1158. {
  1159. m_scriptMove[i].flMaxVelocity = MAX( m_scriptMove[i].flMaxVelocity, MIN_VELOCITY );
  1160. }
  1161. // rebuild fields
  1162. m_scriptMove[0].flElapsedTime = 0;
  1163. for (i = 0; i < m_scriptMove.Count() - 1; )
  1164. {
  1165. m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
  1166. if (m_scriptMove[i].flMaxVelocity == 0 && m_scriptMove[i+1].flMaxVelocity == 0)
  1167. {
  1168. // force a minimum velocity
  1169. Assert( 0 );
  1170. m_scriptMove[i+1].flMaxVelocity = 1.0;
  1171. }
  1172. float t = m_scriptMove[i].flDist / (0.5 * (m_scriptMove[i].flMaxVelocity + m_scriptMove[i+1].flMaxVelocity));
  1173. m_scriptMove[i].flTime = t;
  1174. /*
  1175. if (m_scriptMove[i].flDist < 0.01)
  1176. {
  1177. // Assert( m_scriptMove[i+1].pWaypoint == NULL );
  1178. m_scriptMove.Remove( i + 1 );
  1179. continue;
  1180. }
  1181. */
  1182. m_scriptMove[i+1].flElapsedTime = m_scriptMove[i].flElapsedTime + m_scriptMove[i].flTime;
  1183. i++;
  1184. }
  1185. /*
  1186. for (i = 0; i < m_scriptMove.Count(); i++)
  1187. {
  1188. DevMsg("(%.2f : %.2f : %.2f)", m_scriptMove[i].flMaxVelocity, m_scriptMove[i].flDist, m_scriptMove[i].flTime );
  1189. // DevMsg("(%.2f:%.2f)", m_scriptMove[i].flTime, m_scriptMove[i].flElapsedTime );
  1190. }
  1191. DevMsg("\n");
  1192. */
  1193. }
  1194. void CAI_BlendedMotor::InsertSlowdown( float distToObstruction, float idealAccel, bool bAlwaysSlowdown )
  1195. {
  1196. int i;
  1197. AI_Movementscript_t script;
  1198. if (distToObstruction <= 0.0)
  1199. return;
  1200. for (i = 0; i < m_scriptMove.Count() - 1; i++)
  1201. {
  1202. if (m_scriptMove[i].flDist > 0 && distToObstruction - m_scriptMove[i].flDist < 0)
  1203. {
  1204. float a = distToObstruction / m_scriptMove[i].flDist;
  1205. Assert( a >= 0 && a <= 1);
  1206. script.vecLocation = (1 - a) * m_scriptMove[i].vecLocation + a * m_scriptMove[i+1].vecLocation;
  1207. //NDebugOverlay::Line( m_scriptMove[i].vecLocation + Vector( 0, 0, 5 ), script.vecLocation + Vector( 0, 0, 5 ), 0,255,0, true, 0.1 );
  1208. //NDebugOverlay::Line( script.vecLocation + Vector( 0, 0, 5 ), m_scriptMove[i+1].vecLocation + Vector( 0, 0, 5 ), 0,0,255, true, 0.1 );
  1209. float r1, r2;
  1210. // clamp the next velocity to the possible accel in the given distance
  1211. if (!bAlwaysSlowdown && SolveQuadratic( -0.5 * idealAccel, m_scriptMove[0].flMaxVelocity, -distToObstruction, r1, r2 ))
  1212. {
  1213. script.flMaxVelocity = MAX( 10, m_scriptMove[0].flMaxVelocity - idealAccel * r1 );
  1214. }
  1215. else
  1216. {
  1217. script.flMaxVelocity = 10.0;
  1218. }
  1219. script.flMaxVelocity = 1.0; // as much as reasonable
  1220. script.pWaypoint = NULL;
  1221. script.flDist = m_scriptMove[i].flDist - distToObstruction;
  1222. m_scriptMove[i].flDist = distToObstruction;
  1223. m_scriptMove.InsertAfter( i, script );
  1224. break;
  1225. }
  1226. else
  1227. {
  1228. distToObstruction -= m_scriptMove[i].flDist;
  1229. }
  1230. }
  1231. }
  1232. //-----------------------------------------------------------------------------
  1233. // Purpose: issues turn gestures when it detects that the body has turned but the feet haven't compensated
  1234. //-----------------------------------------------------------------------------
  1235. void CAI_BlendedMotor::MaintainTurnActivity( void )
  1236. {
  1237. AI_PROFILE_SCOPE(CAI_BlendedMotor_MaintainTurnActivity);
  1238. if (m_flNextTurnGesture > gpGlobals->curtime || m_flNextTurnAct > gpGlobals->curtime || GetOuter()->IsMoving() )
  1239. {
  1240. // clear out turn detection if currently turing or moving
  1241. m_doTurn = m_doRight = m_doLeft = 0;
  1242. if ( GetOuter()->IsMoving())
  1243. {
  1244. m_flNextTurnAct = gpGlobals->curtime + 0.3;
  1245. }
  1246. }
  1247. else
  1248. {
  1249. // detect undirected turns
  1250. if (m_prevYaw != GetAbsAngles().y)
  1251. {
  1252. float diff = UTIL_AngleDiff( m_prevYaw, GetAbsAngles().y );
  1253. if (diff < 0.0)
  1254. {
  1255. m_doLeft += -diff;
  1256. }
  1257. else
  1258. {
  1259. m_doRight += diff;
  1260. }
  1261. m_prevYaw = GetAbsAngles().y;
  1262. }
  1263. // accumulate turn angle, delay response for short turns
  1264. m_doTurn += m_doRight + m_doLeft;
  1265. // accumulate random foot stick clearing
  1266. m_doTurn += random->RandomFloat( 0.4, 0.6 );
  1267. }
  1268. if (m_doTurn > 15.0f)
  1269. {
  1270. // mostly a foot stick clear
  1271. int iSeq = ACT_INVALID;
  1272. if (m_doLeft > m_doRight)
  1273. {
  1274. iSeq = SelectWeightedSequence( ACT_GESTURE_TURN_LEFT );
  1275. }
  1276. else
  1277. {
  1278. iSeq = SelectWeightedSequence( ACT_GESTURE_TURN_RIGHT );
  1279. }
  1280. m_doLeft = 0;
  1281. m_doRight = 0;
  1282. if (iSeq != ACT_INVALID)
  1283. {
  1284. int iLayer = GetOuter()->AddGestureSequence( iSeq );
  1285. if (iLayer != -1)
  1286. {
  1287. GetOuter()->SetLayerPriority( iLayer, 100 );
  1288. // increase speed if we're getting behind or they're turning quickly
  1289. float rate = random->RandomFloat( 0.8, 1.2 );
  1290. if (m_doTurn > 90.0)
  1291. {
  1292. rate *= 1.5;
  1293. }
  1294. GetOuter()->SetLayerPlaybackRate( iLayer, rate );
  1295. // disable turing for the duration of the gesture
  1296. m_flNextTurnAct = gpGlobals->curtime + GetOuter()->GetLayerDuration( iLayer );
  1297. }
  1298. else
  1299. {
  1300. // too many active gestures, try again in half a second
  1301. m_flNextTurnAct = gpGlobals->curtime + 0.3;
  1302. }
  1303. }
  1304. m_doTurn = m_doRight = m_doLeft = 0;
  1305. }
  1306. }
  1307. ConVar scene_flatturn( "scene_flatturn", "1" );
  1308. bool CAI_BlendedMotor::AddTurnGesture( float flYD )
  1309. {
  1310. // some funky bug with human turn gestures, disable for now
  1311. return false;
  1312. // try using a turn gesture
  1313. Activity activity = ACT_INVALID;
  1314. float weight = 1.0;
  1315. float turnCompletion = 1.0;
  1316. if (m_flNextTurnGesture > gpGlobals->curtime)
  1317. {
  1318. /*
  1319. if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  1320. {
  1321. Msg( "%.1f : [ %.2f ]\n", flYD, m_flNextTurnAct - gpGlobals->curtime );
  1322. }
  1323. */
  1324. return false;
  1325. }
  1326. if ( GetOuter()->IsMoving() || GetOuter()->IsCrouching() )
  1327. {
  1328. return false;
  1329. }
  1330. if (fabs( flYD ) < 15)
  1331. {
  1332. return false;
  1333. }
  1334. else if (flYD < -45)
  1335. {
  1336. activity = ACT_GESTURE_TURN_RIGHT90;
  1337. weight = flYD / -90;
  1338. turnCompletion = 0.36;
  1339. }
  1340. else if (flYD < 0)
  1341. {
  1342. activity = ACT_GESTURE_TURN_RIGHT45;
  1343. weight = flYD / -45;
  1344. turnCompletion = 0.4;
  1345. }
  1346. else if (flYD <= 45)
  1347. {
  1348. activity = ACT_GESTURE_TURN_LEFT45;
  1349. weight = flYD / 45;
  1350. turnCompletion = 0.4;
  1351. }
  1352. else
  1353. {
  1354. activity = ACT_GESTURE_TURN_LEFT90;
  1355. weight = flYD / 90;
  1356. turnCompletion = 0.36;
  1357. }
  1358. int seq = SelectWeightedSequence( activity );
  1359. if (scene_flatturn.GetBool() && GetOuter()->IsCurSchedule( SCHED_SCENE_GENERIC ))
  1360. {
  1361. Activity flatactivity = activity;
  1362. if (activity == ACT_GESTURE_TURN_RIGHT90)
  1363. {
  1364. flatactivity = ACT_GESTURE_TURN_RIGHT90_FLAT;
  1365. }
  1366. else if (activity == ACT_GESTURE_TURN_RIGHT45)
  1367. {
  1368. flatactivity = ACT_GESTURE_TURN_RIGHT45_FLAT;
  1369. }
  1370. else if (activity == ACT_GESTURE_TURN_LEFT90)
  1371. {
  1372. flatactivity = ACT_GESTURE_TURN_LEFT90_FLAT;
  1373. }
  1374. else if (activity == ACT_GESTURE_TURN_LEFT45)
  1375. {
  1376. flatactivity = ACT_GESTURE_TURN_LEFT45_FLAT;
  1377. }
  1378. if (flatactivity != activity)
  1379. {
  1380. int newseq = SelectWeightedSequence( flatactivity );
  1381. if (newseq != ACTIVITY_NOT_AVAILABLE)
  1382. {
  1383. seq = newseq;
  1384. }
  1385. }
  1386. }
  1387. if (seq != ACTIVITY_NOT_AVAILABLE)
  1388. {
  1389. int iLayer = GetOuter()->AddGestureSequence( seq );
  1390. if (iLayer != -1)
  1391. {
  1392. GetOuter()->SetLayerPriority( iLayer, 100 );
  1393. // vary the playback a bit
  1394. SetLayerPlaybackRate( iLayer, 1.0 );
  1395. float actualDuration = GetOuter()->GetLayerDuration( iLayer );
  1396. float rate = random->RandomFloat( 0.5, 1.1 );
  1397. float diff = fabs( flYD );
  1398. float speed = (diff / (turnCompletion * actualDuration / rate)) * 0.1;
  1399. speed = clamp( speed, 15, 35 );
  1400. speed = MIN( speed, diff );
  1401. actualDuration = (diff / (turnCompletion * speed)) * 0.1 ;
  1402. GetOuter()->SetLayerDuration( iLayer, actualDuration );
  1403. SetLayerWeight( iLayer, weight );
  1404. SetYawSpeed( speed );
  1405. Remember( bits_MEMORY_TURNING );
  1406. // don't overlap the turn portion of the gestures, and don't play them too often
  1407. m_flNextTurnGesture = gpGlobals->curtime + MAX( turnCompletion * actualDuration, 0.3 );
  1408. /*
  1409. if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  1410. {
  1411. Msg( "%.1f : %.2f %.2f : %.2f (%.2f)\n", flYD, weight, speed, actualDuration, turnCompletion * actualDuration );
  1412. }
  1413. */
  1414. return true;
  1415. }
  1416. else
  1417. {
  1418. return false;
  1419. }
  1420. }
  1421. return false;
  1422. }
  1423. //-------------------------------------
  1424. #if 0
  1425. Activity CAI_BlendedMotor::GetTransitionActivity( )
  1426. {
  1427. AI_Waypoint_t *waypoint = GetNavigator()->GetPath()->GetTransitionWaypoint();
  1428. if ( waypoint->Flags() & bits_WP_TO_GOAL )
  1429. {
  1430. if ( waypoint->activity != ACT_INVALID)
  1431. {
  1432. return waypoint->activity;
  1433. }
  1434. return GetStoppedActivity( );
  1435. }
  1436. if (waypoint)
  1437. waypoint = waypoint->GetNext();
  1438. switch(waypoint->NavType() )
  1439. {
  1440. case NAV_JUMP:
  1441. return ACT_JUMP; // are jumps going to get a movement track added to them?
  1442. case NAV_GROUND:
  1443. return GetNavigator()->GetMovementActivity(); // yuck
  1444. case NAV_CLIMB:
  1445. return ACT_CLIMB_UP; // depends on specifics of climb node
  1446. default:
  1447. return ACT_IDLE;
  1448. }
  1449. }
  1450. #endif
  1451. //-------------------------------------
  1452. // Purpose: return a velocity that should be hit at the end of the interval to match goal
  1453. // Input : flInterval - time interval to consider
  1454. // : flGoalDistance - distance to goal
  1455. // : flGoalVelocity - desired velocity at goal
  1456. // : flCurVelocity - current velocity
  1457. // : flIdealVelocity - velocity to go at if goal is too far away
  1458. // : flAccelRate - maximum acceleration/deceleration rate
  1459. // Output : target velocity at time t+flInterval
  1460. //-------------------------------------
  1461. float ChangeDistance( float flInterval, float flGoalDistance, float flGoalVelocity, float flCurVelocity, float flIdealVelocity, float flAccelRate, float &flNewDistance, float &flNewVelocity )
  1462. {
  1463. float scale = 1.0;
  1464. if (flGoalDistance < 0)
  1465. {
  1466. flGoalDistance = - flGoalDistance;
  1467. flCurVelocity = -flCurVelocity;
  1468. scale = -1.0;
  1469. }
  1470. flNewVelocity = flCurVelocity;
  1471. flNewDistance = 0.0;
  1472. // if I'm too close, just go ahead and set the velocity
  1473. if (flGoalDistance < 0.01)
  1474. {
  1475. return flGoalVelocity * scale;
  1476. }
  1477. float flGoalAccel = DeltaV( flCurVelocity, flGoalVelocity, flGoalDistance );
  1478. flNewVelocity = flCurVelocity;
  1479. // --------------------------------------------
  1480. // if goal is close enough try to match the goal velocity, else try to go ideal velocity
  1481. // --------------------------------------------
  1482. if (flGoalAccel < 0 && flGoalAccel < -flAccelRate)
  1483. {
  1484. // I need to slow down;
  1485. flNewVelocity = flCurVelocity + flGoalAccel * flInterval;
  1486. if (flNewVelocity < 0)
  1487. flNewVelocity = 0;
  1488. }
  1489. else if (flGoalAccel > 0 && flGoalAccel >= flAccelRate)
  1490. {
  1491. // I need to speed up
  1492. flNewVelocity = flCurVelocity + flGoalAccel * flInterval;
  1493. if (flNewVelocity > flGoalVelocity)
  1494. flGoalVelocity = flGoalVelocity;
  1495. }
  1496. else if (flNewVelocity < flIdealVelocity)
  1497. {
  1498. // speed up to ideal velocity;
  1499. flNewVelocity = flCurVelocity + flAccelRate * flInterval;
  1500. if (flNewVelocity > flIdealVelocity)
  1501. flNewVelocity = flIdealVelocity;
  1502. // don't overshoot
  1503. if (0.5*(flNewVelocity + flCurVelocity) * flInterval > flGoalDistance)
  1504. {
  1505. flNewVelocity = 0.5 * (2 * flGoalDistance / flInterval - flCurVelocity);
  1506. }
  1507. }
  1508. else if (flNewVelocity > flIdealVelocity)
  1509. {
  1510. // slow down to ideal velocity;
  1511. flNewVelocity = flCurVelocity - flAccelRate * flInterval;
  1512. if (flNewVelocity < flIdealVelocity)
  1513. flNewVelocity = flIdealVelocity;
  1514. }
  1515. float flDist = 0.5*(flNewVelocity + flCurVelocity) * flInterval;
  1516. if (flDist > flGoalDistance)
  1517. {
  1518. flDist = flGoalDistance;
  1519. flNewVelocity = flGoalVelocity;
  1520. }
  1521. flNewVelocity = flNewVelocity * scale;
  1522. flNewDistance = (flGoalDistance - flDist) * scale;
  1523. return 0.0;
  1524. }
  1525. //-----------------------------------------------------------------------------