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.

1392 lines
45 KiB

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