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.

1511 lines
49 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "modelentities.h"
  8. #include "iservervehicle.h"
  9. #include "movevars_shared.h"
  10. #include "ai_moveprobe.h"
  11. #include "ai_basenpc.h"
  12. #include "ai_routedist.h"
  13. #include "props.h"
  14. #include "vphysics/object_hash.h"
  15. // memdbgon must be the last include file in a .cpp file!!!
  16. #include "tier0/memdbgon.h"
  17. #undef LOCAL_STEP_SIZE
  18. // FIXME: this should be based in their hull width
  19. #define LOCAL_STEP_SIZE 16.0 // 8 // 16
  20. // If set to 1, results will be drawn for moveprobes done by player-selected NPCs
  21. ConVar ai_moveprobe_debug( "ai_moveprobe_debug", "0" );
  22. ConVar ai_moveprobe_jump_debug( "ai_moveprobe_jump_debug", "0" );
  23. ConVar ai_moveprobe_usetracelist( "ai_moveprobe_usetracelist", "0" );
  24. ConVar ai_strong_optimizations_no_checkstand( "ai_strong_optimizations_no_checkstand", "0" );
  25. #ifdef DEBUG
  26. ConVar ai_old_check_stand_position( "ai_old_check_stand_position", "0" );
  27. #define UseOldCheckStandPosition() (ai_old_check_stand_position.GetBool())
  28. #else
  29. #define UseOldCheckStandPosition() (false)
  30. #endif
  31. //-----------------------------------------------------------------------------
  32. // We may be able to remove this, but due to certain collision
  33. // problems on displacements, and due to the fact that CheckStep is currently
  34. // being called from code outside motor code, we may need to give it a little
  35. // room to avoid boundary condition problems. Also note that this will
  36. // cause us to start 2*EPSILON above the ground the next time that this
  37. // function is called, but for now, that appears to not be a problem.
  38. float MOVE_HEIGHT_EPSILON = 0.0625f;
  39. CON_COMMAND( ai_set_move_height_epsilon, "Set how high AI bumps up ground walkers when checking steps" )
  40. {
  41. if ( args.ArgC() > 1 )
  42. {
  43. float newEps = atof( args[1] );
  44. if ( newEps >= 0.0 && newEps < 1.0 )
  45. {
  46. MOVE_HEIGHT_EPSILON = newEps;
  47. }
  48. Msg( "Epsilon now %f\n", MOVE_HEIGHT_EPSILON );
  49. }
  50. }
  51. //-----------------------------------------------------------------------------
  52. BEGIN_SIMPLE_DATADESC(CAI_MoveProbe)
  53. // m_pTraceListData (not saved, a cached item)
  54. DEFINE_FIELD( m_bIgnoreTransientEntities, FIELD_BOOLEAN ),
  55. DEFINE_FIELD( m_hLastBlockingEnt, FIELD_EHANDLE ),
  56. END_DATADESC();
  57. //-----------------------------------------------------------------------------
  58. // Categorizes the blocker and sets the appropriate bits
  59. //-----------------------------------------------------------------------------
  60. AIMoveResult_t AIComputeBlockerMoveResult( CBaseEntity *pBlocker )
  61. {
  62. if (pBlocker->MyNPCPointer())
  63. return AIMR_BLOCKED_NPC;
  64. else if (pBlocker->entindex() == 0)
  65. return AIMR_BLOCKED_WORLD;
  66. return AIMR_BLOCKED_ENTITY;
  67. }
  68. //-----------------------------------------------------------------------------
  69. bool CAI_MoveProbe::ShouldBrushBeIgnored( CBaseEntity *pEntity )
  70. {
  71. if ( pEntity->m_iClassname == g_iszFuncBrushClassname )
  72. {
  73. CFuncBrush *pFuncBrush = assert_cast<CFuncBrush *>(pEntity);
  74. if ( pFuncBrush->m_iszExcludedClass == NULL_STRING )
  75. return false;
  76. // this is true if my class or entity name matches the exclusion name on the func brush
  77. #if HL2_EPISODIC
  78. bool nameMatches = ( pFuncBrush->m_iszExcludedClass == GetOuter()->m_iClassname ) || GetOuter()->NameMatches(pFuncBrush->m_iszExcludedClass);
  79. #else // do not match against entity name in base HL2 (just in case there is some case somewhere that might be broken by this)
  80. bool nameMatches = ( pFuncBrush->m_iszExcludedClass == GetOuter()->m_iClassname );
  81. #endif
  82. // return true (ignore brush) if the name matches, or, if exclusion is inverted, if the name does not match
  83. return ( pFuncBrush->m_bInvertExclusion ? !nameMatches : nameMatches );
  84. }
  85. return false;
  86. }
  87. //-----------------------------------------------------------------------------
  88. void CAI_MoveProbe::TraceLine( const Vector &vecStart, const Vector &vecEnd, unsigned int mask,
  89. bool bUseCollisionGroup, trace_t *pResult ) const
  90. {
  91. int collisionGroup = (bUseCollisionGroup) ?
  92. GetCollisionGroup() :
  93. COLLISION_GROUP_NONE;
  94. CTraceFilterNav traceFilter( const_cast<CAI_BaseNPC *>(GetOuter()), m_bIgnoreTransientEntities, GetOuter(), collisionGroup );
  95. AI_TraceLine( vecStart, vecEnd, mask, &traceFilter, pResult );
  96. #ifdef _DEBUG
  97. // Just to make sure; I'm not sure that this is always the case but it should be
  98. if (pResult->allsolid)
  99. {
  100. Assert( pResult->startsolid );
  101. }
  102. #endif
  103. }
  104. //-----------------------------------------------------------------------------
  105. CAI_MoveProbe::CAI_MoveProbe(CAI_BaseNPC *pOuter)
  106. : CAI_Component( pOuter ),
  107. m_bIgnoreTransientEntities( false ),
  108. m_pTraceListData( NULL )
  109. {
  110. }
  111. //-----------------------------------------------------------------------------
  112. CAI_MoveProbe::~CAI_MoveProbe()
  113. {
  114. enginetrace->FreeTraceListData( m_pTraceListData );
  115. }
  116. //-----------------------------------------------------------------------------
  117. void CAI_MoveProbe::TraceHull(
  118. const Vector &vecStart, const Vector &vecEnd, const Vector &hullMin,
  119. const Vector &hullMax, unsigned int mask, trace_t *pResult ) const
  120. {
  121. AI_PROFILE_SCOPE( CAI_MoveProbe_TraceHull );
  122. CTraceFilterNav traceFilter( const_cast<CAI_BaseNPC *>(GetOuter()), m_bIgnoreTransientEntities, GetOuter(), GetCollisionGroup() );
  123. Ray_t ray;
  124. ray.Init( vecStart, vecEnd, hullMin, hullMax );
  125. if ( !m_pTraceListData || m_pTraceListData->IsEmpty() )
  126. {
  127. enginetrace->TraceRay( ray, mask, &traceFilter, pResult );
  128. }
  129. else
  130. {
  131. enginetrace->TraceRayAgainstLeafAndEntityList( ray, (const_cast<CAI_MoveProbe *>(this)->m_pTraceListData), mask, &traceFilter, pResult );
  132. #if 0
  133. trace_t verificationTrace;
  134. enginetrace->TraceRay( ray, mask, &traceFilter, &verificationTrace );
  135. Assert( fabsf(verificationTrace.fraction - pResult->fraction) < 0.01 &&
  136. VectorsAreEqual( verificationTrace.endpos, pResult->endpos, 0.01 ) &&
  137. verificationTrace.m_pEnt == pResult->m_pEnt );
  138. #endif
  139. }
  140. if ( r_visualizetraces.GetBool() )
  141. DebugDrawLine( pResult->startpos, pResult->endpos, 255, 255, 0, true, -1.0f );
  142. //NDebugOverlay::SweptBox( vecStart, vecEnd, hullMin, hullMax, vec3_angle, 255, 255, 0, 0, 10 );
  143. // Just to make sure; I'm not sure that this is always the case but it should be
  144. Assert( !pResult->allsolid || pResult->startsolid );
  145. }
  146. //-----------------------------------------------------------------------------
  147. void CAI_MoveProbe::TraceHull( const Vector &vecStart, const Vector &vecEnd,
  148. unsigned int mask, trace_t *pResult ) const
  149. {
  150. TraceHull( vecStart, vecEnd, WorldAlignMins(), WorldAlignMaxs(), mask, pResult);
  151. }
  152. //-----------------------------------------------------------------------------
  153. void CAI_MoveProbe::SetupCheckStepTraceListData( const CheckStepArgs_t &args ) const
  154. {
  155. if ( ai_moveprobe_usetracelist.GetBool() )
  156. {
  157. Ray_t ray;
  158. Vector hullMin = WorldAlignMins();
  159. Vector hullMax = WorldAlignMaxs();
  160. hullMax.z += MOVE_HEIGHT_EPSILON;
  161. hullMin.z -= MOVE_HEIGHT_EPSILON;
  162. hullMax.z += args.stepHeight;
  163. hullMin.z -= args.stepHeight;
  164. if ( args.groundTest != STEP_DONT_CHECK_GROUND )
  165. hullMin.z -= args.stepHeight;
  166. hullMax.x += args.minStepLanding;
  167. hullMin.x -= args.minStepLanding;
  168. hullMax.y += args.minStepLanding;
  169. hullMin.y -= args.minStepLanding;
  170. Vector vecEnd;
  171. Vector2DMA( args.vecStart.AsVector2D(), args.stepSize, args.vecStepDir.AsVector2D(), vecEnd.AsVector2D() );
  172. vecEnd.z = args.vecStart.z;
  173. ray.Init( args.vecStart, vecEnd, hullMin, hullMax );
  174. if ( !m_pTraceListData )
  175. {
  176. const_cast<CAI_MoveProbe *>(this)->m_pTraceListData = enginetrace->AllocTraceListData();
  177. }
  178. enginetrace->SetupLeafAndEntityListRay( ray, (const_cast<CAI_MoveProbe *>(this)->m_pTraceListData) );
  179. }
  180. }
  181. //-----------------------------------------------------------------------------
  182. // Try to step over an obstacle when doing a large crawl
  183. //-----------------------------------------------------------------------------
  184. void CAI_MoveProbe::CheckStepOverLargeCrawl( CheckStepResult_t *pResult,
  185. const CheckStepArgs_t &args, const Vector &vecStart, const Vector &vecEnd, const trace_t &blockedTrace ) const
  186. {
  187. trace_t stepTrace, upTrace;
  188. Vector vecStepStart = blockedTrace.endpos;
  189. Vector vecStepEnd = vecEnd;
  190. while ( true )
  191. {
  192. // First, try to crawl upward.
  193. // Trace up to locate the maximum step up in the space
  194. Vector vecStepUp( vecStepStart );
  195. vecStepUp.z += args.stepHeight;
  196. TraceHull( vecStepStart, vecStepUp, args.collisionMask, &upTrace );
  197. bool bHitCeiling = ( upTrace.fraction < 1.0f );
  198. // Advance forward
  199. vecStepStart = upTrace.endpos;
  200. vecStepEnd.z = vecStepStart.z;
  201. TraceHull( vecStepStart, vecStepEnd, args.collisionMask, &stepTrace );
  202. if ( stepTrace.fraction == 1.0f )
  203. {
  204. // It's clear; we're done
  205. pResult->bCrawling = true;
  206. pResult->endPoint = vecStepEnd;
  207. pResult->pBlocker = NULL;
  208. pResult->fStartSolid = blockedTrace.startsolid;
  209. break;
  210. }
  211. // FIXME: if we hit a func_breakable, we want to ignore it for the purposes of nav
  212. if ( bHitCeiling || ( stepTrace.startsolid && blockedTrace.startsolid ) )
  213. {
  214. // Hit the ceiling, or started in solid and never escaped; we're done
  215. pResult->bCrawling = false;
  216. pResult->endPoint = blockedTrace.endpos;
  217. pResult->hitNormal = blockedTrace.plane.normal;
  218. pResult->pBlocker = blockedTrace.m_pEnt;
  219. pResult->fStartSolid = blockedTrace.startsolid;
  220. break;
  221. }
  222. }
  223. }
  224. //-----------------------------------------------------------------------------
  225. // CheckStep() is a fundamentally 2D operation! vecEnd.z is ignored.
  226. // We can step up one StepHeight or down one StepHeight from vecStart
  227. //-----------------------------------------------------------------------------
  228. bool g_bAIDebugStep = false;
  229. bool CAI_MoveProbe::CheckStep( const CheckStepArgs_t &args, CheckStepResult_t *pResult ) const
  230. {
  231. AI_PROFILE_SCOPE( CAI_MoveProbe_CheckStep );
  232. Vector vecEnd;
  233. unsigned collisionMask = args.collisionMask;
  234. VectorMA( args.vecStart, args.stepSize, args.vecStepDir, vecEnd );
  235. pResult->endPoint = args.vecStart;
  236. pResult->fStartSolid = false;
  237. pResult->hitNormal = vec3_origin;
  238. pResult->pBlocker = NULL;
  239. pResult->bCrawling = false;
  240. // This is fundamentally a 2D operation; we just want the end
  241. // position in Z to be no more than a step height from the start position
  242. Vector stepStart( args.vecStart.x, args.vecStart.y, args.vecStart.z + MOVE_HEIGHT_EPSILON );
  243. Vector stepEnd( vecEnd.x, vecEnd.y, args.vecStart.z + MOVE_HEIGHT_EPSILON );
  244. if ( g_bAIDebugStep )
  245. {
  246. NDebugOverlay::Line( stepStart, stepEnd, 255, 255, 255, true, 5 );
  247. NDebugOverlay::Cross3D( stepEnd, 32, 255, 255, 255, true, 5 );
  248. }
  249. trace_t trace;
  250. AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStep_Forward );
  251. TraceHull( stepStart, stepEnd, collisionMask, &trace );
  252. if ( trace.startsolid || ( trace.fraction < 1.0f ) )
  253. {
  254. if ( args.flags & AITGM_CRAWL_LARGE_STEPS )
  255. {
  256. CheckStepOverLargeCrawl( pResult, args, stepStart, stepEnd, trace );
  257. return ( pResult->pBlocker == NULL );
  258. }
  259. // Either the entity is starting embedded in the world, or it hit something.
  260. // Raise the box by the step height and try again
  261. trace_t stepTrace;
  262. if ( !trace.startsolid )
  263. {
  264. if ( g_bAIDebugStep )
  265. NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 64, 64, 64, 0, 5 );
  266. // Advance to first obstruction point
  267. stepStart = trace.endpos;
  268. // Trace up to locate the maximum step up in the space
  269. Vector stepUp( stepStart );
  270. stepUp.z += args.stepHeight;
  271. TraceHull( stepStart, stepUp, collisionMask, &stepTrace );
  272. if ( g_bAIDebugStep )
  273. NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins(), WorldAlignMaxs(), 96, 96, 96, 0, 5 );
  274. stepStart = stepTrace.endpos;
  275. }
  276. else
  277. {
  278. stepStart.z += args.stepHeight;
  279. }
  280. // Now move forward
  281. stepEnd.z = stepStart.z;
  282. TraceHull( stepStart, stepEnd, collisionMask, &stepTrace );
  283. bool bRejectStep = false;
  284. // Ok, raising it didn't work; we're obstructed
  285. if (stepTrace.startsolid || stepTrace.fraction <= 0.01 )
  286. {
  287. // If started in solid, and never escaped from solid, bail
  288. if ( trace.startsolid )
  289. {
  290. pResult->fStartSolid = true;
  291. pResult->pBlocker = trace.m_pEnt;
  292. pResult->hitNormal = trace.plane.normal;
  293. return false;
  294. }
  295. bRejectStep = true;
  296. }
  297. else
  298. {
  299. if ( g_bAIDebugStep )
  300. NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins(), WorldAlignMaxs(), 128, 128, 128, 0, 5 );
  301. // If didn't step forward enough to qualify as a step, try as if stepped forward to
  302. // confirm there's potentially enough space to "land"
  303. float landingDistSq = ( stepEnd.AsVector2D() - stepStart.AsVector2D() ).LengthSqr();
  304. float requiredLandingDistSq = args.minStepLanding*args.minStepLanding;
  305. if ( landingDistSq < requiredLandingDistSq )
  306. {
  307. trace_t landingTrace;
  308. Vector stepEndWithLanding;
  309. VectorMA( stepStart, args.minStepLanding, args.vecStepDir, stepEndWithLanding );
  310. TraceHull( stepStart, stepEndWithLanding, collisionMask, &landingTrace );
  311. if ( landingTrace.fraction < 1 )
  312. {
  313. if ( g_bAIDebugStep )
  314. NDebugOverlay::Box( landingTrace.endpos, WorldAlignMins() + Vector(0, 0, 0.1), WorldAlignMaxs() + Vector(0, 0, 0.1), 255, 0, 0, 0, 5 );
  315. bRejectStep = true;
  316. if ( landingTrace.m_pEnt )
  317. pResult->pBlocker = landingTrace.m_pEnt;
  318. }
  319. }
  320. else if ( ( stepTrace.endpos.AsVector2D() - stepStart.AsVector2D() ).LengthSqr() < requiredLandingDistSq )
  321. {
  322. if ( g_bAIDebugStep )
  323. NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins() + Vector(0, 0, 0.1), WorldAlignMaxs() + Vector(0, 0, 0.1), 255, 0, 0, 0, 5 );
  324. bRejectStep = true;
  325. }
  326. }
  327. // If trace.fraction == 0, we fall through and check the position
  328. // we moved up to for suitability. This allows for sub-step
  329. // traces if the position ends up being suitable
  330. if ( !bRejectStep )
  331. trace = stepTrace;
  332. if ( trace.fraction < 1.0 )
  333. {
  334. if ( !pResult->pBlocker )
  335. pResult->pBlocker = trace.m_pEnt;
  336. pResult->hitNormal = trace.plane.normal;
  337. }
  338. stepEnd = trace.endpos;
  339. }
  340. AI_PROFILE_SCOPE_END();
  341. // If we're not crawling up, do some checks to ensure we're on the floor
  342. if ( !pResult->bCrawling )
  343. {
  344. AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStep_Down );
  345. // seems okay, now find the ground
  346. // The ground is only valid if it's within a step height of the original position
  347. Assert( VectorsAreEqual( trace.endpos, stepEnd, 1e-3 ) );
  348. stepStart = stepEnd;
  349. stepEnd.z = args.vecStart.z - args.stepHeight * args.stepDownMultiplier - MOVE_HEIGHT_EPSILON;
  350. TraceHull( stepStart, stepEnd, collisionMask, &trace );
  351. if ( (args.flags & AITGM_CRAWL_LARGE_STEPS) && trace.fraction >= 1.0f )
  352. {
  353. // Now move backward... since we're crawling we don't need to test for the floor
  354. trace_t stepTrace;
  355. stepStart = trace.endpos;
  356. stepEnd = args.vecStart;
  357. stepEnd.z = stepStart.z;
  358. TraceHull( stepStart, stepEnd, collisionMask, &stepTrace );
  359. trace.endpos = stepTrace.endpos;
  360. pResult->bCrawling = true;
  361. }
  362. // If we're not crawling we can't allow stepping into empty space
  363. if ( !(args.flags & AITGM_CRAWL_LARGE_STEPS) )
  364. {
  365. // in empty space, lie and say we hit the world
  366. if (trace.fraction == 1.0f)
  367. {
  368. if ( g_bAIDebugStep )
  369. NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 5 );
  370. Assert( pResult->endPoint == args.vecStart );
  371. if ( const_cast<CAI_MoveProbe *>(this)->GetOuter()->GetGroundEntity() )
  372. {
  373. pResult->pBlocker = const_cast<CAI_MoveProbe *>(this)->GetOuter()->GetGroundEntity();
  374. }
  375. else
  376. {
  377. pResult->pBlocker = GetContainingEntity( INDEXENT(0) );
  378. }
  379. return false;
  380. }
  381. if ( g_bAIDebugStep )
  382. NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 160, 160, 160, 0, 5 );
  383. }
  384. AI_PROFILE_SCOPE_END();
  385. // If we're not crawling check that our floor is standable
  386. if ( !(args.flags & AITGM_CRAWL_LARGE_STEPS) )
  387. {
  388. // Checks to see if the thing we're on is a *type* of thing we
  389. // are capable of standing on. Always true for our current ground ent
  390. // otherwise we'll be stuck forever
  391. CBaseEntity *pFloor = trace.m_pEnt;
  392. if ( pFloor != GetOuter()->GetGroundEntity() && !CanStandOn( pFloor ) )
  393. {
  394. if ( g_bAIDebugStep )
  395. NDebugOverlay::Cross3D( trace.endpos, 32, 255, 0, 0, true, 5 );
  396. Assert( pResult->endPoint == args.vecStart );
  397. pResult->pBlocker = pFloor;
  398. return false;
  399. }
  400. // Don't step up onto an odd slope
  401. if ( trace.endpos.z - args.vecStart.z > args.stepHeight * 0.5 &&
  402. ( ( pFloor->IsWorld() && trace.hitbox > 0 ) ||
  403. dynamic_cast<CPhysicsProp *>( pFloor ) ) )
  404. {
  405. if ( fabsf( trace.plane.normal.Dot( Vector(1, 0, 0) ) ) > .4 )
  406. {
  407. Assert( pResult->endPoint == args.vecStart );
  408. pResult->pBlocker = pFloor;
  409. if ( g_bAIDebugStep )
  410. NDebugOverlay::Cross3D( trace.endpos, 32, 0, 0, 255, true, 5 );
  411. return false;
  412. }
  413. }
  414. if (args.groundTest != STEP_DONT_CHECK_GROUND )
  415. {
  416. AI_PROFILE_SCOPE( CAI_Motor_CheckStep_Stand );
  417. // Next, check to see if we can *geometrically* stand on the floor
  418. bool bIsFloorFlat = CheckStandPosition( trace.endpos, collisionMask );
  419. if (args.groundTest != STEP_ON_INVALID_GROUND && !bIsFloorFlat)
  420. {
  421. pResult->pBlocker = pFloor;
  422. if ( g_bAIDebugStep )
  423. NDebugOverlay::Cross3D( trace.endpos, 32, 255, 0, 255, true, 5 );
  424. return false;
  425. }
  426. // If we started on shaky ground (namely, it's not geometrically ok),
  427. // then we can continue going even if we remain on shaky ground.
  428. // This allows NPCs who have been blown into an invalid area to get out
  429. // of that invalid area and into a valid area. As soon as we're in
  430. // a valid area, though, we're not allowed to leave it.
  431. }
  432. }
  433. }
  434. // Return a point that is *on the ground*
  435. // We'll raise it by an epsilon in check step again
  436. pResult->endPoint = trace.endpos;
  437. pResult->endPoint.z += MOVE_HEIGHT_EPSILON; // always safe because always stepped down at least by epsilon
  438. if ( g_bAIDebugStep )
  439. NDebugOverlay::Cross3D( trace.endpos, 32, 0, 255, 0, true, 5 );
  440. return ( pResult->pBlocker == NULL ); // totally clear if pBlocker is NULL, partial blockage otherwise
  441. }
  442. //-----------------------------------------------------------------------------
  443. // Confirm 3D connectivity between 2 nodes
  444. //-----------------------------------------------------------------------------
  445. bool CAI_MoveProbe::Confirm3DConnectivity( AIMoveTrace_t *pMoveTrace, unsigned flags, const Vector &vecDesiredEnd ) const
  446. {
  447. // FIXME: If you started on a ledge and ended on a ledge,
  448. // should it return an error condition (that you hit the world)?
  449. // Certainly not for Step(), but maybe for GroundMoveLimit()?
  450. // Make sure we actually made it to the target position
  451. // and not a ledge above or below the target.
  452. if ( flags & AITGM_2D )
  453. return true;
  454. float threshold = MAX( 0.5f * GetHullHeight(), StepHeight() + 0.1 );
  455. if ( fabs( pMoveTrace->vEndPosition.z - vecDesiredEnd.z ) > threshold )
  456. {
  457. #if 0
  458. NDebugOverlay::Cross3D( vecDesiredEnd, 8, 0, 255, 0, false, 0.1 );
  459. NDebugOverlay::Cross3D( pMoveTrace->vEndPosition, 8, 255, 0, 0, false, 0.1 );
  460. #endif
  461. // Ok, we ended up on a ledge above or below the desired destination
  462. pMoveTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
  463. pMoveTrace->vHitNormal = vec3_origin;
  464. pMoveTrace->fStatus = AIMR_BLOCKED_WORLD;
  465. pMoveTrace->flDistObstructed = ComputePathDistance( NAV_GROUND, pMoveTrace->vEndPosition, vecDesiredEnd );
  466. return false;
  467. }
  468. return true;
  469. }
  470. //-----------------------------------------------------------------------------
  471. // Checks a ground-based movement
  472. // NOTE: The movement will be based on an *actual* start position and
  473. // a *desired* end position; it works this way because the ground-based movement
  474. // is 2 1/2D, and we may end up on a ledge above or below the actual desired endpoint.
  475. //-----------------------------------------------------------------------------
  476. bool CAI_MoveProbe::TestGroundMove( const Vector &vecActualStart, const Vector &vecDesiredEnd,
  477. unsigned int collisionMask, float pctToCheckStandPositions, unsigned flags, AIMoveTrace_t *pMoveTrace ) const
  478. {
  479. AIMoveTrace_t ignored;
  480. if ( !pMoveTrace )
  481. pMoveTrace = &ignored;
  482. // Set a reasonable default set of values
  483. pMoveTrace->flDistObstructed = 0.0f;
  484. pMoveTrace->pObstruction = NULL;
  485. pMoveTrace->vHitNormal = vec3_origin;
  486. pMoveTrace->fStatus = AIMR_OK;
  487. pMoveTrace->vEndPosition = vecActualStart;
  488. pMoveTrace->flStepUpDistance = 0;
  489. Vector vecMoveDir;
  490. pMoveTrace->flTotalDist = ComputePathDirection( NAV_GROUND, vecActualStart, vecDesiredEnd, &vecMoveDir );
  491. if (pMoveTrace->flTotalDist == 0.0f)
  492. return Confirm3DConnectivity( pMoveTrace, flags, vecDesiredEnd );
  493. // If it starts hanging over an edge, tough it out until it's not
  494. // This allows us to blow an NPC in an invalid region + allow him to walk out
  495. StepGroundTest_t groundTest;
  496. if ( (flags & AITGM_IGNORE_FLOOR) || pctToCheckStandPositions < 0.001 )
  497. {
  498. groundTest = STEP_DONT_CHECK_GROUND;
  499. pctToCheckStandPositions = 0; // AITGM_IGNORE_FLOOR always overrides pct
  500. }
  501. else
  502. {
  503. if ( pctToCheckStandPositions > 99.999 )
  504. pctToCheckStandPositions = 100;
  505. if ((flags & AITGM_IGNORE_INITIAL_STAND_POS) || CheckStandPosition(vecActualStart, collisionMask))
  506. groundTest = STEP_ON_VALID_GROUND;
  507. else
  508. groundTest = STEP_ON_INVALID_GROUND;
  509. }
  510. if ( ( flags & AITGM_DRAW_RESULTS ) && !CheckStandPosition(vecActualStart, collisionMask) )
  511. {
  512. NDebugOverlay::Cross3D( vecActualStart, 16, 128, 0, 0, true, 2.0 );
  513. }
  514. // Take single steps towards the goal
  515. float distClear = 0;
  516. int i;
  517. CheckStepArgs_t checkStepArgs;
  518. CheckStepResult_t checkStepResult;
  519. checkStepArgs.vecStart = vecActualStart;
  520. checkStepArgs.vecStepDir = vecMoveDir;
  521. checkStepArgs.stepSize = 0;
  522. checkStepArgs.stepHeight = StepHeight();
  523. checkStepArgs.stepDownMultiplier = GetOuter()->GetStepDownMultiplier();
  524. checkStepArgs.minStepLanding = GetHullWidth() * 0.3333333;
  525. checkStepArgs.collisionMask = collisionMask;
  526. checkStepArgs.groundTest = groundTest;
  527. checkStepArgs.flags = flags;
  528. checkStepResult.endPoint = vecActualStart;
  529. checkStepResult.hitNormal = vec3_origin;
  530. checkStepResult.pBlocker = NULL;
  531. float distStartToIgnoreGround = (pctToCheckStandPositions == 100) ? pMoveTrace->flTotalDist : pMoveTrace->flTotalDist * ( pctToCheckStandPositions * 0.01);
  532. bool bTryNavIgnore = ( ( vecActualStart - GetLocalOrigin() ).Length2DSqr() < 0.1 && fabsf(vecActualStart.z - GetLocalOrigin().z) < checkStepArgs.stepHeight * 0.5 );
  533. CUtlVector<CBaseEntity *> ignoredEntities;
  534. for (;;)
  535. {
  536. float flStepSize = MIN( LOCAL_STEP_SIZE, pMoveTrace->flTotalDist - distClear );
  537. if ( flStepSize < 0.001 )
  538. break;
  539. checkStepArgs.stepSize = flStepSize;
  540. if ( distClear - distStartToIgnoreGround > 0.001 )
  541. checkStepArgs.groundTest = STEP_DONT_CHECK_GROUND;
  542. Assert( !m_pTraceListData || m_pTraceListData->IsEmpty() );
  543. SetupCheckStepTraceListData( checkStepArgs );
  544. for ( i = 0; i < 16; i++ )
  545. {
  546. CheckStep( checkStepArgs, &checkStepResult );
  547. if ( !bTryNavIgnore || !checkStepResult.pBlocker || !checkStepResult.fStartSolid )
  548. break;
  549. if ( checkStepResult.pBlocker->GetMoveType() != MOVETYPE_VPHYSICS && !checkStepResult.pBlocker->IsNPC() )
  550. break;
  551. // Only permit pass through of objects initially embedded in
  552. if ( vecActualStart != checkStepArgs.vecStart )
  553. {
  554. bTryNavIgnore = false;
  555. break;
  556. }
  557. // Only allow move away from physics objects
  558. if ( checkStepResult.pBlocker->GetMoveType() == MOVETYPE_VPHYSICS )
  559. {
  560. Vector vMoveDir = vecDesiredEnd - vecActualStart;
  561. VectorNormalize( vMoveDir );
  562. Vector vObstacleDir = (checkStepResult.pBlocker->WorldSpaceCenter() - GetOuter()->WorldSpaceCenter() );
  563. VectorNormalize( vObstacleDir );
  564. if ( vMoveDir.Dot( vObstacleDir ) >= 0 )
  565. break;
  566. }
  567. if ( ( flags & AITGM_DRAW_RESULTS ) && checkStepResult.fStartSolid && checkStepResult.pBlocker->IsNPC() )
  568. {
  569. NDebugOverlay::EntityBounds( GetOuter(), 0, 0, 255, 0, .5 );
  570. NDebugOverlay::EntityBounds( checkStepResult.pBlocker, 255, 0, 0, 0, .5 );
  571. }
  572. ignoredEntities.AddToTail( checkStepResult.pBlocker );
  573. checkStepResult.pBlocker->SetNavIgnore();
  574. }
  575. ResetTraceListData();
  576. if ( flags & AITGM_DRAW_RESULTS )
  577. {
  578. if ( !CheckStandPosition(checkStepResult.endPoint, collisionMask) )
  579. {
  580. NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 0.1 );
  581. NDebugOverlay::Cross3D( checkStepResult.endPoint, 16, 255, 0, 0, true, 0.1 );
  582. }
  583. else
  584. {
  585. NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 0, 255, 0, 0, 0.1 );
  586. NDebugOverlay::Cross3D( checkStepResult.endPoint, 16, 0, 255, 0, true, 0.1 );
  587. }
  588. }
  589. // If we're being blocked by something, move as close as we can and stop
  590. if ( checkStepResult.pBlocker )
  591. {
  592. distClear += ( checkStepResult.endPoint - checkStepArgs.vecStart ).Length2D();
  593. if ( checkStepResult.bCrawling )
  594. {
  595. // Weren't not really blocked when crawling up, but need to do it in steps
  596. checkStepResult.pBlocker = NULL;
  597. }
  598. break;
  599. }
  600. // If we're not crawling up add on the z change to the distance
  601. float dz = checkStepResult.endPoint.z - checkStepArgs.vecStart.z;
  602. if ( dz < 0 )
  603. {
  604. dz = 0;
  605. }
  606. pMoveTrace->flStepUpDistance += dz;
  607. distClear += flStepSize;
  608. checkStepArgs.vecStart = checkStepResult.endPoint;
  609. }
  610. for ( i = 0; i < ignoredEntities.Count(); i++ )
  611. {
  612. ignoredEntities[i]->ClearNavIgnore();
  613. }
  614. pMoveTrace->vEndPosition = checkStepResult.endPoint;
  615. if ( checkStepResult.pBlocker )
  616. {
  617. pMoveTrace->pObstruction = checkStepResult.pBlocker;
  618. pMoveTrace->vHitNormal = checkStepResult.hitNormal;
  619. pMoveTrace->fStatus = AIComputeBlockerMoveResult( checkStepResult.pBlocker );
  620. pMoveTrace->flDistObstructed = pMoveTrace->flTotalDist - distClear;
  621. if ( flags & AITGM_DRAW_RESULTS )
  622. {
  623. NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 0.5 );
  624. }
  625. return false;
  626. }
  627. return Confirm3DConnectivity( pMoveTrace, flags, vecDesiredEnd );
  628. }
  629. //-----------------------------------------------------------------------------
  630. // Tries to generate a route from the specified start to end positions
  631. // Will return the results of the attempt in the AIMoveTrace_t structure
  632. //-----------------------------------------------------------------------------
  633. void CAI_MoveProbe::GroundMoveLimit( const Vector &vecStart, const Vector &vecEnd,
  634. unsigned int collisionMask, const CBaseEntity *pTarget, unsigned testGroundMoveFlags, float pctToCheckStandPositions, AIMoveTrace_t* pTrace ) const
  635. {
  636. // NOTE: Never call this directly!!! Always use MoveLimit!!
  637. // This assertion should ensure this happens
  638. Assert( !IsMoveBlocked( *pTrace ) );
  639. AI_PROFILE_SCOPE( CAI_Motor_GroundMoveLimit );
  640. Vector vecActualStart, vecDesiredEnd;
  641. pTrace->flTotalDist = ComputePathDistance( NAV_GROUND, vecStart, vecEnd );
  642. if ( !IterativeFloorPoint( vecStart, collisionMask, &vecActualStart ) )
  643. {
  644. pTrace->flDistObstructed = pTrace->flTotalDist;
  645. pTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
  646. pTrace->vHitNormal = vec3_origin;
  647. pTrace->fStatus = AIMR_BLOCKED_WORLD;
  648. pTrace->vEndPosition = vecStart;
  649. //DevMsg( "Warning: attempting to path from/to a point that is in solid space or is too high\n" );
  650. return;
  651. }
  652. // find out where they (in theory) should have ended up
  653. if (!(testGroundMoveFlags & AITGM_2D))
  654. IterativeFloorPoint( vecEnd, collisionMask, &vecDesiredEnd );
  655. else
  656. vecDesiredEnd = vecEnd;
  657. // When checking the route, look for ground geometric validity
  658. // Let's try to avoid invalid routes
  659. TestGroundMove( vecActualStart, vecDesiredEnd, collisionMask, pctToCheckStandPositions, testGroundMoveFlags, pTrace );
  660. // Check to see if the target is in a vehicle and the vehicle is blocking our way
  661. bool bVehicleMatchesObstruction = false;
  662. if ( pTarget != NULL )
  663. {
  664. CBaseCombatCharacter *pCCTarget = ((CBaseEntity *)pTarget)->MyCombatCharacterPointer();
  665. if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() )
  666. {
  667. CBaseEntity *pVehicleEnt = pCCTarget->GetVehicleEntity();
  668. if ( pVehicleEnt == pTrace->pObstruction )
  669. bVehicleMatchesObstruction = true;
  670. }
  671. }
  672. if ( (pTarget && (pTarget == pTrace->pObstruction)) || bVehicleMatchesObstruction )
  673. {
  674. // Collided with target entity, return there was no collision!!
  675. // but leave the end trace position
  676. pTrace->flDistObstructed = 0.0f;
  677. pTrace->pObstruction = NULL;
  678. pTrace->vHitNormal = vec3_origin;
  679. pTrace->fStatus = AIMR_OK;
  680. }
  681. }
  682. //-----------------------------------------------------------------------------
  683. // Purpose: returns zero if the caller can walk a straight line from
  684. // vecStart to vecEnd ignoring collisions with pTarget
  685. //
  686. // if the move fails, returns the distance remaining to vecEnd
  687. //-----------------------------------------------------------------------------
  688. void CAI_MoveProbe::FlyMoveLimit( const Vector &vecStart, const Vector &vecEnd,
  689. unsigned int collisionMask, const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
  690. {
  691. // NOTE: Never call this directly!!! Always use MoveLimit!!
  692. // This assertion should ensure this happens
  693. Assert( !IsMoveBlocked( *pMoveTrace) );
  694. trace_t tr;
  695. TraceHull( vecStart, vecEnd, collisionMask, &tr );
  696. if ( tr.fraction < 1 )
  697. {
  698. CBaseEntity *pBlocker = tr.m_pEnt;
  699. if ( pBlocker )
  700. {
  701. if ( pTarget == pBlocker )
  702. {
  703. // Colided with target entity, movement is ok
  704. pMoveTrace->vEndPosition = tr.endpos;
  705. return;
  706. }
  707. // If blocked by an npc remember
  708. pMoveTrace->pObstruction = pBlocker;
  709. pMoveTrace->vHitNormal = vec3_origin;
  710. pMoveTrace->fStatus = AIComputeBlockerMoveResult( pBlocker );
  711. }
  712. pMoveTrace->flDistObstructed = ComputePathDistance( NAV_FLY, tr.endpos, vecEnd );
  713. pMoveTrace->vEndPosition = tr.endpos;
  714. return;
  715. }
  716. // no collisions, movement is ok
  717. pMoveTrace->vEndPosition = vecEnd;
  718. }
  719. //-----------------------------------------------------------------------------
  720. // Purpose: returns zero if the caller can jump from
  721. // vecStart to vecEnd ignoring collisions with pTarget
  722. //
  723. // if the jump fails, returns the distance
  724. // that can be travelled before an obstacle is hit
  725. //-----------------------------------------------------------------------------
  726. void CAI_MoveProbe::JumpMoveLimit( const Vector &vecStart, const Vector &vecEnd,
  727. unsigned int collisionMask, const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
  728. {
  729. pMoveTrace->vJumpVelocity.Init( 0, 0, 0 );
  730. float flDist = ComputePathDistance( NAV_JUMP, vecStart, vecEnd );
  731. if (!IsJumpLegal(vecStart, vecEnd, vecEnd))
  732. {
  733. pMoveTrace->fStatus = AIMR_ILLEGAL;
  734. pMoveTrace->flDistObstructed = flDist;
  735. return;
  736. }
  737. // --------------------------------------------------------------------------
  738. // Drop start and end vectors to the floor and check to see if they're legal
  739. // --------------------------------------------------------------------------
  740. Vector vecFrom;
  741. IterativeFloorPoint( vecStart, collisionMask, &vecFrom );
  742. Vector vecTo;
  743. IterativeFloorPoint( vecEnd, collisionMask, StepHeight() * 0.5, &vecTo );
  744. if (!CheckStandPosition( vecTo, collisionMask))
  745. {
  746. pMoveTrace->fStatus = AIMR_ILLEGAL;
  747. pMoveTrace->flDistObstructed = flDist;
  748. return;
  749. }
  750. if (vecFrom == vecTo)
  751. {
  752. pMoveTrace->fStatus = AIMR_ILLEGAL;
  753. pMoveTrace->flDistObstructed = flDist;
  754. return;
  755. }
  756. if ((vecFrom - vecTo).Length2D() == 0.0)
  757. {
  758. pMoveTrace->fStatus = AIMR_ILLEGAL;
  759. pMoveTrace->flDistObstructed = flDist;
  760. return;
  761. }
  762. // FIXME: add MAX jump velocity callback? Look at the velocity in the jump animation? use ideal running speed?
  763. float maxHorzVel = GetOuter()->GetMaxJumpSpeed();
  764. Vector gravity = Vector(0, 0, sv_gravity.GetFloat() * GetOuter()->GetJumpGravity() );
  765. if ( gravity.z < 0.01 )
  766. {
  767. pMoveTrace->fStatus = AIMR_ILLEGAL;
  768. pMoveTrace->flDistObstructed = flDist;
  769. return;
  770. }
  771. // intialize error state to it being an illegal jump
  772. CBaseEntity *pObstruction = NULL;
  773. AIMoveResult_t fStatus = AIMR_ILLEGAL;
  774. float flDistObstructed = flDist;
  775. // initialize jump state
  776. float minSuccessfulJumpHeight = 1024.0;
  777. float minJumpHeight = 0.0;
  778. float minJumpStep = 1024.0;
  779. // initial jump, sets baseline for minJumpHeight
  780. Vector vecApex;
  781. Vector rawJumpVel = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minJumpHeight, maxHorzVel, &vecApex );
  782. float flNPCMinJumpHeight = GetOuter()->GetMinJumpHeight();
  783. if ( flNPCMinJumpHeight && minJumpHeight < flNPCMinJumpHeight )
  784. {
  785. minJumpHeight = flNPCMinJumpHeight;
  786. }
  787. float baselineJumpHeight = minJumpHeight;
  788. // FIXME: this is a binary search, which really isn't the right thing to do. If there's a gap
  789. // the npc can jump through, this won't reliably find it. The only way I can think to do this is a
  790. // linear search trying ever higher jumps until the gap is either found or the jump is illegal.
  791. do
  792. {
  793. rawJumpVel = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minJumpHeight, maxHorzVel, &vecApex );
  794. // DevMsg( "%.0f ", minJumpHeight );
  795. if (!IsJumpLegal(vecFrom, vecApex, vecTo))
  796. {
  797. // too high, try lower
  798. minJumpStep = minJumpStep / 2.0;
  799. minJumpHeight = minJumpHeight - minJumpStep;
  800. }
  801. else
  802. {
  803. // Calculate the total time of the jump minus a tiny fraction
  804. float jumpTime = (vecFrom - vecTo).Length2D()/rawJumpVel.Length2D();
  805. float timeStep = jumpTime / 10.0;
  806. Vector vecTest = vecFrom;
  807. bool bMadeIt = true;
  808. // this sweeps out a rough approximation of the jump
  809. // FIXME: this won't reliably hit the apex
  810. for (float flTime = 0 ; flTime < jumpTime - 0.01; flTime += timeStep )
  811. {
  812. trace_t trace;
  813. // Calculate my position after the time step (average velocity over this time step)
  814. Vector nextPos = vecTest + (rawJumpVel - 0.5 * gravity * timeStep) * timeStep;
  815. TraceHull( vecTest, nextPos, collisionMask, &trace );
  816. if (trace.startsolid || trace.fraction < 0.99) // FIXME: getting inconsistant trace fractions, revisit after Jay resolves collision eplisons
  817. {
  818. // NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 255, 255, 0, 0, 10.0 );
  819. // save error state
  820. pObstruction = trace.m_pEnt;
  821. fStatus = AIComputeBlockerMoveResult( pObstruction );
  822. flDistObstructed = ComputePathDistance( NAV_JUMP, vecTest, vecTo );
  823. if (trace.plane.normal.z < 0.0)
  824. {
  825. // hit a ceiling looking thing, too high, try lower
  826. minJumpStep = minJumpStep / 2.0;
  827. minJumpHeight = minJumpHeight - minJumpStep;
  828. }
  829. else
  830. {
  831. // hit wall looking thing, try higher
  832. minJumpStep = minJumpStep / 2.0;
  833. minJumpHeight += minJumpStep;
  834. }
  835. if ( ai_moveprobe_jump_debug.GetBool() )
  836. {
  837. NDebugOverlay::Line( vecTest, nextPos, 255, 0, 0, true, 2.0f );
  838. }
  839. bMadeIt = false;
  840. break;
  841. }
  842. else
  843. {
  844. if ( ai_moveprobe_jump_debug.GetBool() )
  845. {
  846. NDebugOverlay::Line( vecTest, nextPos, 0, 255, 0, true, 2.0f );
  847. }
  848. }
  849. rawJumpVel = rawJumpVel - gravity * timeStep;
  850. vecTest = nextPos;
  851. }
  852. if (bMadeIt)
  853. {
  854. // made it, try lower
  855. minSuccessfulJumpHeight = minJumpHeight;
  856. minJumpStep = minJumpStep / 2.0;
  857. minJumpHeight -= minJumpStep;
  858. }
  859. }
  860. }
  861. while (minJumpHeight > baselineJumpHeight && minJumpHeight <= 1024.0 && minJumpStep >= 16.0);
  862. // DevMsg( "(%.0f)\n", minSuccessfulJumpHeight );
  863. if (minSuccessfulJumpHeight != 1024.0)
  864. {
  865. // Get my jump velocity
  866. pMoveTrace->vJumpVelocity = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minSuccessfulJumpHeight, maxHorzVel, &vecApex );
  867. }
  868. else
  869. {
  870. // ----------------------------------------------------------
  871. // If blocked by an npc remember
  872. // ----------------------------------------------------------
  873. pMoveTrace->pObstruction = pObstruction;
  874. pMoveTrace->vHitNormal = vec3_origin;
  875. pMoveTrace->fStatus = fStatus;
  876. pMoveTrace->flDistObstructed = flDistObstructed;
  877. }
  878. }
  879. //-----------------------------------------------------------------------------
  880. // Purpose: returns zero if the caller can climb from
  881. // vecStart to vecEnd ignoring collisions with pTarget
  882. //
  883. // if the climb fails, returns the distance remaining
  884. // before the obstacle is hit
  885. //-----------------------------------------------------------------------------
  886. void CAI_MoveProbe::ClimbMoveLimit( const Vector &vecStart, const Vector &vecEnd,
  887. const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
  888. {
  889. trace_t tr;
  890. TraceHull( vecStart, vecEnd, GetOuter()->GetAITraceMask(), &tr );
  891. if (tr.fraction < 1.0)
  892. {
  893. CBaseEntity *pEntity = tr.m_pEnt;
  894. if (pEntity == pTarget)
  895. {
  896. return;
  897. }
  898. else
  899. {
  900. // ----------------------------------------------------------
  901. // If blocked by an npc remember
  902. // ----------------------------------------------------------
  903. pMoveTrace->pObstruction = pEntity;
  904. pMoveTrace->vHitNormal = vec3_origin;
  905. pMoveTrace->fStatus = AIComputeBlockerMoveResult( pEntity );
  906. float flDist = (1.0 - tr.fraction) * ComputePathDistance( NAV_CLIMB, vecStart, vecEnd );
  907. if (flDist <= 0.001)
  908. {
  909. flDist = 0.001;
  910. }
  911. pMoveTrace->flDistObstructed = flDist;
  912. return;
  913. }
  914. }
  915. }
  916. //-----------------------------------------------------------------------------
  917. // Purpose:
  918. //-----------------------------------------------------------------------------
  919. bool CAI_MoveProbe::MoveLimit( Navigation_t navType, const Vector &vecStart,
  920. const Vector &vecEnd, unsigned int collisionMask, const CBaseEntity *pTarget,
  921. float pctToCheckStandPositions, unsigned flags, AIMoveTrace_t* pTrace)
  922. {
  923. AIMoveTrace_t ignoredTrace;
  924. if ( !pTrace )
  925. pTrace = &ignoredTrace;
  926. // Set a reasonable default set of values
  927. pTrace->flTotalDist = ComputePathDistance( navType, vecStart, vecEnd );
  928. pTrace->flDistObstructed = 0.0f;
  929. pTrace->pObstruction = NULL;
  930. pTrace->vHitNormal = vec3_origin;
  931. pTrace->fStatus = AIMR_OK;
  932. pTrace->vEndPosition = vecStart;
  933. switch (navType)
  934. {
  935. case NAV_CRAWL:
  936. case NAV_GROUND:
  937. {
  938. unsigned testGroundMoveFlags = AITGM_DEFAULT;
  939. if (flags & AIMLF_2D )
  940. testGroundMoveFlags |= AITGM_2D;
  941. if ( flags & AIMLF_DRAW_RESULTS )
  942. testGroundMoveFlags |= AITGM_DRAW_RESULTS;
  943. if ( ai_moveprobe_debug.GetBool() && (GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
  944. testGroundMoveFlags |= AITGM_DRAW_RESULTS;
  945. if ( flags & AIMLF_IGNORE_TRANSIENTS )
  946. const_cast<CAI_MoveProbe *>(this)->m_bIgnoreTransientEntities = true;
  947. bool bDoIt = true;
  948. if ( flags & AIMLF_QUICK_REJECT )
  949. {
  950. Assert( vecStart == GetLocalOrigin() );
  951. trace_t tr;
  952. TraceLine(const_cast<CAI_MoveProbe *>(this)->GetOuter()->EyePosition(), vecEnd, collisionMask, true, &tr);
  953. bDoIt = ( tr.fraction > 0.99 );
  954. }
  955. if ( navType == NAV_CRAWL )
  956. {
  957. testGroundMoveFlags |= AITGM_CRAWL_LARGE_STEPS;
  958. }
  959. if ( bDoIt )
  960. GroundMoveLimit(vecStart, vecEnd, collisionMask, pTarget, testGroundMoveFlags, pctToCheckStandPositions, pTrace);
  961. else
  962. {
  963. pTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
  964. pTrace->vHitNormal = vec3_origin;
  965. pTrace->fStatus = AIMR_BLOCKED_WORLD;
  966. pTrace->flDistObstructed = ComputePathDistance( NAV_GROUND, vecStart, vecEnd );
  967. }
  968. const_cast<CAI_MoveProbe *>(this)->m_bIgnoreTransientEntities = false;
  969. break;
  970. }
  971. case NAV_FLY:
  972. FlyMoveLimit(vecStart, vecEnd, collisionMask, pTarget, pTrace);
  973. break;
  974. case NAV_JUMP:
  975. JumpMoveLimit(vecStart, vecEnd, collisionMask, pTarget, pTrace);
  976. break;
  977. case NAV_CLIMB:
  978. ClimbMoveLimit(vecStart, vecEnd, pTarget, pTrace);
  979. break;
  980. default:
  981. pTrace->fStatus = AIMR_ILLEGAL;
  982. pTrace->flDistObstructed = ComputePathDistance( navType, vecStart, vecEnd );
  983. break;
  984. }
  985. if (IsMoveBlocked(pTrace->fStatus) && pTrace->pObstruction && !pTrace->pObstruction->IsWorld())
  986. {
  987. m_hLastBlockingEnt = pTrace->pObstruction;
  988. }
  989. return !IsMoveBlocked(pTrace->fStatus);
  990. }
  991. //-----------------------------------------------------------------------------
  992. // Purpose: Returns a jump lauch velocity for the current target entity
  993. // Input :
  994. // Output :
  995. //-----------------------------------------------------------------------------
  996. Vector CAI_MoveProbe::CalcJumpLaunchVelocity(const Vector &startPos, const Vector &endPos, float flGravity, float *pminHeight, float maxHorzVelocity, Vector *pvecApex ) const
  997. {
  998. // Get the height I have to jump to get to the target
  999. float stepHeight = endPos.z - startPos.z;
  1000. // get horizontal distance to target
  1001. Vector targetDir2D = endPos - startPos;
  1002. targetDir2D.z = 0;
  1003. float distance = VectorNormalize(targetDir2D);
  1004. Assert( maxHorzVelocity > 0 );
  1005. // get minimum times and heights to meet ideal horz velocity
  1006. float minHorzTime = distance / maxHorzVelocity;
  1007. float minHorzHeight = 0.5 * flGravity * (minHorzTime * 0.5) * (minHorzTime * 0.5);
  1008. // jump height must be enough to hang in the air
  1009. *pminHeight = MAX( *pminHeight, minHorzHeight );
  1010. // jump height must be enough to cover the step up
  1011. *pminHeight = MAX( *pminHeight, stepHeight );
  1012. // time from start to apex
  1013. float t0 = sqrt( ( 2.0 * *pminHeight) / flGravity );
  1014. // time from apex to end
  1015. float t1 = sqrt( ( 2.0 * fabs( *pminHeight - stepHeight) ) / flGravity );
  1016. float velHorz = distance / (t0 + t1);
  1017. Vector jumpVel = targetDir2D * velHorz;
  1018. jumpVel.z = (float)sqrt(2.0f * flGravity * (*pminHeight));
  1019. if (pvecApex)
  1020. {
  1021. *pvecApex = startPos + targetDir2D * velHorz * t0 + Vector( 0, 0, *pminHeight );
  1022. }
  1023. // -----------------------------------------------------------
  1024. // Make the horizontal jump vector and add vertical component
  1025. // -----------------------------------------------------------
  1026. return jumpVel;
  1027. }
  1028. //-----------------------------------------------------------------------------
  1029. bool CAI_MoveProbe::CheckStandPosition( const Vector &vecStart, unsigned int collisionMask ) const
  1030. {
  1031. // If we're not supposed to do ground checks, always say we can stand there
  1032. if ( (GetOuter()->CapabilitiesGet() & bits_CAP_SKIP_NAV_GROUND_CHECK) )
  1033. return true;
  1034. // This is an extra-strong optimization
  1035. if ( ai_strong_optimizations_no_checkstand.GetBool() )
  1036. return true;
  1037. if ( UseOldCheckStandPosition() )
  1038. return OldCheckStandPosition( vecStart, collisionMask );
  1039. AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition );
  1040. Vector contactMin, contactMax;
  1041. // this should assume the model is already standing
  1042. Vector vecUp = Vector( vecStart.x, vecStart.y, vecStart.z + 0.1 );
  1043. Vector vecDown = Vector( vecStart.x, vecStart.y, vecStart.z - StepHeight() * GetOuter()->GetStepDownMultiplier() );
  1044. // check a half sized box centered around the foot
  1045. Vector vHullMins = WorldAlignMins();
  1046. Vector vHullMaxs = WorldAlignMaxs();
  1047. if ( vHullMaxs == vec3_origin && vHullMins == vHullMaxs )
  1048. {
  1049. // "Test hulls" have no collision property
  1050. vHullMins = GetHullMins();
  1051. vHullMaxs = GetHullMaxs();
  1052. }
  1053. contactMin.x = vHullMins.x * 0.75 + vHullMaxs.x * 0.25;
  1054. contactMax.x = vHullMins.x * 0.25 + vHullMaxs.x * 0.75;
  1055. contactMin.y = vHullMins.y * 0.75 + vHullMaxs.y * 0.25;
  1056. contactMax.y = vHullMins.y * 0.25 + vHullMaxs.y * 0.75;
  1057. contactMin.z = vHullMins.z;
  1058. contactMax.z = vHullMins.z;
  1059. trace_t trace1, trace2;
  1060. if ( !GetOuter()->IsFlaggedEfficient() )
  1061. {
  1062. AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Sides );
  1063. Vector vHullBottomCenter;
  1064. vHullBottomCenter.Init( 0, 0, vHullMins.z );
  1065. // Try diagonal from lower left to upper right
  1066. TraceHull( vecUp, vecDown, contactMin, vHullBottomCenter, collisionMask, &trace1 );
  1067. if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
  1068. {
  1069. TraceHull( vecUp, vecDown, vHullBottomCenter, contactMax, collisionMask, &trace2 );
  1070. if ( trace2.fraction != 1.0 && ( trace1.m_pEnt == trace2.m_pEnt || CanStandOn( trace2.m_pEnt ) ) )
  1071. {
  1072. return true;
  1073. }
  1074. }
  1075. // Okay, try the other one
  1076. Vector testMin;
  1077. Vector testMax;
  1078. testMin.Init(contactMin.x, 0, vHullMins.z);
  1079. testMax.Init(0, contactMax.y, vHullMins.z);
  1080. TraceHull( vecUp, vecDown, testMin, testMax, collisionMask, &trace1 );
  1081. if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
  1082. {
  1083. testMin.Init(0, contactMin.y, vHullMins.z);
  1084. testMax.Init(contactMax.x, 0, vHullMins.z);
  1085. TraceHull( vecUp, vecDown, testMin, testMax, collisionMask, &trace2 );
  1086. if ( trace2.fraction != 1.0 && ( trace1.m_pEnt == trace2.m_pEnt || CanStandOn( trace2.m_pEnt ) ) )
  1087. {
  1088. return true;
  1089. }
  1090. }
  1091. }
  1092. else
  1093. {
  1094. AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Center );
  1095. TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace1 );
  1096. if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
  1097. return true;
  1098. }
  1099. return false;
  1100. }
  1101. //-----------------------------------------------------------------------------
  1102. bool CAI_MoveProbe::OldCheckStandPosition( const Vector &vecStart, unsigned int collisionMask ) const
  1103. {
  1104. AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition );
  1105. Vector contactMin, contactMax;
  1106. // this should assume the model is already standing
  1107. Vector vecUp = Vector( vecStart.x, vecStart.y, vecStart.z + 0.1 );
  1108. Vector vecDown = Vector( vecStart.x, vecStart.y, vecStart.z - StepHeight() * GetOuter()->GetStepDownMultiplier() );
  1109. // check a half sized box centered around the foot
  1110. const Vector &vHullMins = WorldAlignMins();
  1111. const Vector &vHullMaxs = WorldAlignMaxs();
  1112. contactMin.x = vHullMins.x * 0.75 + vHullMaxs.x * 0.25;
  1113. contactMax.x = vHullMins.x * 0.25 + vHullMaxs.x * 0.75;
  1114. contactMin.y = vHullMins.y * 0.75 + vHullMaxs.y * 0.25;
  1115. contactMax.y = vHullMins.y * 0.25 + vHullMaxs.y * 0.75;
  1116. contactMin.z = vHullMins.z;
  1117. contactMax.z = vHullMins.z;
  1118. trace_t trace;
  1119. AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStandPosition_Center );
  1120. TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace );
  1121. AI_PROFILE_SCOPE_END();
  1122. if (trace.fraction == 1.0 || !CanStandOn( trace.m_pEnt ))
  1123. return false;
  1124. float sumFraction = 0;
  1125. if ( !GetOuter()->IsFlaggedEfficient() )
  1126. {
  1127. AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Sides );
  1128. // check a box for each quadrant, allow one failure
  1129. int already_failed = false;
  1130. for (int x = 0; x <= 1 ;x++)
  1131. {
  1132. for (int y = 0; y <= 1; y++)
  1133. {
  1134. // create bounding boxes for each quadrant
  1135. contactMin[0] = x ? 0 :vHullMins.x;
  1136. contactMax[0] = x ? vHullMaxs.x : 0;
  1137. contactMin[1] = y ? 0 : vHullMins.y;
  1138. contactMax[1] = y ? vHullMaxs.y : 0;
  1139. TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace );
  1140. sumFraction += trace.fraction;
  1141. // this should hit something, if it doesn't allow one failure
  1142. if (trace.fraction == 1.0 || !CanStandOn( trace.m_pEnt ))
  1143. {
  1144. if (already_failed)
  1145. return false;
  1146. else
  1147. {
  1148. already_failed = true;
  1149. }
  1150. }
  1151. else
  1152. {
  1153. if ( sumFraction > 2.0 )
  1154. return false;
  1155. }
  1156. }
  1157. }
  1158. }
  1159. return true;
  1160. }
  1161. //-----------------------------------------------------------------------------
  1162. // Computes a point on the floor below the start point, somewhere
  1163. // between vecStart.z + flStartZ and vecStart.z + flEndZ
  1164. //-----------------------------------------------------------------------------
  1165. bool CAI_MoveProbe::FloorPoint( const Vector &vecStart, unsigned int collisionMask,
  1166. float flStartZ, float flEndZ, Vector *pVecResult ) const
  1167. {
  1168. AI_PROFILE_SCOPE( CAI_Motor_FloorPoint );
  1169. // make a pizzabox shaped bounding hull
  1170. Vector mins = WorldAlignMins();
  1171. Vector maxs( WorldAlignMaxs().x, WorldAlignMaxs().y, mins.z );
  1172. // trace down step height and a bit more
  1173. Vector vecUp( vecStart.x, vecStart.y, vecStart.z + flStartZ + MOVE_HEIGHT_EPSILON );
  1174. Vector vecDown( vecStart.x, vecStart.y, vecStart.z + flEndZ );
  1175. trace_t trace;
  1176. TraceHull( vecUp, vecDown, mins, maxs, collisionMask, &trace );
  1177. bool fStartedInObject = false;
  1178. if (trace.startsolid)
  1179. {
  1180. if ( trace.m_pEnt &&
  1181. ( trace.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS || trace.m_pEnt->IsNPC() ) &&
  1182. ( vecStart - GetLocalOrigin() ).Length() < 0.1 )
  1183. {
  1184. fStartedInObject = true;
  1185. }
  1186. vecUp.z = vecStart.z + MOVE_HEIGHT_EPSILON;
  1187. TraceHull( vecUp, vecDown, mins, maxs, collisionMask, &trace );
  1188. }
  1189. // this should have hit a solid surface by now
  1190. if (trace.fraction == 1 || trace.allsolid || ( fStartedInObject && trace.startsolid ) )
  1191. {
  1192. // set result to start position if it doesn't work
  1193. *pVecResult = vecStart;
  1194. if ( fStartedInObject )
  1195. return true; // in this case, probably got intruded on by a physics object. Try ignoring it...
  1196. return false;
  1197. }
  1198. *pVecResult = trace.endpos;
  1199. return true;
  1200. }
  1201. //-----------------------------------------------------------------------------
  1202. // A floorPoint that is useful only in the context of iterative movement
  1203. //-----------------------------------------------------------------------------
  1204. bool CAI_MoveProbe::IterativeFloorPoint( const Vector &vecStart, unsigned int collisionMask, Vector *pVecResult ) const
  1205. {
  1206. return IterativeFloorPoint( vecStart, collisionMask, 0, pVecResult );
  1207. }
  1208. //-----------------------------------------------------------------------------
  1209. bool CAI_MoveProbe::IterativeFloorPoint( const Vector &vecStart, unsigned int collisionMask, float flAddedStep, Vector *pVecResult ) const
  1210. {
  1211. // Used by the movement code, it guarantees we don't move outside a step
  1212. // height from our current position
  1213. return FloorPoint( vecStart, collisionMask, StepHeight() * GetOuter()->GetStepDownMultiplier() + flAddedStep, -(12*60), pVecResult );
  1214. }
  1215. //-----------------------------------------------------------------------------
  1216. float CAI_MoveProbe::StepHeight() const
  1217. {
  1218. return GetOuter()->StepHeight();
  1219. }
  1220. //-----------------------------------------------------------------------------
  1221. bool CAI_MoveProbe::CanStandOn( CBaseEntity *pSurface ) const
  1222. {
  1223. return GetOuter()->CanStandOn( pSurface );
  1224. }
  1225. //-----------------------------------------------------------------------------
  1226. bool CAI_MoveProbe::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos ) const
  1227. {
  1228. return GetOuter()->IsJumpLegal( startPos, apex, endPos );
  1229. }
  1230. //-----------------------------------------------------------------------------