Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1904 lines
51 KiB

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