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.

448 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ai_localnavigator.h"
  8. #include "ai_basenpc.h"
  9. #include "ai_planesolver.h"
  10. #include "ai_moveprobe.h"
  11. #include "ai_motor.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. ConVar ai_debug_directnavprobe("ai_debug_directnavprobe", "0");
  15. const float TIME_DELAY_FULL_DIRECT_PROBE[2] = { 0.25, 0.35 };
  16. //-----------------------------------------------------------------------------
  17. BEGIN_SIMPLE_DATADESC(CAI_LocalNavigator)
  18. // m_fLastWasClear (not saved)
  19. // m_LastMoveGoal (not saved)
  20. // m_FullDirectTimer (not saved)
  21. // m_pPlaneSolver (not saved)
  22. // m_pMoveProbe (not saved)
  23. END_DATADESC();
  24. //-------------------------------------
  25. CAI_LocalNavigator::CAI_LocalNavigator(CAI_BaseNPC *pOuter) : CAI_Component( pOuter )
  26. {
  27. m_pMoveProbe = NULL;
  28. m_pPlaneSolver = new CAI_PlaneSolver( pOuter );
  29. m_fLastWasClear = false;
  30. memset( &m_LastMoveGoal, 0, sizeof(m_LastMoveGoal) );
  31. }
  32. //-------------------------------------
  33. CAI_LocalNavigator::~CAI_LocalNavigator()
  34. {
  35. delete m_pPlaneSolver;
  36. }
  37. //-------------------------------------
  38. void CAI_LocalNavigator::Init( IAI_MovementSink *pMovementServices )
  39. {
  40. CAI_ProxyMovementSink::Init( pMovementServices );
  41. m_pMoveProbe = GetOuter()->GetMoveProbe(); // @TODO (toml 03-30-03): this is a "bad" way to grab this pointer. Components should have an explcit "init" phase.
  42. }
  43. //-------------------------------------
  44. void CAI_LocalNavigator::ResetMoveCalculations()
  45. {
  46. m_FullDirectTimer.Force();
  47. m_pPlaneSolver->Reset();
  48. }
  49. //-------------------------------------
  50. void CAI_LocalNavigator::AddObstacle( const Vector &pos, float radius, AI_MoveSuggType_t type )
  51. {
  52. m_pPlaneSolver->AddObstacle( pos, radius, NULL, type );
  53. }
  54. //-------------------------------------
  55. bool CAI_LocalNavigator::HaveObstacles()
  56. {
  57. return m_pPlaneSolver->HaveObstacles();
  58. }
  59. //-------------------------------------
  60. bool CAI_LocalNavigator::MoveCalcDirect( AILocalMoveGoal_t *pMoveGoal, bool bOnlyCurThink, float *pDistClear, AIMoveResult_t *pResult )
  61. {
  62. AI_PROFILE_SCOPE(CAI_LocalNavigator_MoveCalcDirect);
  63. bool bRetVal = false;
  64. if ( pMoveGoal->speed )
  65. {
  66. CAI_Motor *pMotor = GetOuter()->GetMotor();
  67. float minCheckDist = pMotor->MinCheckDist();
  68. float probeDist = m_pPlaneSolver->CalcProbeDist( pMoveGoal->speed ); // having this match steering allows one fewer traces
  69. float checkDist = MAX( minCheckDist, probeDist );
  70. float checkStepDist = MAX( 16.0, probeDist * 0.5 );
  71. if ( pMoveGoal->flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) )
  72. {
  73. // clamp checkDist to be no farther than max distance to goal
  74. checkDist = MIN( checkDist, pMoveGoal->maxDist );
  75. }
  76. if ( checkDist <= 0.0 )
  77. {
  78. *pResult = AIMR_OK;
  79. return true;
  80. }
  81. float moveThisInterval = pMotor->CalcIntervalMove();
  82. bool bExpectingArrival = (moveThisInterval >= checkDist);
  83. if ( !m_FullDirectTimer.Expired() )
  84. {
  85. if ( !m_fLastWasClear ||
  86. ( !VectorsAreEqual(pMoveGoal->target, m_LastMoveGoal.target, 0.1) ||
  87. !VectorsAreEqual(pMoveGoal->dir, m_LastMoveGoal.dir, 0.1) ) ||
  88. bExpectingArrival )
  89. {
  90. m_FullDirectTimer.Force();
  91. }
  92. }
  93. if ( bOnlyCurThink ) // Outer code claims to have done a validation (probably a simplify operation)
  94. {
  95. m_FullDirectTimer.Set( TIME_DELAY_FULL_DIRECT_PROBE[AIStrongOpt()] );
  96. }
  97. // First, check the probable move for this cycle
  98. bool bTraceClear = true;
  99. Vector testPos;
  100. if ( !bExpectingArrival )
  101. {
  102. testPos = GetLocalOrigin() + pMoveGoal->dir * moveThisInterval;
  103. bTraceClear = GetMoveProbe()->MoveLimit( pMoveGoal->navType, GetLocalOrigin(), testPos,
  104. MASK_NPCSOLID, pMoveGoal->pMoveTarget,
  105. 100.0,
  106. ( pMoveGoal->navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT,
  107. &pMoveGoal->directTrace );
  108. if ( !bTraceClear )
  109. {
  110. // Adjust probe top match expected probe dist (relied on later in process)
  111. pMoveGoal->directTrace.flDistObstructed = (checkDist - moveThisInterval) + pMoveGoal->directTrace.flDistObstructed;
  112. }
  113. if ( !IsRetail() && ai_debug_directnavprobe.GetBool() )
  114. {
  115. if ( !bTraceClear )
  116. {
  117. DevMsg( GetOuter(), "Close obstruction %f\n", checkDist - pMoveGoal->directTrace.flDistObstructed );
  118. NDebugOverlay::Line( WorldSpaceCenter(), Vector( testPos.x, testPos.y, WorldSpaceCenter().z ), 255, 0, 0, false, 0.1 );
  119. if ( pMoveGoal->directTrace.pObstruction )
  120. NDebugOverlay::Line( WorldSpaceCenter(), pMoveGoal->directTrace.pObstruction->WorldSpaceCenter(), 255, 0, 255, false, 0.1 );
  121. }
  122. else
  123. {
  124. NDebugOverlay::Line( WorldSpaceCenter(), Vector( testPos.x, testPos.y, WorldSpaceCenter().z ), 0, 255, 0, false, 0.1 );
  125. }
  126. }
  127. pMoveGoal->thinkTrace = pMoveGoal->directTrace;
  128. }
  129. // Now project out for future obstructions
  130. if ( bTraceClear )
  131. {
  132. if ( m_FullDirectTimer.Expired() )
  133. {
  134. testPos = GetLocalOrigin() + pMoveGoal->dir * checkDist;
  135. float checkStepPct = (checkStepDist / checkDist) * 100.0;
  136. if ( checkStepPct > 100.0 )
  137. checkStepPct = 100.0;
  138. bTraceClear = GetMoveProbe()->MoveLimit( pMoveGoal->navType, GetLocalOrigin(), testPos,
  139. MASK_NPCSOLID, pMoveGoal->pMoveTarget,
  140. checkStepPct,
  141. ( pMoveGoal->navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT,
  142. &pMoveGoal->directTrace );
  143. if ( bExpectingArrival )
  144. pMoveGoal->thinkTrace = pMoveGoal->directTrace;
  145. if (ai_debug_directnavprobe.GetBool() )
  146. {
  147. if ( !bTraceClear )
  148. {
  149. NDebugOverlay::Line( GetOuter()->EyePosition(), Vector( testPos.x, testPos.y, GetOuter()->EyePosition().z ), 255, 0, 0, false, 0.1 );
  150. DevMsg( GetOuter(), "Obstruction %f\n", checkDist - pMoveGoal->directTrace.flDistObstructed );
  151. }
  152. else
  153. {
  154. NDebugOverlay::Line( GetOuter()->EyePosition(), Vector( testPos.x, testPos.y, GetOuter()->EyePosition().z ), 0, 255, 0, false, 0.1 );
  155. DevMsg( GetOuter(), "No obstruction\n" );
  156. }
  157. }
  158. }
  159. else
  160. {
  161. if ( ai_debug_directnavprobe.GetBool() )
  162. DevMsg( GetOuter(), "No obstruction (Near probe only)\n" );
  163. }
  164. }
  165. pMoveGoal->bHasTraced = true;
  166. float distClear = checkDist - pMoveGoal->directTrace.flDistObstructed;
  167. if (distClear < 0.001)
  168. distClear = 0;
  169. if ( bTraceClear )
  170. {
  171. *pResult = AIMR_OK;
  172. bRetVal = true;
  173. m_fLastWasClear = true;
  174. }
  175. else if ( ( pMoveGoal->flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) ) &&
  176. pMoveGoal->maxDist < distClear )
  177. {
  178. *pResult = AIMR_OK;
  179. bRetVal = true;
  180. m_fLastWasClear = true;
  181. }
  182. else
  183. {
  184. *pDistClear = distClear;
  185. m_fLastWasClear = false;
  186. }
  187. }
  188. else
  189. {
  190. // Should never end up in this function with speed of zero. Probably an activity problem.
  191. *pResult = AIMR_ILLEGAL;
  192. bRetVal = true;
  193. }
  194. m_LastMoveGoal = *pMoveGoal;
  195. if ( bRetVal && m_FullDirectTimer.Expired() )
  196. m_FullDirectTimer.Set( TIME_DELAY_FULL_DIRECT_PROBE[AIStrongOpt()] );
  197. return bRetVal;
  198. }
  199. //-------------------------------------
  200. ConVar ai_no_steer( "ai_no_steer", "0" );
  201. bool CAI_LocalNavigator::MoveCalcSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult )
  202. {
  203. if ( (pMoveGoal->flags & AILMG_NO_STEER) )
  204. return false;
  205. if ( ai_no_steer.GetBool() )
  206. return false;
  207. if ( GetOuter()->IsFlaggedEfficient() )
  208. return false;
  209. AI_PROFILE_SCOPE(CAI_Motor_MoveCalcSteer);
  210. Vector moveSolution;
  211. if ( m_pPlaneSolver->Solve( *pMoveGoal, distClear, &moveSolution ) )
  212. {
  213. if ( moveSolution != pMoveGoal->dir )
  214. {
  215. float dot = moveSolution.AsVector2D().Dot( pMoveGoal->dir.AsVector2D() );
  216. const float COS_HALF_30 = 0.966;
  217. if ( dot > COS_HALF_30 )
  218. {
  219. float probeDist = m_pPlaneSolver->CalcProbeDist( pMoveGoal->speed );
  220. if ( pMoveGoal->maxDist < probeDist * 0.33333 && distClear > probeDist * 0.6666)
  221. {
  222. // A waypoint is coming up, but there's probably time to steer
  223. // away after hitting it
  224. *pResult = AIMR_OK;
  225. return true;
  226. }
  227. }
  228. pMoveGoal->facing = pMoveGoal->dir = moveSolution;
  229. }
  230. *pResult = AIMR_OK;
  231. return true;
  232. }
  233. return false;
  234. }
  235. //-------------------------------------
  236. bool CAI_LocalNavigator::MoveCalcStop( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult )
  237. {
  238. if (distClear < pMoveGoal->maxDist)
  239. {
  240. if ( distClear < 0.1 )
  241. {
  242. DebugNoteMovementFailure();
  243. *pResult = AIMR_ILLEGAL;
  244. }
  245. else
  246. {
  247. pMoveGoal->maxDist = distClear;
  248. *pResult = AIMR_OK;
  249. }
  250. return true;
  251. }
  252. *pResult = AIMR_OK;
  253. return true;
  254. }
  255. //-------------------------------------
  256. #ifdef DEBUG
  257. #define SetSolveCookie() pMoveGoal->solveCookie = __LINE__;
  258. #else
  259. #define SetSolveCookie() ((void)0)
  260. #endif
  261. AIMoveResult_t CAI_LocalNavigator::MoveCalcRaw( AILocalMoveGoal_t *pMoveGoal, bool bOnlyCurThink )
  262. {
  263. AI_PROFILE_SCOPE(CAI_Motor_MoveCalc);
  264. AIMoveResult_t result = AIMR_OK; // Assume success
  265. AIMoveTrace_t directTrace;
  266. float distClear;
  267. // --------------------------------------------------
  268. bool bDirectClear = MoveCalcDirect( pMoveGoal, bOnlyCurThink, &distClear, &result);
  269. if ( OnCalcBaseMove( pMoveGoal, distClear, &result ) )
  270. {
  271. SetSolveCookie();
  272. return DbgResult( result );
  273. }
  274. bool bShouldSteer = ( !(pMoveGoal->flags & AILMG_NO_STEER) && ( !bDirectClear || HaveObstacles() ) );
  275. if ( bDirectClear && !bShouldSteer )
  276. {
  277. SetSolveCookie();
  278. return DbgResult( result );
  279. }
  280. // --------------------------------------------------
  281. if ( bShouldSteer )
  282. {
  283. if ( !bDirectClear )
  284. {
  285. if ( OnObstructionPreSteer( pMoveGoal, distClear, &result ) )
  286. {
  287. SetSolveCookie();
  288. return DbgResult( result );
  289. }
  290. }
  291. if ( MoveCalcSteer( pMoveGoal, distClear, &result ) )
  292. {
  293. SetSolveCookie();
  294. return DbgResult( result );
  295. }
  296. }
  297. if ( OnFailedSteer( pMoveGoal, distClear, &result ) )
  298. {
  299. SetSolveCookie();
  300. return DbgResult( result );
  301. }
  302. // --------------------------------------------------
  303. if ( OnFailedLocalNavigation( pMoveGoal, distClear, &result ) )
  304. {
  305. SetSolveCookie();
  306. return DbgResult( result );
  307. }
  308. if ( distClear < GetOuter()->GetMotor()->MinStoppingDist() )
  309. {
  310. if ( OnInsufficientStopDist( pMoveGoal, distClear, &result ) )
  311. {
  312. SetSolveCookie();
  313. return DbgResult( result );
  314. }
  315. if ( MoveCalcStop( pMoveGoal, distClear, &result) )
  316. {
  317. SetSolveCookie();
  318. return DbgResult( result );
  319. }
  320. }
  321. // A hopeful result... may get in trouble at next waypoint and obstruction is still there
  322. if ( distClear > pMoveGoal->curExpectedDist )
  323. {
  324. SetSolveCookie();
  325. return DbgResult( AIMR_OK );
  326. }
  327. // --------------------------------------------------
  328. DebugNoteMovementFailure();
  329. SetSolveCookie();
  330. return DbgResult( IsMoveBlocked( pMoveGoal->directTrace.fStatus ) ? pMoveGoal->directTrace.fStatus : AIMR_ILLEGAL );
  331. }
  332. //-------------------------------------
  333. AIMoveResult_t CAI_LocalNavigator::MoveCalc( AILocalMoveGoal_t *pMoveGoal, bool bPreviouslyValidated )
  334. {
  335. bool bOnlyCurThink = ( bPreviouslyValidated && !HaveObstacles() );
  336. AIMoveResult_t result = MoveCalcRaw( pMoveGoal, bOnlyCurThink );
  337. if ( pMoveGoal->curExpectedDist > pMoveGoal->maxDist )
  338. pMoveGoal->curExpectedDist = pMoveGoal->maxDist;
  339. // If success, try to dampen really fast turning movement
  340. if ( result == AIMR_OK)
  341. {
  342. float interval = GetOuter()->GetMotor()->GetMoveInterval();
  343. float currentYaw = UTIL_AngleMod( GetLocalAngles().y );
  344. float goalYaw;
  345. float deltaYaw;
  346. float speed;
  347. float clampedYaw;
  348. // Clamp yaw
  349. goalYaw = UTIL_VecToYaw( pMoveGoal->facing );
  350. deltaYaw = fabs( UTIL_AngleDiff( goalYaw, currentYaw ) );
  351. if ( deltaYaw > 15 )
  352. {
  353. speed = deltaYaw * 4.0; // i.e., any maneuver takes a quarter a second
  354. clampedYaw = AI_ClampYaw( speed, currentYaw, goalYaw, interval );
  355. if ( clampedYaw != goalYaw )
  356. {
  357. pMoveGoal->facing = UTIL_YawToVector( clampedYaw );
  358. }
  359. }
  360. }
  361. return result;
  362. }
  363. //-----------------------------------------------------------------------------