aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/ai_moveprobe.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_moveprobe.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_moveprobe.cpp')
-rw-r--r--mp/src/game/server/ai_moveprobe.cpp2784
1 files changed, 1392 insertions, 1392 deletions
diff --git a/mp/src/game/server/ai_moveprobe.cpp b/mp/src/game/server/ai_moveprobe.cpp
index c181ddba..b2a86d83 100644
--- a/mp/src/game/server/ai_moveprobe.cpp
+++ b/mp/src/game/server/ai_moveprobe.cpp
@@ -1,1392 +1,1392 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-
-#include "modelentities.h"
-#include "iservervehicle.h"
-#include "movevars_shared.h"
-
-#include "ai_moveprobe.h"
-
-#include "ai_basenpc.h"
-#include "ai_routedist.h"
-#include "props.h"
-#include "vphysics/object_hash.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-#undef LOCAL_STEP_SIZE
-// FIXME: this should be based in their hull width
-#define LOCAL_STEP_SIZE 16.0 // 8 // 16
-
-// If set to 1, results will be drawn for moveprobes done by player-selected NPCs
-ConVar ai_moveprobe_debug( "ai_moveprobe_debug", "0" );
-ConVar ai_moveprobe_jump_debug( "ai_moveprobe_jump_debug", "0" );
-ConVar ai_moveprobe_usetracelist( "ai_moveprobe_usetracelist", "0" );
-
-ConVar ai_strong_optimizations_no_checkstand( "ai_strong_optimizations_no_checkstand", "0" );
-
-#ifdef DEBUG
-ConVar ai_old_check_stand_position( "ai_old_check_stand_position", "0" );
-#define UseOldCheckStandPosition() (ai_old_check_stand_position.GetBool())
-#else
-#define UseOldCheckStandPosition() (false)
-#endif
-
-//-----------------------------------------------------------------------------
-
-// We may be able to remove this, but due to certain collision
-// problems on displacements, and due to the fact that CheckStep is currently
-// being called from code outside motor code, we may need to give it a little
-// room to avoid boundary condition problems. Also note that this will
-// cause us to start 2*EPSILON above the ground the next time that this
-// function is called, but for now, that appears to not be a problem.
-float MOVE_HEIGHT_EPSILON = 0.0625f;
-
-CON_COMMAND( ai_set_move_height_epsilon, "Set how high AI bumps up ground walkers when checking steps" )
-{
- if ( !UTIL_IsCommandIssuedByServerAdmin() )
- return;
-
- if ( args.ArgC() > 1 )
- {
- float newEps = atof( args[1] );
- if ( newEps >= 0.0 && newEps < 1.0 )
- {
- MOVE_HEIGHT_EPSILON = newEps;
- }
- Msg( "Epsilon now %f\n", MOVE_HEIGHT_EPSILON );
- }
-}
-
-//-----------------------------------------------------------------------------
-
-BEGIN_SIMPLE_DATADESC(CAI_MoveProbe)
- // m_pTraceListData (not saved, a cached item)
- DEFINE_FIELD( m_bIgnoreTransientEntities, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hLastBlockingEnt, FIELD_EHANDLE ),
-
-END_DATADESC();
-
-
-//-----------------------------------------------------------------------------
-// Categorizes the blocker and sets the appropriate bits
-//-----------------------------------------------------------------------------
-AIMoveResult_t AIComputeBlockerMoveResult( CBaseEntity *pBlocker )
-{
- if (pBlocker->MyNPCPointer())
- return AIMR_BLOCKED_NPC;
- else if (pBlocker->entindex() == 0)
- return AIMR_BLOCKED_WORLD;
- return AIMR_BLOCKED_ENTITY;
-}
-
-//-----------------------------------------------------------------------------
-bool CAI_MoveProbe::ShouldBrushBeIgnored( CBaseEntity *pEntity )
-{
- if ( pEntity->m_iClassname == g_iszFuncBrushClassname )
- {
- CFuncBrush *pFuncBrush = assert_cast<CFuncBrush *>(pEntity);
-
- // this is true if my class or entity name matches the exclusion name on the func brush
-#if HL2_EPISODIC
- bool nameMatches = ( pFuncBrush->m_iszExcludedClass == GetOuter()->m_iClassname ) || GetOuter()->NameMatches(pFuncBrush->m_iszExcludedClass);
-#else // do not match against entity name in base HL2 (just in case there is some case somewhere that might be broken by this)
- bool nameMatches = ( pFuncBrush->m_iszExcludedClass == GetOuter()->m_iClassname );
-#endif
-
- // return true (ignore brush) if the name matches, or, if exclusion is inverted, if the name does not match
- return ( pFuncBrush->m_bInvertExclusion ? !nameMatches : nameMatches );
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_MoveProbe::TraceLine( const Vector &vecStart, const Vector &vecEnd, unsigned int mask,
- bool bUseCollisionGroup, trace_t *pResult ) const
-{
- int collisionGroup = (bUseCollisionGroup) ?
- GetCollisionGroup() :
- COLLISION_GROUP_NONE;
-
- CTraceFilterNav traceFilter( const_cast<CAI_BaseNPC *>(GetOuter()), m_bIgnoreTransientEntities, GetOuter(), collisionGroup );
-
- AI_TraceLine( vecStart, vecEnd, mask, &traceFilter, pResult );
-
-#ifdef _DEBUG
- // Just to make sure; I'm not sure that this is always the case but it should be
- if (pResult->allsolid)
- {
- Assert( pResult->startsolid );
- }
-#endif
-}
-
-//-----------------------------------------------------------------------------
-
-CAI_MoveProbe::CAI_MoveProbe(CAI_BaseNPC *pOuter)
- : CAI_Component( pOuter ),
- m_bIgnoreTransientEntities( false ),
- m_pTraceListData( NULL )
-{
-}
-
-//-----------------------------------------------------------------------------
-
-CAI_MoveProbe::~CAI_MoveProbe()
-{
- delete m_pTraceListData;
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_MoveProbe::TraceHull(
- const Vector &vecStart, const Vector &vecEnd, const Vector &hullMin,
- const Vector &hullMax, unsigned int mask, trace_t *pResult ) const
-{
- AI_PROFILE_SCOPE( CAI_MoveProbe_TraceHull );
-
- CTraceFilterNav traceFilter( const_cast<CAI_BaseNPC *>(GetOuter()), m_bIgnoreTransientEntities, GetOuter(), GetCollisionGroup() );
-
- Ray_t ray;
- ray.Init( vecStart, vecEnd, hullMin, hullMax );
-
- if ( !m_pTraceListData || m_pTraceListData->IsEmpty() )
- enginetrace->TraceRay( ray, mask, &traceFilter, pResult );
- else
- {
- enginetrace->TraceRayAgainstLeafAndEntityList( ray, *(const_cast<CAI_MoveProbe *>(this)->m_pTraceListData), mask, &traceFilter, pResult );
-#if 0
- trace_t verificationTrace;
- enginetrace->TraceRay( ray, mask, &traceFilter, &verificationTrace );
- Assert( fabsf(verificationTrace.fraction - pResult->fraction) < 0.01 &&
- VectorsAreEqual( verificationTrace.endpos, pResult->endpos, 0.01 ) &&
- verificationTrace.m_pEnt == pResult->m_pEnt );
-
-#endif
- }
-
- if ( r_visualizetraces.GetBool() )
- DebugDrawLine( pResult->startpos, pResult->endpos, 255, 255, 0, true, -1.0f );
-
- //NDebugOverlay::SweptBox( vecStart, vecEnd, hullMin, hullMax, vec3_angle, 255, 255, 0, 0, 10 );
- // Just to make sure; I'm not sure that this is always the case but it should be
- Assert( !pResult->allsolid || pResult->startsolid );
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_MoveProbe::TraceHull( const Vector &vecStart, const Vector &vecEnd,
- unsigned int mask, trace_t *pResult ) const
-{
- TraceHull( vecStart, vecEnd, WorldAlignMins(), WorldAlignMaxs(), mask, pResult);
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_MoveProbe::SetupCheckStepTraceListData( const CheckStepArgs_t &args ) const
-{
- if ( ai_moveprobe_usetracelist.GetBool() )
- {
- Ray_t ray;
- Vector hullMin = WorldAlignMins();
- Vector hullMax = WorldAlignMaxs();
-
- hullMax.z += MOVE_HEIGHT_EPSILON;
- hullMin.z -= MOVE_HEIGHT_EPSILON;
-
- hullMax.z += args.stepHeight;
- hullMin.z -= args.stepHeight;
-
- if ( args.groundTest != STEP_DONT_CHECK_GROUND )
- hullMin.z -= args.stepHeight;
-
- hullMax.x += args.minStepLanding;
- hullMin.x -= args.minStepLanding;
-
- hullMax.y += args.minStepLanding;
- hullMin.y -= args.minStepLanding;
-
- Vector vecEnd;
- Vector2DMA( args.vecStart.AsVector2D(), args.stepSize, args.vecStepDir.AsVector2D(), vecEnd.AsVector2D() );
- vecEnd.z = args.vecStart.z;
-
- ray.Init( args.vecStart, vecEnd, hullMin, hullMax );
-
- if ( !m_pTraceListData )
- {
- const_cast<CAI_MoveProbe *>(this)->m_pTraceListData = new CTraceListData;
- }
- enginetrace->SetupLeafAndEntityListRay( ray, *(const_cast<CAI_MoveProbe *>(this)->m_pTraceListData) );
- }
-}
-
-//-----------------------------------------------------------------------------
-// CheckStep() is a fundamentally 2D operation! vecEnd.z is ignored.
-// We can step up one StepHeight or down one StepHeight from vecStart
-//-----------------------------------------------------------------------------
-bool g_bAIDebugStep = false;
-
-bool CAI_MoveProbe::CheckStep( const CheckStepArgs_t &args, CheckStepResult_t *pResult ) const
-{
- AI_PROFILE_SCOPE( CAI_MoveProbe_CheckStep );
-
- Vector vecEnd;
- unsigned collisionMask = args.collisionMask;
- VectorMA( args.vecStart, args.stepSize, args.vecStepDir, vecEnd );
-
- pResult->endPoint = args.vecStart;
- pResult->fStartSolid = false;
- pResult->hitNormal = vec3_origin;
- pResult->pBlocker = NULL;
-
- // This is fundamentally a 2D operation; we just want the end
- // position in Z to be no more than a step height from the start position
- Vector stepStart( args.vecStart.x, args.vecStart.y, args.vecStart.z + MOVE_HEIGHT_EPSILON );
- Vector stepEnd( vecEnd.x, vecEnd.y, args.vecStart.z + MOVE_HEIGHT_EPSILON );
-
- if ( g_bAIDebugStep )
- {
- NDebugOverlay::Line( stepStart, stepEnd, 255, 255, 255, true, 5 );
- NDebugOverlay::Cross3D( stepEnd, 32, 255, 255, 255, true, 5 );
- }
-
- trace_t trace;
-
- AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStep_Forward );
-
- TraceHull( stepStart, stepEnd, collisionMask, &trace );
-
- if (trace.startsolid || (trace.fraction < 1))
- {
- // Either the entity is starting embedded in the world, or it hit something.
- // Raise the box by the step height and try again
- trace_t stepTrace;
-
- if ( !trace.startsolid )
- {
- if ( g_bAIDebugStep )
- NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 64, 64, 64, 0, 5 );
-
- // Advance to first obstruction point
- stepStart = trace.endpos;
-
- // Trace up to locate the maximum step up in the space
- Vector stepUp( stepStart );
- stepUp.z += args.stepHeight;
- TraceHull( stepStart, stepUp, collisionMask, &stepTrace );
-
- if ( g_bAIDebugStep )
- NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins(), WorldAlignMaxs(), 96, 96, 96, 0, 5 );
-
- stepStart = stepTrace.endpos;
- }
- else
- stepStart.z += args.stepHeight;
-
- // Now move forward
- stepEnd.z = stepStart.z;
-
- TraceHull( stepStart, stepEnd, collisionMask, &stepTrace );
- bool bRejectStep = false;
-
- // Ok, raising it didn't work; we're obstructed
- if (stepTrace.startsolid || stepTrace.fraction <= 0.01 )
- {
- // If started in solid, and never escaped from solid, bail
- if ( trace.startsolid )
- {
- pResult->fStartSolid = true;
- pResult->pBlocker = trace.m_pEnt;
- pResult->hitNormal = trace.plane.normal;
- return false;
- }
-
- bRejectStep = true;
- }
- else
- {
- if ( g_bAIDebugStep )
- NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins(), WorldAlignMaxs(), 128, 128, 128, 0, 5 );
-
- // If didn't step forward enough to qualify as a step, try as if stepped forward to
- // confirm there's potentially enough space to "land"
- float landingDistSq = ( stepEnd.AsVector2D() - stepStart.AsVector2D() ).LengthSqr();
- float requiredLandingDistSq = args.minStepLanding*args.minStepLanding;
- if ( landingDistSq < requiredLandingDistSq )
- {
- trace_t landingTrace;
- Vector stepEndWithLanding;
-
- VectorMA( stepStart, args.minStepLanding, args.vecStepDir, stepEndWithLanding );
- TraceHull( stepStart, stepEndWithLanding, collisionMask, &landingTrace );
- if ( landingTrace.fraction < 1 )
- {
- if ( g_bAIDebugStep )
- NDebugOverlay::Box( landingTrace.endpos, WorldAlignMins() + Vector(0, 0, 0.1), WorldAlignMaxs() + Vector(0, 0, 0.1), 255, 0, 0, 0, 5 );
-
- bRejectStep = true;
- if ( landingTrace.m_pEnt )
- pResult->pBlocker = landingTrace.m_pEnt;
- }
- }
- else if ( ( stepTrace.endpos.AsVector2D() - stepStart.AsVector2D() ).LengthSqr() < requiredLandingDistSq )
- {
- if ( g_bAIDebugStep )
- NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins() + Vector(0, 0, 0.1), WorldAlignMaxs() + Vector(0, 0, 0.1), 255, 0, 0, 0, 5 );
-
- bRejectStep = true;
- }
- }
-
- // If trace.fraction == 0, we fall through and check the position
- // we moved up to for suitability. This allows for sub-step
- // traces if the position ends up being suitable
- if ( !bRejectStep )
- trace = stepTrace;
-
- if ( trace.fraction < 1.0 )
- {
- if ( !pResult->pBlocker )
- pResult->pBlocker = trace.m_pEnt;
- pResult->hitNormal = trace.plane.normal;
- }
-
- stepEnd = trace.endpos;
- }
-
- AI_PROFILE_SCOPE_END();
-
- AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStep_Down );
- // seems okay, now find the ground
- // The ground is only valid if it's within a step height of the original position
- Assert( VectorsAreEqual( trace.endpos, stepEnd, 1e-3 ) );
- stepStart = stepEnd;
- stepEnd.z = args.vecStart.z - args.stepHeight * args.stepDownMultiplier - MOVE_HEIGHT_EPSILON;
-
- TraceHull( stepStart, stepEnd, collisionMask, &trace );
-
- // in empty space, lie and say we hit the world
- if (trace.fraction == 1.0f)
- {
- if ( g_bAIDebugStep )
- NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 5 );
-
- Assert( pResult->endPoint == args.vecStart );
- if ( const_cast<CAI_MoveProbe *>(this)->GetOuter()->GetGroundEntity() )
- {
- pResult->pBlocker = const_cast<CAI_MoveProbe *>(this)->GetOuter()->GetGroundEntity();
- }
- else
- {
- pResult->pBlocker = GetContainingEntity( INDEXENT(0) );
- }
- return false;
- }
-
- if ( g_bAIDebugStep )
- NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 160, 160, 160, 0, 5 );
-
- AI_PROFILE_SCOPE_END();
-
- // Checks to see if the thing we're on is a *type* of thing we
- // are capable of standing on. Always true ffor our current ground ent
- // otherwise we'll be stuck forever
- CBaseEntity *pFloor = trace.m_pEnt;
- if ( pFloor != GetOuter()->GetGroundEntity() && !CanStandOn( pFloor ) )
- {
- if ( g_bAIDebugStep )
- NDebugOverlay::Cross3D( trace.endpos, 32, 255, 0, 0, true, 5 );
-
- Assert( pResult->endPoint == args.vecStart );
- pResult->pBlocker = pFloor;
- return false;
- }
-
- // Don't step up onto an odd slope
- if ( trace.endpos.z - args.vecStart.z > args.stepHeight * 0.5 &&
- ( ( pFloor->IsWorld() && trace.hitbox > 0 ) ||
- dynamic_cast<CPhysicsProp *>( pFloor ) ) )
- {
- if ( fabsf( trace.plane.normal.Dot( Vector(1, 0, 0) ) ) > .4 )
- {
- Assert( pResult->endPoint == args.vecStart );
- pResult->pBlocker = pFloor;
-
- if ( g_bAIDebugStep )
- NDebugOverlay::Cross3D( trace.endpos, 32, 0, 0, 255, true, 5 );
- return false;
- }
- }
-
- if (args.groundTest != STEP_DONT_CHECK_GROUND)
- {
- AI_PROFILE_SCOPE( CAI_Motor_CheckStep_Stand );
- // Next, check to see if we can *geometrically* stand on the floor
- bool bIsFloorFlat = CheckStandPosition( trace.endpos, collisionMask );
- if (args.groundTest != STEP_ON_INVALID_GROUND && !bIsFloorFlat)
- {
- pResult->pBlocker = pFloor;
-
- if ( g_bAIDebugStep )
- NDebugOverlay::Cross3D( trace.endpos, 32, 255, 0, 255, true, 5 );
- return false;
- }
- // If we started on shaky ground (namely, it's not geometrically ok),
- // then we can continue going even if we remain on shaky ground.
- // This allows NPCs who have been blown into an invalid area to get out
- // of that invalid area and into a valid area. As soon as we're in
- // a valid area, though, we're not allowed to leave it.
- }
-
- // Return a point that is *on the ground*
- // We'll raise it by an epsilon in check step again
- pResult->endPoint = trace.endpos;
- pResult->endPoint.z += MOVE_HEIGHT_EPSILON; // always safe because always stepped down at least by epsilon
-
- if ( g_bAIDebugStep )
- NDebugOverlay::Cross3D( trace.endpos, 32, 0, 255, 0, true, 5 );
-
- return ( pResult->pBlocker == NULL ); // totally clear if pBlocker is NULL, partial blockage otherwise
-}
-
-//-----------------------------------------------------------------------------
-// Checks a ground-based movement
-// NOTE: The movement will be based on an *actual* start position and
-// a *desired* end position; it works this way because the ground-based movement
-// is 2 1/2D, and we may end up on a ledge above or below the actual desired endpoint.
-//-----------------------------------------------------------------------------
-bool CAI_MoveProbe::TestGroundMove( const Vector &vecActualStart, const Vector &vecDesiredEnd,
- unsigned int collisionMask, float pctToCheckStandPositions, unsigned flags, AIMoveTrace_t *pMoveTrace ) const
-{
- AIMoveTrace_t ignored;
- if ( !pMoveTrace )
- pMoveTrace = &ignored;
-
- // Set a reasonable default set of values
- pMoveTrace->flDistObstructed = 0.0f;
- pMoveTrace->pObstruction = NULL;
- pMoveTrace->vHitNormal = vec3_origin;
- pMoveTrace->fStatus = AIMR_OK;
- pMoveTrace->vEndPosition = vecActualStart;
- pMoveTrace->flStepUpDistance = 0;
-
- Vector vecMoveDir;
- pMoveTrace->flTotalDist = ComputePathDirection( NAV_GROUND, vecActualStart, vecDesiredEnd, &vecMoveDir );
- if (pMoveTrace->flTotalDist == 0.0f)
- {
- return true;
- }
-
- // If it starts hanging over an edge, tough it out until it's not
- // This allows us to blow an NPC in an invalid region + allow him to walk out
- StepGroundTest_t groundTest;
- if ( (flags & AITGM_IGNORE_FLOOR) || pctToCheckStandPositions < 0.001 )
- {
- groundTest = STEP_DONT_CHECK_GROUND;
- pctToCheckStandPositions = 0; // AITGM_IGNORE_FLOOR always overrides pct
- }
- else
- {
- if ( pctToCheckStandPositions > 99.999 )
- pctToCheckStandPositions = 100;
-
- if ((flags & AITGM_IGNORE_INITIAL_STAND_POS) || CheckStandPosition(vecActualStart, collisionMask))
- groundTest = STEP_ON_VALID_GROUND;
- else
- groundTest = STEP_ON_INVALID_GROUND;
- }
-
- if ( ( flags & AITGM_DRAW_RESULTS ) && !CheckStandPosition(vecActualStart, collisionMask) )
- {
- NDebugOverlay::Cross3D( vecActualStart, 16, 128, 0, 0, true, 2.0 );
- }
-
- // Take single steps towards the goal
- float distClear = 0;
- int i;
-
- CheckStepArgs_t checkStepArgs;
- CheckStepResult_t checkStepResult;
-
- checkStepArgs.vecStart = vecActualStart;
- checkStepArgs.vecStepDir = vecMoveDir;
- checkStepArgs.stepSize = 0;
- checkStepArgs.stepHeight = StepHeight();
- checkStepArgs.stepDownMultiplier = GetOuter()->GetStepDownMultiplier();
- checkStepArgs.minStepLanding = GetHullWidth() * 0.3333333;
- checkStepArgs.collisionMask = collisionMask;
- checkStepArgs.groundTest = groundTest;
-
- checkStepResult.endPoint = vecActualStart;
- checkStepResult.hitNormal = vec3_origin;
- checkStepResult.pBlocker = NULL;
-
- float distStartToIgnoreGround = (pctToCheckStandPositions == 100) ? pMoveTrace->flTotalDist : pMoveTrace->flTotalDist * ( pctToCheckStandPositions * 0.01);
- bool bTryNavIgnore = ( ( vecActualStart - GetLocalOrigin() ).Length2DSqr() < 0.1 && fabsf(vecActualStart.z - GetLocalOrigin().z) < checkStepArgs.stepHeight * 0.5 );
-
- CUtlVector<CBaseEntity *> ignoredEntities;
-
- for (;;)
- {
- float flStepSize = MIN( LOCAL_STEP_SIZE, pMoveTrace->flTotalDist - distClear );
- if ( flStepSize < 0.001 )
- break;
-
- checkStepArgs.stepSize = flStepSize;
- if ( distClear - distStartToIgnoreGround > 0.001 )
- checkStepArgs.groundTest = STEP_DONT_CHECK_GROUND;
-
- Assert( !m_pTraceListData || m_pTraceListData->IsEmpty() );
- SetupCheckStepTraceListData( checkStepArgs );
-
- for ( i = 0; i < 16; i++ )
- {
- CheckStep( checkStepArgs, &checkStepResult );
-
- if ( !bTryNavIgnore || !checkStepResult.pBlocker || !checkStepResult.fStartSolid )
- break;
-
- if ( checkStepResult.pBlocker->GetMoveType() != MOVETYPE_VPHYSICS && !checkStepResult.pBlocker->IsNPC() )
- break;
-
- // Only permit pass through of objects initially embedded in
- if ( vecActualStart != checkStepArgs.vecStart )
- {
- bTryNavIgnore = false;
- break;
- }
-
- // Only allow move away from physics objects
- if ( checkStepResult.pBlocker->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- Vector vMoveDir = vecDesiredEnd - vecActualStart;
- VectorNormalize( vMoveDir );
-
- Vector vObstacleDir = (checkStepResult.pBlocker->WorldSpaceCenter() - GetOuter()->WorldSpaceCenter() );
- VectorNormalize( vObstacleDir );
-
- if ( vMoveDir.Dot( vObstacleDir ) >= 0 )
- break;
- }
-
- if ( ( flags & AITGM_DRAW_RESULTS ) && checkStepResult.fStartSolid && checkStepResult.pBlocker->IsNPC() )
- {
- NDebugOverlay::EntityBounds( GetOuter(), 0, 0, 255, 0, .5 );
- NDebugOverlay::EntityBounds( checkStepResult.pBlocker, 255, 0, 0, 0, .5 );
- }
-
- ignoredEntities.AddToTail( checkStepResult.pBlocker );
- checkStepResult.pBlocker->SetNavIgnore();
- }
-
- ResetTraceListData();
-
- if ( flags & AITGM_DRAW_RESULTS )
- {
- if ( !CheckStandPosition(checkStepResult.endPoint, collisionMask) )
- {
- NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 0.1 );
- NDebugOverlay::Cross3D( checkStepResult.endPoint, 16, 255, 0, 0, true, 0.1 );
- }
- else
- {
- NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 0, 255, 0, 0, 0.1 );
- NDebugOverlay::Cross3D( checkStepResult.endPoint, 16, 0, 255, 0, true, 0.1 );
- }
- }
-
- // If we're being blocked by something, move as close as we can and stop
- if ( checkStepResult.pBlocker )
- {
- distClear += ( checkStepResult.endPoint - checkStepArgs.vecStart ).Length2D();
- break;
- }
-
- float dz = checkStepResult.endPoint.z - checkStepArgs.vecStart.z;
- if ( dz < 0 )
- {
- dz = 0;
- }
-
- pMoveTrace->flStepUpDistance += dz;
- distClear += flStepSize;
- checkStepArgs.vecStart = checkStepResult.endPoint;
- }
-
- for ( i = 0; i < ignoredEntities.Count(); i++ )
- {
- ignoredEntities[i]->ClearNavIgnore();
- }
-
- pMoveTrace->vEndPosition = checkStepResult.endPoint;
-
- if (checkStepResult.pBlocker)
- {
- pMoveTrace->pObstruction = checkStepResult.pBlocker;
- pMoveTrace->vHitNormal = checkStepResult.hitNormal;
- pMoveTrace->fStatus = AIComputeBlockerMoveResult( checkStepResult.pBlocker );
- pMoveTrace->flDistObstructed = pMoveTrace->flTotalDist - distClear;
-
- if ( flags & AITGM_DRAW_RESULTS )
- {
- NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 0.5 );
- }
-
- return false;
- }
-
- // FIXME: If you started on a ledge and ended on a ledge,
- // should it return an error condition (that you hit the world)?
- // Certainly not for Step(), but maybe for GroundMoveLimit()?
-
- // Make sure we actually made it to the target position
- // and not a ledge above or below the target.
- if (!(flags & AITGM_2D))
- {
- float threshold = MAX( 0.5f * GetHullHeight(), StepHeight() + 0.1 );
- if (fabs(pMoveTrace->vEndPosition.z - vecDesiredEnd.z) > threshold)
- {
-#if 0
- NDebugOverlay::Cross3D( vecDesiredEnd, 8, 0, 255, 0, false, 0.1 );
- NDebugOverlay::Cross3D( pMoveTrace->vEndPosition, 8, 255, 0, 0, false, 0.1 );
-#endif
- // Ok, we ended up on a ledge above or below the desired destination
- pMoveTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
- pMoveTrace->vHitNormal = vec3_origin;
- pMoveTrace->fStatus = AIMR_BLOCKED_WORLD;
- pMoveTrace->flDistObstructed = ComputePathDistance( NAV_GROUND, pMoveTrace->vEndPosition, vecDesiredEnd );
- return false;
- }
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Tries to generate a route from the specified start to end positions
-// Will return the results of the attempt in the AIMoveTrace_t structure
-//-----------------------------------------------------------------------------
-void CAI_MoveProbe::GroundMoveLimit( const Vector &vecStart, const Vector &vecEnd,
- unsigned int collisionMask, const CBaseEntity *pTarget, unsigned testGroundMoveFlags, float pctToCheckStandPositions, AIMoveTrace_t* pTrace ) const
-{
- // NOTE: Never call this directly!!! Always use MoveLimit!!
- // This assertion should ensure this happens
- Assert( !IsMoveBlocked( *pTrace ) );
-
- AI_PROFILE_SCOPE( CAI_Motor_GroundMoveLimit );
-
- Vector vecActualStart, vecDesiredEnd;
-
- pTrace->flTotalDist = ComputePathDistance( NAV_GROUND, vecStart, vecEnd );
-
- if ( !IterativeFloorPoint( vecStart, collisionMask, &vecActualStart ) )
- {
- pTrace->flDistObstructed = pTrace->flTotalDist;
- pTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
- pTrace->vHitNormal = vec3_origin;
- pTrace->fStatus = AIMR_BLOCKED_WORLD;
- pTrace->vEndPosition = vecStart;
-
- //DevMsg( "Warning: attempting to path from/to a point that is in solid space or is too high\n" );
- return;
- }
-
- // find out where they (in theory) should have ended up
- if (!(testGroundMoveFlags & AITGM_2D))
- IterativeFloorPoint( vecEnd, collisionMask, &vecDesiredEnd );
- else
- vecDesiredEnd = vecEnd;
-
- // When checking the route, look for ground geometric validity
- // Let's try to avoid invalid routes
- TestGroundMove( vecActualStart, vecDesiredEnd, collisionMask, pctToCheckStandPositions, testGroundMoveFlags, pTrace );
-
- // Check to see if the target is in a vehicle and the vehicle is blocking our way
- bool bVehicleMatchesObstruction = false;
-
- if ( pTarget != NULL )
- {
- CBaseCombatCharacter *pCCTarget = ((CBaseEntity *)pTarget)->MyCombatCharacterPointer();
- if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() )
- {
- CBaseEntity *pVehicleEnt = pCCTarget->GetVehicleEntity();
- if ( pVehicleEnt == pTrace->pObstruction )
- bVehicleMatchesObstruction = true;
- }
- }
-
- if ( (pTarget && (pTarget == pTrace->pObstruction)) || bVehicleMatchesObstruction )
- {
- // Collided with target entity, return there was no collision!!
- // but leave the end trace position
- pTrace->flDistObstructed = 0.0f;
- pTrace->pObstruction = NULL;
- pTrace->vHitNormal = vec3_origin;
- pTrace->fStatus = AIMR_OK;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: returns zero if the caller can walk a straight line from
-// vecStart to vecEnd ignoring collisions with pTarget
-//
-// if the move fails, returns the distance remaining to vecEnd
-//-----------------------------------------------------------------------------
-void CAI_MoveProbe::FlyMoveLimit( const Vector &vecStart, const Vector &vecEnd,
- unsigned int collisionMask, const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
-{
- // NOTE: Never call this directly!!! Always use MoveLimit!!
- // This assertion should ensure this happens
- Assert( !IsMoveBlocked( *pMoveTrace) );
-
- trace_t tr;
- TraceHull( vecStart, vecEnd, collisionMask, &tr );
-
- if ( tr.fraction < 1 )
- {
- CBaseEntity *pBlocker = tr.m_pEnt;
- if ( pBlocker )
- {
- if ( pTarget == pBlocker )
- {
- // Colided with target entity, movement is ok
- pMoveTrace->vEndPosition = tr.endpos;
- return;
- }
-
- // If blocked by an npc remember
- pMoveTrace->pObstruction = pBlocker;
- pMoveTrace->vHitNormal = vec3_origin;
- pMoveTrace->fStatus = AIComputeBlockerMoveResult( pBlocker );
- }
- pMoveTrace->flDistObstructed = ComputePathDistance( NAV_FLY, tr.endpos, vecEnd );
- pMoveTrace->vEndPosition = tr.endpos;
- return;
- }
-
- // no collisions, movement is ok
- pMoveTrace->vEndPosition = vecEnd;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: returns zero if the caller can jump from
-// vecStart to vecEnd ignoring collisions with pTarget
-//
-// if the jump fails, returns the distance
-// that can be travelled before an obstacle is hit
-//-----------------------------------------------------------------------------
-void CAI_MoveProbe::JumpMoveLimit( const Vector &vecStart, const Vector &vecEnd,
- unsigned int collisionMask, const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
-{
- pMoveTrace->vJumpVelocity.Init( 0, 0, 0 );
-
- float flDist = ComputePathDistance( NAV_JUMP, vecStart, vecEnd );
-
- if (!IsJumpLegal(vecStart, vecEnd, vecEnd))
- {
- pMoveTrace->fStatus = AIMR_ILLEGAL;
- pMoveTrace->flDistObstructed = flDist;
- return;
- }
-
- // --------------------------------------------------------------------------
- // Drop start and end vectors to the floor and check to see if they're legal
- // --------------------------------------------------------------------------
- Vector vecFrom;
- IterativeFloorPoint( vecStart, collisionMask, &vecFrom );
-
- Vector vecTo;
- IterativeFloorPoint( vecEnd, collisionMask, StepHeight() * 0.5, &vecTo );
- if (!CheckStandPosition( vecTo, collisionMask))
- {
- pMoveTrace->fStatus = AIMR_ILLEGAL;
- pMoveTrace->flDistObstructed = flDist;
- return;
- }
-
- if (vecFrom == vecTo)
- {
- pMoveTrace->fStatus = AIMR_ILLEGAL;
- pMoveTrace->flDistObstructed = flDist;
- return;
- }
-
- if ((vecFrom - vecTo).Length2D() == 0.0)
- {
- pMoveTrace->fStatus = AIMR_ILLEGAL;
- pMoveTrace->flDistObstructed = flDist;
- return;
- }
-
- // FIXME: add max jump velocity callback? Look at the velocity in the jump animation? use ideal running speed?
- float maxHorzVel = GetOuter()->GetMaxJumpSpeed();
-
- Vector gravity = Vector(0, 0, GetCurrentGravity() * GetOuter()->GetJumpGravity() );
-
- if ( gravity.z < 0.01 )
- {
- pMoveTrace->fStatus = AIMR_ILLEGAL;
- pMoveTrace->flDistObstructed = flDist;
- return;
- }
-
- // intialize error state to it being an illegal jump
- CBaseEntity *pObstruction = NULL;
- AIMoveResult_t fStatus = AIMR_ILLEGAL;
- float flDistObstructed = flDist;
-
- // initialize jump state
- float minSuccessfulJumpHeight = 1024.0;
- float minJumpHeight = 0.0;
- float minJumpStep = 1024.0;
-
- // initial jump, sets baseline for minJumpHeight
- Vector vecApex;
- Vector rawJumpVel = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minJumpHeight, maxHorzVel, &vecApex );
- float baselineJumpHeight = minJumpHeight;
-
- // FIXME: this is a binary search, which really isn't the right thing to do. If there's a gap
- // the npc can jump through, this won't reliably find it. The only way I can think to do this is a
- // linear search trying ever higher jumps until the gap is either found or the jump is illegal.
- do
- {
- rawJumpVel = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minJumpHeight, maxHorzVel, &vecApex );
- // DevMsg( "%.0f ", minJumpHeight );
-
- if (!IsJumpLegal(vecFrom, vecApex, vecTo))
- {
- // too high, try lower
- minJumpStep = minJumpStep / 2.0;
- minJumpHeight = minJumpHeight - minJumpStep;
- }
- else
- {
- // Calculate the total time of the jump minus a tiny fraction
- float jumpTime = (vecFrom - vecTo).Length2D()/rawJumpVel.Length2D();
- float timeStep = jumpTime / 10.0;
-
- Vector vecTest = vecFrom;
- bool bMadeIt = true;
-
- // this sweeps out a rough approximation of the jump
- // FIXME: this won't reliably hit the apex
- for (float flTime = 0 ; flTime < jumpTime - 0.01; flTime += timeStep )
- {
- trace_t trace;
-
- // Calculate my position after the time step (average velocity over this time step)
- Vector nextPos = vecTest + (rawJumpVel - 0.5 * gravity * timeStep) * timeStep;
-
- TraceHull( vecTest, nextPos, collisionMask, &trace );
-
- if (trace.startsolid || trace.fraction < 0.99) // FIXME: getting inconsistant trace fractions, revisit after Jay resolves collision eplisons
- {
- // NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 255, 255, 0, 0, 10.0 );
-
- // save error state
- pObstruction = trace.m_pEnt;
- fStatus = AIComputeBlockerMoveResult( pObstruction );
- flDistObstructed = ComputePathDistance( NAV_JUMP, vecTest, vecTo );
-
- if (trace.plane.normal.z < 0.0)
- {
- // hit a ceiling looking thing, too high, try lower
- minJumpStep = minJumpStep / 2.0;
- minJumpHeight = minJumpHeight - minJumpStep;
- }
- else
- {
- // hit wall looking thing, try higher
- minJumpStep = minJumpStep / 2.0;
- minJumpHeight += minJumpStep;
- }
-
- if ( ai_moveprobe_jump_debug.GetBool() )
- {
- NDebugOverlay::Line( vecTest, nextPos, 255, 0, 0, true, 2.0f );
- }
-
- bMadeIt = false;
- break;
- }
- else
- {
- if ( ai_moveprobe_jump_debug.GetBool() )
- {
- NDebugOverlay::Line( vecTest, nextPos, 0, 255, 0, true, 2.0f );
- }
- }
-
- rawJumpVel = rawJumpVel - gravity * timeStep;
- vecTest = nextPos;
- }
-
- if (bMadeIt)
- {
- // made it, try lower
- minSuccessfulJumpHeight = minJumpHeight;
- minJumpStep = minJumpStep / 2.0;
- minJumpHeight -= minJumpStep;
- }
- }
- }
- while (minJumpHeight > baselineJumpHeight && minJumpHeight <= 1024.0 && minJumpStep >= 16.0);
-
- // DevMsg( "(%.0f)\n", minSuccessfulJumpHeight );
-
- if (minSuccessfulJumpHeight != 1024.0)
- {
- // Get my jump velocity
- pMoveTrace->vJumpVelocity = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minSuccessfulJumpHeight, maxHorzVel, &vecApex );
- }
- else
- {
- // ----------------------------------------------------------
- // If blocked by an npc remember
- // ----------------------------------------------------------
- pMoveTrace->pObstruction = pObstruction;
- pMoveTrace->vHitNormal = vec3_origin;
- pMoveTrace->fStatus = fStatus;
- pMoveTrace->flDistObstructed = flDistObstructed;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: returns zero if the caller can climb from
-// vecStart to vecEnd ignoring collisions with pTarget
-//
-// if the climb fails, returns the distance remaining
-// before the obstacle is hit
-//-----------------------------------------------------------------------------
-void CAI_MoveProbe::ClimbMoveLimit( const Vector &vecStart, const Vector &vecEnd,
- const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
-{
- trace_t tr;
- TraceHull( vecStart, vecEnd, MASK_NPCSOLID, &tr );
-
- if (tr.fraction < 1.0)
- {
- CBaseEntity *pEntity = tr.m_pEnt;
- if (pEntity == pTarget)
- {
- return;
- }
- else
- {
- // ----------------------------------------------------------
- // If blocked by an npc remember
- // ----------------------------------------------------------
- pMoveTrace->pObstruction = pEntity;
- pMoveTrace->vHitNormal = vec3_origin;
- pMoveTrace->fStatus = AIComputeBlockerMoveResult( pEntity );
-
- float flDist = (1.0 - tr.fraction) * ComputePathDistance( NAV_CLIMB, vecStart, vecEnd );
- if (flDist <= 0.001)
- {
- flDist = 0.001;
- }
- pMoveTrace->flDistObstructed = flDist;
- return;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_MoveProbe::MoveLimit( Navigation_t navType, const Vector &vecStart,
- const Vector &vecEnd, unsigned int collisionMask, const CBaseEntity *pTarget,
- float pctToCheckStandPositions, unsigned flags, AIMoveTrace_t* pTrace)
-{
- AIMoveTrace_t ignoredTrace;
- if ( !pTrace )
- pTrace = &ignoredTrace;
-
- // Set a reasonable default set of values
- pTrace->flTotalDist = ComputePathDistance( navType, vecStart, vecEnd );
- pTrace->flDistObstructed = 0.0f;
- pTrace->pObstruction = NULL;
- pTrace->vHitNormal = vec3_origin;
- pTrace->fStatus = AIMR_OK;
- pTrace->vEndPosition = vecStart;
-
- switch (navType)
- {
- case NAV_GROUND:
- {
- unsigned testGroundMoveFlags = AITGM_DEFAULT;
- if (flags & AIMLF_2D )
- testGroundMoveFlags |= AITGM_2D;
- if ( flags & AIMLF_DRAW_RESULTS )
- testGroundMoveFlags |= AITGM_DRAW_RESULTS;
- if ( ai_moveprobe_debug.GetBool() && (GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
- testGroundMoveFlags |= AITGM_DRAW_RESULTS;
-
- if ( flags & AIMLF_IGNORE_TRANSIENTS )
- const_cast<CAI_MoveProbe *>(this)->m_bIgnoreTransientEntities = true;
-
- bool bDoIt = true;
- if ( flags & AIMLF_QUICK_REJECT )
- {
- Assert( vecStart == GetLocalOrigin() );
- trace_t tr;
- TraceLine(const_cast<CAI_MoveProbe *>(this)->GetOuter()->EyePosition(), vecEnd, collisionMask, true, &tr);
- bDoIt = ( tr.fraction > 0.99 );
- }
-
- if ( bDoIt )
- GroundMoveLimit(vecStart, vecEnd, collisionMask, pTarget, testGroundMoveFlags, pctToCheckStandPositions, pTrace);
- else
- {
- pTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
- pTrace->vHitNormal = vec3_origin;
- pTrace->fStatus = AIMR_BLOCKED_WORLD;
- pTrace->flDistObstructed = ComputePathDistance( NAV_GROUND, vecStart, vecEnd );
- }
-
- const_cast<CAI_MoveProbe *>(this)->m_bIgnoreTransientEntities = false;
-
- break;
- }
-
- case NAV_FLY:
- FlyMoveLimit(vecStart, vecEnd, collisionMask, pTarget, pTrace);
- break;
-
- case NAV_JUMP:
- JumpMoveLimit(vecStart, vecEnd, collisionMask, pTarget, pTrace);
- break;
-
- case NAV_CLIMB:
- ClimbMoveLimit(vecStart, vecEnd, pTarget, pTrace);
- break;
-
- default:
- pTrace->fStatus = AIMR_ILLEGAL;
- pTrace->flDistObstructed = ComputePathDistance( navType, vecStart, vecEnd );
- break;
- }
-
- if (IsMoveBlocked(pTrace->fStatus) && pTrace->pObstruction && !pTrace->pObstruction->IsWorld())
- {
- m_hLastBlockingEnt = pTrace->pObstruction;
- }
-
- return !IsMoveBlocked(pTrace->fStatus);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns a jump lauch velocity for the current target entity
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-Vector CAI_MoveProbe::CalcJumpLaunchVelocity(const Vector &startPos, const Vector &endPos, float flGravity, float *pminHeight, float maxHorzVelocity, Vector *pvecApex ) const
-{
- // Get the height I have to jump to get to the target
- float stepHeight = endPos.z - startPos.z;
-
- // get horizontal distance to target
- Vector targetDir2D = endPos - startPos;
- targetDir2D.z = 0;
- float distance = VectorNormalize(targetDir2D);
-
- Assert( maxHorzVelocity > 0 );
-
- // get minimum times and heights to meet ideal horz velocity
- float minHorzTime = distance / maxHorzVelocity;
- float minHorzHeight = 0.5 * flGravity * (minHorzTime * 0.5) * (minHorzTime * 0.5);
-
- // jump height must be enough to hang in the air
- *pminHeight = MAX( *pminHeight, minHorzHeight );
- // jump height must be enough to cover the step up
- *pminHeight = MAX( *pminHeight, stepHeight );
-
- // time from start to apex
- float t0 = sqrt( ( 2.0 * *pminHeight) / flGravity );
- // time from apex to end
- float t1 = sqrt( ( 2.0 * fabs( *pminHeight - stepHeight) ) / flGravity );
-
- float velHorz = distance / (t0 + t1);
-
- Vector jumpVel = targetDir2D * velHorz;
-
- jumpVel.z = (float)sqrt(2.0f * flGravity * (*pminHeight));
-
- if (pvecApex)
- {
- *pvecApex = startPos + targetDir2D * velHorz * t0 + Vector( 0, 0, *pminHeight );
- }
-
- // -----------------------------------------------------------
- // Make the horizontal jump vector and add vertical component
- // -----------------------------------------------------------
-
- return jumpVel;
-}
-
-//-----------------------------------------------------------------------------
-
-bool CAI_MoveProbe::CheckStandPosition( const Vector &vecStart, unsigned int collisionMask ) const
-{
- // If we're not supposed to do ground checks, always say we can stand there
- if ( (GetOuter()->CapabilitiesGet() & bits_CAP_SKIP_NAV_GROUND_CHECK) )
- return true;
-
- // This is an extra-strong optimization
- if ( ai_strong_optimizations_no_checkstand.GetBool() )
- return true;
-
- if ( UseOldCheckStandPosition() )
- return OldCheckStandPosition( vecStart, collisionMask );
-
- AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition );
-
- Vector contactMin, contactMax;
-
- // this should assume the model is already standing
- Vector vecUp = Vector( vecStart.x, vecStart.y, vecStart.z + 0.1 );
- Vector vecDown = Vector( vecStart.x, vecStart.y, vecStart.z - StepHeight() * GetOuter()->GetStepDownMultiplier() );
-
- // check a half sized box centered around the foot
- Vector vHullMins = WorldAlignMins();
- Vector vHullMaxs = WorldAlignMaxs();
-
- if ( vHullMaxs == vec3_origin && vHullMins == vHullMaxs )
- {
- // "Test hulls" have no collision property
- vHullMins = GetHullMins();
- vHullMaxs = GetHullMaxs();
- }
-
- contactMin.x = vHullMins.x * 0.75 + vHullMaxs.x * 0.25;
- contactMax.x = vHullMins.x * 0.25 + vHullMaxs.x * 0.75;
- contactMin.y = vHullMins.y * 0.75 + vHullMaxs.y * 0.25;
- contactMax.y = vHullMins.y * 0.25 + vHullMaxs.y * 0.75;
- contactMin.z = vHullMins.z;
- contactMax.z = vHullMins.z;
-
- trace_t trace1, trace2;
-
- if ( !GetOuter()->IsFlaggedEfficient() )
- {
- AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Sides );
-
- Vector vHullBottomCenter;
- vHullBottomCenter.Init( 0, 0, vHullMins.z );
-
- // Try diagonal from lower left to upper right
- TraceHull( vecUp, vecDown, contactMin, vHullBottomCenter, collisionMask, &trace1 );
- if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
- {
- TraceHull( vecUp, vecDown, vHullBottomCenter, contactMax, collisionMask, &trace2 );
- if ( trace2.fraction != 1.0 && ( trace1.m_pEnt == trace2.m_pEnt || CanStandOn( trace2.m_pEnt ) ) )
- {
- return true;
- }
- }
-
- // Okay, try the other one
- Vector testMin;
- Vector testMax;
- testMin.Init(contactMin.x, 0, vHullMins.z);
- testMax.Init(0, contactMax.y, vHullMins.z);
-
- TraceHull( vecUp, vecDown, testMin, testMax, collisionMask, &trace1 );
- if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
- {
- testMin.Init(0, contactMin.y, vHullMins.z);
- testMax.Init(contactMax.x, 0, vHullMins.z);
- TraceHull( vecUp, vecDown, testMin, testMax, collisionMask, &trace2 );
- if ( trace2.fraction != 1.0 && ( trace1.m_pEnt == trace2.m_pEnt || CanStandOn( trace2.m_pEnt ) ) )
- {
- return true;
- }
- }
- }
- else
- {
- AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Center );
- TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace1 );
- if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-
-bool CAI_MoveProbe::OldCheckStandPosition( const Vector &vecStart, unsigned int collisionMask ) const
-{
- AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition );
-
- Vector contactMin, contactMax;
-
- // this should assume the model is already standing
- Vector vecUp = Vector( vecStart.x, vecStart.y, vecStart.z + 0.1 );
- Vector vecDown = Vector( vecStart.x, vecStart.y, vecStart.z - StepHeight() * GetOuter()->GetStepDownMultiplier() );
-
- // check a half sized box centered around the foot
- const Vector &vHullMins = WorldAlignMins();
- const Vector &vHullMaxs = WorldAlignMaxs();
-
- contactMin.x = vHullMins.x * 0.75 + vHullMaxs.x * 0.25;
- contactMax.x = vHullMins.x * 0.25 + vHullMaxs.x * 0.75;
- contactMin.y = vHullMins.y * 0.75 + vHullMaxs.y * 0.25;
- contactMax.y = vHullMins.y * 0.25 + vHullMaxs.y * 0.75;
- contactMin.z = vHullMins.z;
- contactMax.z = vHullMins.z;
-
- trace_t trace;
-
- AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStandPosition_Center );
- TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace );
- AI_PROFILE_SCOPE_END();
-
- if (trace.fraction == 1.0 || !CanStandOn( trace.m_pEnt ))
- return false;
-
- float sumFraction = 0;
-
- if ( !GetOuter()->IsFlaggedEfficient() )
- {
- AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Sides );
-
- // check a box for each quadrant, allow one failure
- int already_failed = false;
- for (int x = 0; x <= 1 ;x++)
- {
- for (int y = 0; y <= 1; y++)
- {
- // create bounding boxes for each quadrant
- contactMin[0] = x ? 0 :vHullMins.x;
- contactMax[0] = x ? vHullMaxs.x : 0;
- contactMin[1] = y ? 0 : vHullMins.y;
- contactMax[1] = y ? vHullMaxs.y : 0;
-
- TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace );
-
- sumFraction += trace.fraction;
-
- // this should hit something, if it doesn't allow one failure
- if (trace.fraction == 1.0 || !CanStandOn( trace.m_pEnt ))
- {
- if (already_failed)
- return false;
- else
- {
- already_failed = true;
- }
- }
- else
- {
- if ( sumFraction > 2.0 )
- return false;
- }
- }
- }
- }
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Computes a point on the floor below the start point, somewhere
-// between vecStart.z + flStartZ and vecStart.z + flEndZ
-//-----------------------------------------------------------------------------
-bool CAI_MoveProbe::FloorPoint( const Vector &vecStart, unsigned int collisionMask,
- float flStartZ, float flEndZ, Vector *pVecResult ) const
-{
- AI_PROFILE_SCOPE( CAI_Motor_FloorPoint );
-
- // make a pizzabox shaped bounding hull
- Vector mins = WorldAlignMins();
- Vector maxs( WorldAlignMaxs().x, WorldAlignMaxs().y, mins.z );
-
- // trace down step height and a bit more
- Vector vecUp( vecStart.x, vecStart.y, vecStart.z + flStartZ + MOVE_HEIGHT_EPSILON );
- Vector vecDown( vecStart.x, vecStart.y, vecStart.z + flEndZ );
-
- trace_t trace;
- TraceHull( vecUp, vecDown, mins, maxs, collisionMask, &trace );
-
- bool fStartedInObject = false;
-
- if (trace.startsolid)
- {
- if ( trace.m_pEnt &&
- ( trace.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS || trace.m_pEnt->IsNPC() ) &&
- ( vecStart - GetLocalOrigin() ).Length() < 0.1 )
- {
- fStartedInObject = true;
- }
-
- vecUp.z = vecStart.z + MOVE_HEIGHT_EPSILON;
- TraceHull( vecUp, vecDown, mins, maxs, collisionMask, &trace );
- }
-
- // this should have hit a solid surface by now
- if (trace.fraction == 1 || trace.allsolid || ( fStartedInObject && trace.startsolid ) )
- {
- // set result to start position if it doesn't work
- *pVecResult = vecStart;
- if ( fStartedInObject )
- return true; // in this case, probably got intruded on by a physics object. Try ignoring it...
- return false;
- }
-
- *pVecResult = trace.endpos;
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// A floorPoint that is useful only in the context of iterative movement
-//-----------------------------------------------------------------------------
-bool CAI_MoveProbe::IterativeFloorPoint( const Vector &vecStart, unsigned int collisionMask, Vector *pVecResult ) const
-{
- return IterativeFloorPoint( vecStart, collisionMask, 0, pVecResult );
-}
-
-//-----------------------------------------------------------------------------
-bool CAI_MoveProbe::IterativeFloorPoint( const Vector &vecStart, unsigned int collisionMask, float flAddedStep, Vector *pVecResult ) const
-{
- // Used by the movement code, it guarantees we don't move outside a step
- // height from our current position
- return FloorPoint( vecStart, collisionMask, StepHeight() * GetOuter()->GetStepDownMultiplier() + flAddedStep, -(12*60), pVecResult );
-}
-
-//-----------------------------------------------------------------------------
-
-float CAI_MoveProbe::StepHeight() const
-{
- return GetOuter()->StepHeight();
-}
-
-//-----------------------------------------------------------------------------
-
-bool CAI_MoveProbe::CanStandOn( CBaseEntity *pSurface ) const
-{
- return GetOuter()->CanStandOn( pSurface );
-}
-
-//-----------------------------------------------------------------------------
-
-bool CAI_MoveProbe::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos ) const
-{
- return GetOuter()->IsJumpLegal( startPos, apex, endPos );
-}
-
-//-----------------------------------------------------------------------------
-
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "modelentities.h"
+#include "iservervehicle.h"
+#include "movevars_shared.h"
+
+#include "ai_moveprobe.h"
+
+#include "ai_basenpc.h"
+#include "ai_routedist.h"
+#include "props.h"
+#include "vphysics/object_hash.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#undef LOCAL_STEP_SIZE
+// FIXME: this should be based in their hull width
+#define LOCAL_STEP_SIZE 16.0 // 8 // 16
+
+// If set to 1, results will be drawn for moveprobes done by player-selected NPCs
+ConVar ai_moveprobe_debug( "ai_moveprobe_debug", "0" );
+ConVar ai_moveprobe_jump_debug( "ai_moveprobe_jump_debug", "0" );
+ConVar ai_moveprobe_usetracelist( "ai_moveprobe_usetracelist", "0" );
+
+ConVar ai_strong_optimizations_no_checkstand( "ai_strong_optimizations_no_checkstand", "0" );
+
+#ifdef DEBUG
+ConVar ai_old_check_stand_position( "ai_old_check_stand_position", "0" );
+#define UseOldCheckStandPosition() (ai_old_check_stand_position.GetBool())
+#else
+#define UseOldCheckStandPosition() (false)
+#endif
+
+//-----------------------------------------------------------------------------
+
+// We may be able to remove this, but due to certain collision
+// problems on displacements, and due to the fact that CheckStep is currently
+// being called from code outside motor code, we may need to give it a little
+// room to avoid boundary condition problems. Also note that this will
+// cause us to start 2*EPSILON above the ground the next time that this
+// function is called, but for now, that appears to not be a problem.
+float MOVE_HEIGHT_EPSILON = 0.0625f;
+
+CON_COMMAND( ai_set_move_height_epsilon, "Set how high AI bumps up ground walkers when checking steps" )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ if ( args.ArgC() > 1 )
+ {
+ float newEps = atof( args[1] );
+ if ( newEps >= 0.0 && newEps < 1.0 )
+ {
+ MOVE_HEIGHT_EPSILON = newEps;
+ }
+ Msg( "Epsilon now %f\n", MOVE_HEIGHT_EPSILON );
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+BEGIN_SIMPLE_DATADESC(CAI_MoveProbe)
+ // m_pTraceListData (not saved, a cached item)
+ DEFINE_FIELD( m_bIgnoreTransientEntities, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hLastBlockingEnt, FIELD_EHANDLE ),
+
+END_DATADESC();
+
+
+//-----------------------------------------------------------------------------
+// Categorizes the blocker and sets the appropriate bits
+//-----------------------------------------------------------------------------
+AIMoveResult_t AIComputeBlockerMoveResult( CBaseEntity *pBlocker )
+{
+ if (pBlocker->MyNPCPointer())
+ return AIMR_BLOCKED_NPC;
+ else if (pBlocker->entindex() == 0)
+ return AIMR_BLOCKED_WORLD;
+ return AIMR_BLOCKED_ENTITY;
+}
+
+//-----------------------------------------------------------------------------
+bool CAI_MoveProbe::ShouldBrushBeIgnored( CBaseEntity *pEntity )
+{
+ if ( pEntity->m_iClassname == g_iszFuncBrushClassname )
+ {
+ CFuncBrush *pFuncBrush = assert_cast<CFuncBrush *>(pEntity);
+
+ // this is true if my class or entity name matches the exclusion name on the func brush
+#if HL2_EPISODIC
+ bool nameMatches = ( pFuncBrush->m_iszExcludedClass == GetOuter()->m_iClassname ) || GetOuter()->NameMatches(pFuncBrush->m_iszExcludedClass);
+#else // do not match against entity name in base HL2 (just in case there is some case somewhere that might be broken by this)
+ bool nameMatches = ( pFuncBrush->m_iszExcludedClass == GetOuter()->m_iClassname );
+#endif
+
+ // return true (ignore brush) if the name matches, or, if exclusion is inverted, if the name does not match
+ return ( pFuncBrush->m_bInvertExclusion ? !nameMatches : nameMatches );
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_MoveProbe::TraceLine( const Vector &vecStart, const Vector &vecEnd, unsigned int mask,
+ bool bUseCollisionGroup, trace_t *pResult ) const
+{
+ int collisionGroup = (bUseCollisionGroup) ?
+ GetCollisionGroup() :
+ COLLISION_GROUP_NONE;
+
+ CTraceFilterNav traceFilter( const_cast<CAI_BaseNPC *>(GetOuter()), m_bIgnoreTransientEntities, GetOuter(), collisionGroup );
+
+ AI_TraceLine( vecStart, vecEnd, mask, &traceFilter, pResult );
+
+#ifdef _DEBUG
+ // Just to make sure; I'm not sure that this is always the case but it should be
+ if (pResult->allsolid)
+ {
+ Assert( pResult->startsolid );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+CAI_MoveProbe::CAI_MoveProbe(CAI_BaseNPC *pOuter)
+ : CAI_Component( pOuter ),
+ m_bIgnoreTransientEntities( false ),
+ m_pTraceListData( NULL )
+{
+}
+
+//-----------------------------------------------------------------------------
+
+CAI_MoveProbe::~CAI_MoveProbe()
+{
+ delete m_pTraceListData;
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_MoveProbe::TraceHull(
+ const Vector &vecStart, const Vector &vecEnd, const Vector &hullMin,
+ const Vector &hullMax, unsigned int mask, trace_t *pResult ) const
+{
+ AI_PROFILE_SCOPE( CAI_MoveProbe_TraceHull );
+
+ CTraceFilterNav traceFilter( const_cast<CAI_BaseNPC *>(GetOuter()), m_bIgnoreTransientEntities, GetOuter(), GetCollisionGroup() );
+
+ Ray_t ray;
+ ray.Init( vecStart, vecEnd, hullMin, hullMax );
+
+ if ( !m_pTraceListData || m_pTraceListData->IsEmpty() )
+ enginetrace->TraceRay( ray, mask, &traceFilter, pResult );
+ else
+ {
+ enginetrace->TraceRayAgainstLeafAndEntityList( ray, *(const_cast<CAI_MoveProbe *>(this)->m_pTraceListData), mask, &traceFilter, pResult );
+#if 0
+ trace_t verificationTrace;
+ enginetrace->TraceRay( ray, mask, &traceFilter, &verificationTrace );
+ Assert( fabsf(verificationTrace.fraction - pResult->fraction) < 0.01 &&
+ VectorsAreEqual( verificationTrace.endpos, pResult->endpos, 0.01 ) &&
+ verificationTrace.m_pEnt == pResult->m_pEnt );
+
+#endif
+ }
+
+ if ( r_visualizetraces.GetBool() )
+ DebugDrawLine( pResult->startpos, pResult->endpos, 255, 255, 0, true, -1.0f );
+
+ //NDebugOverlay::SweptBox( vecStart, vecEnd, hullMin, hullMax, vec3_angle, 255, 255, 0, 0, 10 );
+ // Just to make sure; I'm not sure that this is always the case but it should be
+ Assert( !pResult->allsolid || pResult->startsolid );
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_MoveProbe::TraceHull( const Vector &vecStart, const Vector &vecEnd,
+ unsigned int mask, trace_t *pResult ) const
+{
+ TraceHull( vecStart, vecEnd, WorldAlignMins(), WorldAlignMaxs(), mask, pResult);
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_MoveProbe::SetupCheckStepTraceListData( const CheckStepArgs_t &args ) const
+{
+ if ( ai_moveprobe_usetracelist.GetBool() )
+ {
+ Ray_t ray;
+ Vector hullMin = WorldAlignMins();
+ Vector hullMax = WorldAlignMaxs();
+
+ hullMax.z += MOVE_HEIGHT_EPSILON;
+ hullMin.z -= MOVE_HEIGHT_EPSILON;
+
+ hullMax.z += args.stepHeight;
+ hullMin.z -= args.stepHeight;
+
+ if ( args.groundTest != STEP_DONT_CHECK_GROUND )
+ hullMin.z -= args.stepHeight;
+
+ hullMax.x += args.minStepLanding;
+ hullMin.x -= args.minStepLanding;
+
+ hullMax.y += args.minStepLanding;
+ hullMin.y -= args.minStepLanding;
+
+ Vector vecEnd;
+ Vector2DMA( args.vecStart.AsVector2D(), args.stepSize, args.vecStepDir.AsVector2D(), vecEnd.AsVector2D() );
+ vecEnd.z = args.vecStart.z;
+
+ ray.Init( args.vecStart, vecEnd, hullMin, hullMax );
+
+ if ( !m_pTraceListData )
+ {
+ const_cast<CAI_MoveProbe *>(this)->m_pTraceListData = new CTraceListData;
+ }
+ enginetrace->SetupLeafAndEntityListRay( ray, *(const_cast<CAI_MoveProbe *>(this)->m_pTraceListData) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CheckStep() is a fundamentally 2D operation! vecEnd.z is ignored.
+// We can step up one StepHeight or down one StepHeight from vecStart
+//-----------------------------------------------------------------------------
+bool g_bAIDebugStep = false;
+
+bool CAI_MoveProbe::CheckStep( const CheckStepArgs_t &args, CheckStepResult_t *pResult ) const
+{
+ AI_PROFILE_SCOPE( CAI_MoveProbe_CheckStep );
+
+ Vector vecEnd;
+ unsigned collisionMask = args.collisionMask;
+ VectorMA( args.vecStart, args.stepSize, args.vecStepDir, vecEnd );
+
+ pResult->endPoint = args.vecStart;
+ pResult->fStartSolid = false;
+ pResult->hitNormal = vec3_origin;
+ pResult->pBlocker = NULL;
+
+ // This is fundamentally a 2D operation; we just want the end
+ // position in Z to be no more than a step height from the start position
+ Vector stepStart( args.vecStart.x, args.vecStart.y, args.vecStart.z + MOVE_HEIGHT_EPSILON );
+ Vector stepEnd( vecEnd.x, vecEnd.y, args.vecStart.z + MOVE_HEIGHT_EPSILON );
+
+ if ( g_bAIDebugStep )
+ {
+ NDebugOverlay::Line( stepStart, stepEnd, 255, 255, 255, true, 5 );
+ NDebugOverlay::Cross3D( stepEnd, 32, 255, 255, 255, true, 5 );
+ }
+
+ trace_t trace;
+
+ AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStep_Forward );
+
+ TraceHull( stepStart, stepEnd, collisionMask, &trace );
+
+ if (trace.startsolid || (trace.fraction < 1))
+ {
+ // Either the entity is starting embedded in the world, or it hit something.
+ // Raise the box by the step height and try again
+ trace_t stepTrace;
+
+ if ( !trace.startsolid )
+ {
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 64, 64, 64, 0, 5 );
+
+ // Advance to first obstruction point
+ stepStart = trace.endpos;
+
+ // Trace up to locate the maximum step up in the space
+ Vector stepUp( stepStart );
+ stepUp.z += args.stepHeight;
+ TraceHull( stepStart, stepUp, collisionMask, &stepTrace );
+
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins(), WorldAlignMaxs(), 96, 96, 96, 0, 5 );
+
+ stepStart = stepTrace.endpos;
+ }
+ else
+ stepStart.z += args.stepHeight;
+
+ // Now move forward
+ stepEnd.z = stepStart.z;
+
+ TraceHull( stepStart, stepEnd, collisionMask, &stepTrace );
+ bool bRejectStep = false;
+
+ // Ok, raising it didn't work; we're obstructed
+ if (stepTrace.startsolid || stepTrace.fraction <= 0.01 )
+ {
+ // If started in solid, and never escaped from solid, bail
+ if ( trace.startsolid )
+ {
+ pResult->fStartSolid = true;
+ pResult->pBlocker = trace.m_pEnt;
+ pResult->hitNormal = trace.plane.normal;
+ return false;
+ }
+
+ bRejectStep = true;
+ }
+ else
+ {
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins(), WorldAlignMaxs(), 128, 128, 128, 0, 5 );
+
+ // If didn't step forward enough to qualify as a step, try as if stepped forward to
+ // confirm there's potentially enough space to "land"
+ float landingDistSq = ( stepEnd.AsVector2D() - stepStart.AsVector2D() ).LengthSqr();
+ float requiredLandingDistSq = args.minStepLanding*args.minStepLanding;
+ if ( landingDistSq < requiredLandingDistSq )
+ {
+ trace_t landingTrace;
+ Vector stepEndWithLanding;
+
+ VectorMA( stepStart, args.minStepLanding, args.vecStepDir, stepEndWithLanding );
+ TraceHull( stepStart, stepEndWithLanding, collisionMask, &landingTrace );
+ if ( landingTrace.fraction < 1 )
+ {
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Box( landingTrace.endpos, WorldAlignMins() + Vector(0, 0, 0.1), WorldAlignMaxs() + Vector(0, 0, 0.1), 255, 0, 0, 0, 5 );
+
+ bRejectStep = true;
+ if ( landingTrace.m_pEnt )
+ pResult->pBlocker = landingTrace.m_pEnt;
+ }
+ }
+ else if ( ( stepTrace.endpos.AsVector2D() - stepStart.AsVector2D() ).LengthSqr() < requiredLandingDistSq )
+ {
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Box( stepTrace.endpos, WorldAlignMins() + Vector(0, 0, 0.1), WorldAlignMaxs() + Vector(0, 0, 0.1), 255, 0, 0, 0, 5 );
+
+ bRejectStep = true;
+ }
+ }
+
+ // If trace.fraction == 0, we fall through and check the position
+ // we moved up to for suitability. This allows for sub-step
+ // traces if the position ends up being suitable
+ if ( !bRejectStep )
+ trace = stepTrace;
+
+ if ( trace.fraction < 1.0 )
+ {
+ if ( !pResult->pBlocker )
+ pResult->pBlocker = trace.m_pEnt;
+ pResult->hitNormal = trace.plane.normal;
+ }
+
+ stepEnd = trace.endpos;
+ }
+
+ AI_PROFILE_SCOPE_END();
+
+ AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStep_Down );
+ // seems okay, now find the ground
+ // The ground is only valid if it's within a step height of the original position
+ Assert( VectorsAreEqual( trace.endpos, stepEnd, 1e-3 ) );
+ stepStart = stepEnd;
+ stepEnd.z = args.vecStart.z - args.stepHeight * args.stepDownMultiplier - MOVE_HEIGHT_EPSILON;
+
+ TraceHull( stepStart, stepEnd, collisionMask, &trace );
+
+ // in empty space, lie and say we hit the world
+ if (trace.fraction == 1.0f)
+ {
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 5 );
+
+ Assert( pResult->endPoint == args.vecStart );
+ if ( const_cast<CAI_MoveProbe *>(this)->GetOuter()->GetGroundEntity() )
+ {
+ pResult->pBlocker = const_cast<CAI_MoveProbe *>(this)->GetOuter()->GetGroundEntity();
+ }
+ else
+ {
+ pResult->pBlocker = GetContainingEntity( INDEXENT(0) );
+ }
+ return false;
+ }
+
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 160, 160, 160, 0, 5 );
+
+ AI_PROFILE_SCOPE_END();
+
+ // Checks to see if the thing we're on is a *type* of thing we
+ // are capable of standing on. Always true ffor our current ground ent
+ // otherwise we'll be stuck forever
+ CBaseEntity *pFloor = trace.m_pEnt;
+ if ( pFloor != GetOuter()->GetGroundEntity() && !CanStandOn( pFloor ) )
+ {
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Cross3D( trace.endpos, 32, 255, 0, 0, true, 5 );
+
+ Assert( pResult->endPoint == args.vecStart );
+ pResult->pBlocker = pFloor;
+ return false;
+ }
+
+ // Don't step up onto an odd slope
+ if ( trace.endpos.z - args.vecStart.z > args.stepHeight * 0.5 &&
+ ( ( pFloor->IsWorld() && trace.hitbox > 0 ) ||
+ dynamic_cast<CPhysicsProp *>( pFloor ) ) )
+ {
+ if ( fabsf( trace.plane.normal.Dot( Vector(1, 0, 0) ) ) > .4 )
+ {
+ Assert( pResult->endPoint == args.vecStart );
+ pResult->pBlocker = pFloor;
+
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Cross3D( trace.endpos, 32, 0, 0, 255, true, 5 );
+ return false;
+ }
+ }
+
+ if (args.groundTest != STEP_DONT_CHECK_GROUND)
+ {
+ AI_PROFILE_SCOPE( CAI_Motor_CheckStep_Stand );
+ // Next, check to see if we can *geometrically* stand on the floor
+ bool bIsFloorFlat = CheckStandPosition( trace.endpos, collisionMask );
+ if (args.groundTest != STEP_ON_INVALID_GROUND && !bIsFloorFlat)
+ {
+ pResult->pBlocker = pFloor;
+
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Cross3D( trace.endpos, 32, 255, 0, 255, true, 5 );
+ return false;
+ }
+ // If we started on shaky ground (namely, it's not geometrically ok),
+ // then we can continue going even if we remain on shaky ground.
+ // This allows NPCs who have been blown into an invalid area to get out
+ // of that invalid area and into a valid area. As soon as we're in
+ // a valid area, though, we're not allowed to leave it.
+ }
+
+ // Return a point that is *on the ground*
+ // We'll raise it by an epsilon in check step again
+ pResult->endPoint = trace.endpos;
+ pResult->endPoint.z += MOVE_HEIGHT_EPSILON; // always safe because always stepped down at least by epsilon
+
+ if ( g_bAIDebugStep )
+ NDebugOverlay::Cross3D( trace.endpos, 32, 0, 255, 0, true, 5 );
+
+ return ( pResult->pBlocker == NULL ); // totally clear if pBlocker is NULL, partial blockage otherwise
+}
+
+//-----------------------------------------------------------------------------
+// Checks a ground-based movement
+// NOTE: The movement will be based on an *actual* start position and
+// a *desired* end position; it works this way because the ground-based movement
+// is 2 1/2D, and we may end up on a ledge above or below the actual desired endpoint.
+//-----------------------------------------------------------------------------
+bool CAI_MoveProbe::TestGroundMove( const Vector &vecActualStart, const Vector &vecDesiredEnd,
+ unsigned int collisionMask, float pctToCheckStandPositions, unsigned flags, AIMoveTrace_t *pMoveTrace ) const
+{
+ AIMoveTrace_t ignored;
+ if ( !pMoveTrace )
+ pMoveTrace = &ignored;
+
+ // Set a reasonable default set of values
+ pMoveTrace->flDistObstructed = 0.0f;
+ pMoveTrace->pObstruction = NULL;
+ pMoveTrace->vHitNormal = vec3_origin;
+ pMoveTrace->fStatus = AIMR_OK;
+ pMoveTrace->vEndPosition = vecActualStart;
+ pMoveTrace->flStepUpDistance = 0;
+
+ Vector vecMoveDir;
+ pMoveTrace->flTotalDist = ComputePathDirection( NAV_GROUND, vecActualStart, vecDesiredEnd, &vecMoveDir );
+ if (pMoveTrace->flTotalDist == 0.0f)
+ {
+ return true;
+ }
+
+ // If it starts hanging over an edge, tough it out until it's not
+ // This allows us to blow an NPC in an invalid region + allow him to walk out
+ StepGroundTest_t groundTest;
+ if ( (flags & AITGM_IGNORE_FLOOR) || pctToCheckStandPositions < 0.001 )
+ {
+ groundTest = STEP_DONT_CHECK_GROUND;
+ pctToCheckStandPositions = 0; // AITGM_IGNORE_FLOOR always overrides pct
+ }
+ else
+ {
+ if ( pctToCheckStandPositions > 99.999 )
+ pctToCheckStandPositions = 100;
+
+ if ((flags & AITGM_IGNORE_INITIAL_STAND_POS) || CheckStandPosition(vecActualStart, collisionMask))
+ groundTest = STEP_ON_VALID_GROUND;
+ else
+ groundTest = STEP_ON_INVALID_GROUND;
+ }
+
+ if ( ( flags & AITGM_DRAW_RESULTS ) && !CheckStandPosition(vecActualStart, collisionMask) )
+ {
+ NDebugOverlay::Cross3D( vecActualStart, 16, 128, 0, 0, true, 2.0 );
+ }
+
+ // Take single steps towards the goal
+ float distClear = 0;
+ int i;
+
+ CheckStepArgs_t checkStepArgs;
+ CheckStepResult_t checkStepResult;
+
+ checkStepArgs.vecStart = vecActualStart;
+ checkStepArgs.vecStepDir = vecMoveDir;
+ checkStepArgs.stepSize = 0;
+ checkStepArgs.stepHeight = StepHeight();
+ checkStepArgs.stepDownMultiplier = GetOuter()->GetStepDownMultiplier();
+ checkStepArgs.minStepLanding = GetHullWidth() * 0.3333333;
+ checkStepArgs.collisionMask = collisionMask;
+ checkStepArgs.groundTest = groundTest;
+
+ checkStepResult.endPoint = vecActualStart;
+ checkStepResult.hitNormal = vec3_origin;
+ checkStepResult.pBlocker = NULL;
+
+ float distStartToIgnoreGround = (pctToCheckStandPositions == 100) ? pMoveTrace->flTotalDist : pMoveTrace->flTotalDist * ( pctToCheckStandPositions * 0.01);
+ bool bTryNavIgnore = ( ( vecActualStart - GetLocalOrigin() ).Length2DSqr() < 0.1 && fabsf(vecActualStart.z - GetLocalOrigin().z) < checkStepArgs.stepHeight * 0.5 );
+
+ CUtlVector<CBaseEntity *> ignoredEntities;
+
+ for (;;)
+ {
+ float flStepSize = MIN( LOCAL_STEP_SIZE, pMoveTrace->flTotalDist - distClear );
+ if ( flStepSize < 0.001 )
+ break;
+
+ checkStepArgs.stepSize = flStepSize;
+ if ( distClear - distStartToIgnoreGround > 0.001 )
+ checkStepArgs.groundTest = STEP_DONT_CHECK_GROUND;
+
+ Assert( !m_pTraceListData || m_pTraceListData->IsEmpty() );
+ SetupCheckStepTraceListData( checkStepArgs );
+
+ for ( i = 0; i < 16; i++ )
+ {
+ CheckStep( checkStepArgs, &checkStepResult );
+
+ if ( !bTryNavIgnore || !checkStepResult.pBlocker || !checkStepResult.fStartSolid )
+ break;
+
+ if ( checkStepResult.pBlocker->GetMoveType() != MOVETYPE_VPHYSICS && !checkStepResult.pBlocker->IsNPC() )
+ break;
+
+ // Only permit pass through of objects initially embedded in
+ if ( vecActualStart != checkStepArgs.vecStart )
+ {
+ bTryNavIgnore = false;
+ break;
+ }
+
+ // Only allow move away from physics objects
+ if ( checkStepResult.pBlocker->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ Vector vMoveDir = vecDesiredEnd - vecActualStart;
+ VectorNormalize( vMoveDir );
+
+ Vector vObstacleDir = (checkStepResult.pBlocker->WorldSpaceCenter() - GetOuter()->WorldSpaceCenter() );
+ VectorNormalize( vObstacleDir );
+
+ if ( vMoveDir.Dot( vObstacleDir ) >= 0 )
+ break;
+ }
+
+ if ( ( flags & AITGM_DRAW_RESULTS ) && checkStepResult.fStartSolid && checkStepResult.pBlocker->IsNPC() )
+ {
+ NDebugOverlay::EntityBounds( GetOuter(), 0, 0, 255, 0, .5 );
+ NDebugOverlay::EntityBounds( checkStepResult.pBlocker, 255, 0, 0, 0, .5 );
+ }
+
+ ignoredEntities.AddToTail( checkStepResult.pBlocker );
+ checkStepResult.pBlocker->SetNavIgnore();
+ }
+
+ ResetTraceListData();
+
+ if ( flags & AITGM_DRAW_RESULTS )
+ {
+ if ( !CheckStandPosition(checkStepResult.endPoint, collisionMask) )
+ {
+ NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 0.1 );
+ NDebugOverlay::Cross3D( checkStepResult.endPoint, 16, 255, 0, 0, true, 0.1 );
+ }
+ else
+ {
+ NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 0, 255, 0, 0, 0.1 );
+ NDebugOverlay::Cross3D( checkStepResult.endPoint, 16, 0, 255, 0, true, 0.1 );
+ }
+ }
+
+ // If we're being blocked by something, move as close as we can and stop
+ if ( checkStepResult.pBlocker )
+ {
+ distClear += ( checkStepResult.endPoint - checkStepArgs.vecStart ).Length2D();
+ break;
+ }
+
+ float dz = checkStepResult.endPoint.z - checkStepArgs.vecStart.z;
+ if ( dz < 0 )
+ {
+ dz = 0;
+ }
+
+ pMoveTrace->flStepUpDistance += dz;
+ distClear += flStepSize;
+ checkStepArgs.vecStart = checkStepResult.endPoint;
+ }
+
+ for ( i = 0; i < ignoredEntities.Count(); i++ )
+ {
+ ignoredEntities[i]->ClearNavIgnore();
+ }
+
+ pMoveTrace->vEndPosition = checkStepResult.endPoint;
+
+ if (checkStepResult.pBlocker)
+ {
+ pMoveTrace->pObstruction = checkStepResult.pBlocker;
+ pMoveTrace->vHitNormal = checkStepResult.hitNormal;
+ pMoveTrace->fStatus = AIComputeBlockerMoveResult( checkStepResult.pBlocker );
+ pMoveTrace->flDistObstructed = pMoveTrace->flTotalDist - distClear;
+
+ if ( flags & AITGM_DRAW_RESULTS )
+ {
+ NDebugOverlay::Box( checkStepResult.endPoint, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 0, 0.5 );
+ }
+
+ return false;
+ }
+
+ // FIXME: If you started on a ledge and ended on a ledge,
+ // should it return an error condition (that you hit the world)?
+ // Certainly not for Step(), but maybe for GroundMoveLimit()?
+
+ // Make sure we actually made it to the target position
+ // and not a ledge above or below the target.
+ if (!(flags & AITGM_2D))
+ {
+ float threshold = MAX( 0.5f * GetHullHeight(), StepHeight() + 0.1 );
+ if (fabs(pMoveTrace->vEndPosition.z - vecDesiredEnd.z) > threshold)
+ {
+#if 0
+ NDebugOverlay::Cross3D( vecDesiredEnd, 8, 0, 255, 0, false, 0.1 );
+ NDebugOverlay::Cross3D( pMoveTrace->vEndPosition, 8, 255, 0, 0, false, 0.1 );
+#endif
+ // Ok, we ended up on a ledge above or below the desired destination
+ pMoveTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
+ pMoveTrace->vHitNormal = vec3_origin;
+ pMoveTrace->fStatus = AIMR_BLOCKED_WORLD;
+ pMoveTrace->flDistObstructed = ComputePathDistance( NAV_GROUND, pMoveTrace->vEndPosition, vecDesiredEnd );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Tries to generate a route from the specified start to end positions
+// Will return the results of the attempt in the AIMoveTrace_t structure
+//-----------------------------------------------------------------------------
+void CAI_MoveProbe::GroundMoveLimit( const Vector &vecStart, const Vector &vecEnd,
+ unsigned int collisionMask, const CBaseEntity *pTarget, unsigned testGroundMoveFlags, float pctToCheckStandPositions, AIMoveTrace_t* pTrace ) const
+{
+ // NOTE: Never call this directly!!! Always use MoveLimit!!
+ // This assertion should ensure this happens
+ Assert( !IsMoveBlocked( *pTrace ) );
+
+ AI_PROFILE_SCOPE( CAI_Motor_GroundMoveLimit );
+
+ Vector vecActualStart, vecDesiredEnd;
+
+ pTrace->flTotalDist = ComputePathDistance( NAV_GROUND, vecStart, vecEnd );
+
+ if ( !IterativeFloorPoint( vecStart, collisionMask, &vecActualStart ) )
+ {
+ pTrace->flDistObstructed = pTrace->flTotalDist;
+ pTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
+ pTrace->vHitNormal = vec3_origin;
+ pTrace->fStatus = AIMR_BLOCKED_WORLD;
+ pTrace->vEndPosition = vecStart;
+
+ //DevMsg( "Warning: attempting to path from/to a point that is in solid space or is too high\n" );
+ return;
+ }
+
+ // find out where they (in theory) should have ended up
+ if (!(testGroundMoveFlags & AITGM_2D))
+ IterativeFloorPoint( vecEnd, collisionMask, &vecDesiredEnd );
+ else
+ vecDesiredEnd = vecEnd;
+
+ // When checking the route, look for ground geometric validity
+ // Let's try to avoid invalid routes
+ TestGroundMove( vecActualStart, vecDesiredEnd, collisionMask, pctToCheckStandPositions, testGroundMoveFlags, pTrace );
+
+ // Check to see if the target is in a vehicle and the vehicle is blocking our way
+ bool bVehicleMatchesObstruction = false;
+
+ if ( pTarget != NULL )
+ {
+ CBaseCombatCharacter *pCCTarget = ((CBaseEntity *)pTarget)->MyCombatCharacterPointer();
+ if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() )
+ {
+ CBaseEntity *pVehicleEnt = pCCTarget->GetVehicleEntity();
+ if ( pVehicleEnt == pTrace->pObstruction )
+ bVehicleMatchesObstruction = true;
+ }
+ }
+
+ if ( (pTarget && (pTarget == pTrace->pObstruction)) || bVehicleMatchesObstruction )
+ {
+ // Collided with target entity, return there was no collision!!
+ // but leave the end trace position
+ pTrace->flDistObstructed = 0.0f;
+ pTrace->pObstruction = NULL;
+ pTrace->vHitNormal = vec3_origin;
+ pTrace->fStatus = AIMR_OK;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns zero if the caller can walk a straight line from
+// vecStart to vecEnd ignoring collisions with pTarget
+//
+// if the move fails, returns the distance remaining to vecEnd
+//-----------------------------------------------------------------------------
+void CAI_MoveProbe::FlyMoveLimit( const Vector &vecStart, const Vector &vecEnd,
+ unsigned int collisionMask, const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
+{
+ // NOTE: Never call this directly!!! Always use MoveLimit!!
+ // This assertion should ensure this happens
+ Assert( !IsMoveBlocked( *pMoveTrace) );
+
+ trace_t tr;
+ TraceHull( vecStart, vecEnd, collisionMask, &tr );
+
+ if ( tr.fraction < 1 )
+ {
+ CBaseEntity *pBlocker = tr.m_pEnt;
+ if ( pBlocker )
+ {
+ if ( pTarget == pBlocker )
+ {
+ // Colided with target entity, movement is ok
+ pMoveTrace->vEndPosition = tr.endpos;
+ return;
+ }
+
+ // If blocked by an npc remember
+ pMoveTrace->pObstruction = pBlocker;
+ pMoveTrace->vHitNormal = vec3_origin;
+ pMoveTrace->fStatus = AIComputeBlockerMoveResult( pBlocker );
+ }
+ pMoveTrace->flDistObstructed = ComputePathDistance( NAV_FLY, tr.endpos, vecEnd );
+ pMoveTrace->vEndPosition = tr.endpos;
+ return;
+ }
+
+ // no collisions, movement is ok
+ pMoveTrace->vEndPosition = vecEnd;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns zero if the caller can jump from
+// vecStart to vecEnd ignoring collisions with pTarget
+//
+// if the jump fails, returns the distance
+// that can be travelled before an obstacle is hit
+//-----------------------------------------------------------------------------
+void CAI_MoveProbe::JumpMoveLimit( const Vector &vecStart, const Vector &vecEnd,
+ unsigned int collisionMask, const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
+{
+ pMoveTrace->vJumpVelocity.Init( 0, 0, 0 );
+
+ float flDist = ComputePathDistance( NAV_JUMP, vecStart, vecEnd );
+
+ if (!IsJumpLegal(vecStart, vecEnd, vecEnd))
+ {
+ pMoveTrace->fStatus = AIMR_ILLEGAL;
+ pMoveTrace->flDistObstructed = flDist;
+ return;
+ }
+
+ // --------------------------------------------------------------------------
+ // Drop start and end vectors to the floor and check to see if they're legal
+ // --------------------------------------------------------------------------
+ Vector vecFrom;
+ IterativeFloorPoint( vecStart, collisionMask, &vecFrom );
+
+ Vector vecTo;
+ IterativeFloorPoint( vecEnd, collisionMask, StepHeight() * 0.5, &vecTo );
+ if (!CheckStandPosition( vecTo, collisionMask))
+ {
+ pMoveTrace->fStatus = AIMR_ILLEGAL;
+ pMoveTrace->flDistObstructed = flDist;
+ return;
+ }
+
+ if (vecFrom == vecTo)
+ {
+ pMoveTrace->fStatus = AIMR_ILLEGAL;
+ pMoveTrace->flDistObstructed = flDist;
+ return;
+ }
+
+ if ((vecFrom - vecTo).Length2D() == 0.0)
+ {
+ pMoveTrace->fStatus = AIMR_ILLEGAL;
+ pMoveTrace->flDistObstructed = flDist;
+ return;
+ }
+
+ // FIXME: add max jump velocity callback? Look at the velocity in the jump animation? use ideal running speed?
+ float maxHorzVel = GetOuter()->GetMaxJumpSpeed();
+
+ Vector gravity = Vector(0, 0, GetCurrentGravity() * GetOuter()->GetJumpGravity() );
+
+ if ( gravity.z < 0.01 )
+ {
+ pMoveTrace->fStatus = AIMR_ILLEGAL;
+ pMoveTrace->flDistObstructed = flDist;
+ return;
+ }
+
+ // intialize error state to it being an illegal jump
+ CBaseEntity *pObstruction = NULL;
+ AIMoveResult_t fStatus = AIMR_ILLEGAL;
+ float flDistObstructed = flDist;
+
+ // initialize jump state
+ float minSuccessfulJumpHeight = 1024.0;
+ float minJumpHeight = 0.0;
+ float minJumpStep = 1024.0;
+
+ // initial jump, sets baseline for minJumpHeight
+ Vector vecApex;
+ Vector rawJumpVel = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minJumpHeight, maxHorzVel, &vecApex );
+ float baselineJumpHeight = minJumpHeight;
+
+ // FIXME: this is a binary search, which really isn't the right thing to do. If there's a gap
+ // the npc can jump through, this won't reliably find it. The only way I can think to do this is a
+ // linear search trying ever higher jumps until the gap is either found or the jump is illegal.
+ do
+ {
+ rawJumpVel = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minJumpHeight, maxHorzVel, &vecApex );
+ // DevMsg( "%.0f ", minJumpHeight );
+
+ if (!IsJumpLegal(vecFrom, vecApex, vecTo))
+ {
+ // too high, try lower
+ minJumpStep = minJumpStep / 2.0;
+ minJumpHeight = minJumpHeight - minJumpStep;
+ }
+ else
+ {
+ // Calculate the total time of the jump minus a tiny fraction
+ float jumpTime = (vecFrom - vecTo).Length2D()/rawJumpVel.Length2D();
+ float timeStep = jumpTime / 10.0;
+
+ Vector vecTest = vecFrom;
+ bool bMadeIt = true;
+
+ // this sweeps out a rough approximation of the jump
+ // FIXME: this won't reliably hit the apex
+ for (float flTime = 0 ; flTime < jumpTime - 0.01; flTime += timeStep )
+ {
+ trace_t trace;
+
+ // Calculate my position after the time step (average velocity over this time step)
+ Vector nextPos = vecTest + (rawJumpVel - 0.5 * gravity * timeStep) * timeStep;
+
+ TraceHull( vecTest, nextPos, collisionMask, &trace );
+
+ if (trace.startsolid || trace.fraction < 0.99) // FIXME: getting inconsistant trace fractions, revisit after Jay resolves collision eplisons
+ {
+ // NDebugOverlay::Box( trace.endpos, WorldAlignMins(), WorldAlignMaxs(), 255, 255, 0, 0, 10.0 );
+
+ // save error state
+ pObstruction = trace.m_pEnt;
+ fStatus = AIComputeBlockerMoveResult( pObstruction );
+ flDistObstructed = ComputePathDistance( NAV_JUMP, vecTest, vecTo );
+
+ if (trace.plane.normal.z < 0.0)
+ {
+ // hit a ceiling looking thing, too high, try lower
+ minJumpStep = minJumpStep / 2.0;
+ minJumpHeight = minJumpHeight - minJumpStep;
+ }
+ else
+ {
+ // hit wall looking thing, try higher
+ minJumpStep = minJumpStep / 2.0;
+ minJumpHeight += minJumpStep;
+ }
+
+ if ( ai_moveprobe_jump_debug.GetBool() )
+ {
+ NDebugOverlay::Line( vecTest, nextPos, 255, 0, 0, true, 2.0f );
+ }
+
+ bMadeIt = false;
+ break;
+ }
+ else
+ {
+ if ( ai_moveprobe_jump_debug.GetBool() )
+ {
+ NDebugOverlay::Line( vecTest, nextPos, 0, 255, 0, true, 2.0f );
+ }
+ }
+
+ rawJumpVel = rawJumpVel - gravity * timeStep;
+ vecTest = nextPos;
+ }
+
+ if (bMadeIt)
+ {
+ // made it, try lower
+ minSuccessfulJumpHeight = minJumpHeight;
+ minJumpStep = minJumpStep / 2.0;
+ minJumpHeight -= minJumpStep;
+ }
+ }
+ }
+ while (minJumpHeight > baselineJumpHeight && minJumpHeight <= 1024.0 && minJumpStep >= 16.0);
+
+ // DevMsg( "(%.0f)\n", minSuccessfulJumpHeight );
+
+ if (minSuccessfulJumpHeight != 1024.0)
+ {
+ // Get my jump velocity
+ pMoveTrace->vJumpVelocity = CalcJumpLaunchVelocity(vecFrom, vecTo, gravity.z, &minSuccessfulJumpHeight, maxHorzVel, &vecApex );
+ }
+ else
+ {
+ // ----------------------------------------------------------
+ // If blocked by an npc remember
+ // ----------------------------------------------------------
+ pMoveTrace->pObstruction = pObstruction;
+ pMoveTrace->vHitNormal = vec3_origin;
+ pMoveTrace->fStatus = fStatus;
+ pMoveTrace->flDistObstructed = flDistObstructed;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns zero if the caller can climb from
+// vecStart to vecEnd ignoring collisions with pTarget
+//
+// if the climb fails, returns the distance remaining
+// before the obstacle is hit
+//-----------------------------------------------------------------------------
+void CAI_MoveProbe::ClimbMoveLimit( const Vector &vecStart, const Vector &vecEnd,
+ const CBaseEntity *pTarget, AIMoveTrace_t *pMoveTrace ) const
+{
+ trace_t tr;
+ TraceHull( vecStart, vecEnd, MASK_NPCSOLID, &tr );
+
+ if (tr.fraction < 1.0)
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if (pEntity == pTarget)
+ {
+ return;
+ }
+ else
+ {
+ // ----------------------------------------------------------
+ // If blocked by an npc remember
+ // ----------------------------------------------------------
+ pMoveTrace->pObstruction = pEntity;
+ pMoveTrace->vHitNormal = vec3_origin;
+ pMoveTrace->fStatus = AIComputeBlockerMoveResult( pEntity );
+
+ float flDist = (1.0 - tr.fraction) * ComputePathDistance( NAV_CLIMB, vecStart, vecEnd );
+ if (flDist <= 0.001)
+ {
+ flDist = 0.001;
+ }
+ pMoveTrace->flDistObstructed = flDist;
+ return;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAI_MoveProbe::MoveLimit( Navigation_t navType, const Vector &vecStart,
+ const Vector &vecEnd, unsigned int collisionMask, const CBaseEntity *pTarget,
+ float pctToCheckStandPositions, unsigned flags, AIMoveTrace_t* pTrace)
+{
+ AIMoveTrace_t ignoredTrace;
+ if ( !pTrace )
+ pTrace = &ignoredTrace;
+
+ // Set a reasonable default set of values
+ pTrace->flTotalDist = ComputePathDistance( navType, vecStart, vecEnd );
+ pTrace->flDistObstructed = 0.0f;
+ pTrace->pObstruction = NULL;
+ pTrace->vHitNormal = vec3_origin;
+ pTrace->fStatus = AIMR_OK;
+ pTrace->vEndPosition = vecStart;
+
+ switch (navType)
+ {
+ case NAV_GROUND:
+ {
+ unsigned testGroundMoveFlags = AITGM_DEFAULT;
+ if (flags & AIMLF_2D )
+ testGroundMoveFlags |= AITGM_2D;
+ if ( flags & AIMLF_DRAW_RESULTS )
+ testGroundMoveFlags |= AITGM_DRAW_RESULTS;
+ if ( ai_moveprobe_debug.GetBool() && (GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
+ testGroundMoveFlags |= AITGM_DRAW_RESULTS;
+
+ if ( flags & AIMLF_IGNORE_TRANSIENTS )
+ const_cast<CAI_MoveProbe *>(this)->m_bIgnoreTransientEntities = true;
+
+ bool bDoIt = true;
+ if ( flags & AIMLF_QUICK_REJECT )
+ {
+ Assert( vecStart == GetLocalOrigin() );
+ trace_t tr;
+ TraceLine(const_cast<CAI_MoveProbe *>(this)->GetOuter()->EyePosition(), vecEnd, collisionMask, true, &tr);
+ bDoIt = ( tr.fraction > 0.99 );
+ }
+
+ if ( bDoIt )
+ GroundMoveLimit(vecStart, vecEnd, collisionMask, pTarget, testGroundMoveFlags, pctToCheckStandPositions, pTrace);
+ else
+ {
+ pTrace->pObstruction = GetContainingEntity( INDEXENT(0) );
+ pTrace->vHitNormal = vec3_origin;
+ pTrace->fStatus = AIMR_BLOCKED_WORLD;
+ pTrace->flDistObstructed = ComputePathDistance( NAV_GROUND, vecStart, vecEnd );
+ }
+
+ const_cast<CAI_MoveProbe *>(this)->m_bIgnoreTransientEntities = false;
+
+ break;
+ }
+
+ case NAV_FLY:
+ FlyMoveLimit(vecStart, vecEnd, collisionMask, pTarget, pTrace);
+ break;
+
+ case NAV_JUMP:
+ JumpMoveLimit(vecStart, vecEnd, collisionMask, pTarget, pTrace);
+ break;
+
+ case NAV_CLIMB:
+ ClimbMoveLimit(vecStart, vecEnd, pTarget, pTrace);
+ break;
+
+ default:
+ pTrace->fStatus = AIMR_ILLEGAL;
+ pTrace->flDistObstructed = ComputePathDistance( navType, vecStart, vecEnd );
+ break;
+ }
+
+ if (IsMoveBlocked(pTrace->fStatus) && pTrace->pObstruction && !pTrace->pObstruction->IsWorld())
+ {
+ m_hLastBlockingEnt = pTrace->pObstruction;
+ }
+
+ return !IsMoveBlocked(pTrace->fStatus);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a jump lauch velocity for the current target entity
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+Vector CAI_MoveProbe::CalcJumpLaunchVelocity(const Vector &startPos, const Vector &endPos, float flGravity, float *pminHeight, float maxHorzVelocity, Vector *pvecApex ) const
+{
+ // Get the height I have to jump to get to the target
+ float stepHeight = endPos.z - startPos.z;
+
+ // get horizontal distance to target
+ Vector targetDir2D = endPos - startPos;
+ targetDir2D.z = 0;
+ float distance = VectorNormalize(targetDir2D);
+
+ Assert( maxHorzVelocity > 0 );
+
+ // get minimum times and heights to meet ideal horz velocity
+ float minHorzTime = distance / maxHorzVelocity;
+ float minHorzHeight = 0.5 * flGravity * (minHorzTime * 0.5) * (minHorzTime * 0.5);
+
+ // jump height must be enough to hang in the air
+ *pminHeight = MAX( *pminHeight, minHorzHeight );
+ // jump height must be enough to cover the step up
+ *pminHeight = MAX( *pminHeight, stepHeight );
+
+ // time from start to apex
+ float t0 = sqrt( ( 2.0 * *pminHeight) / flGravity );
+ // time from apex to end
+ float t1 = sqrt( ( 2.0 * fabs( *pminHeight - stepHeight) ) / flGravity );
+
+ float velHorz = distance / (t0 + t1);
+
+ Vector jumpVel = targetDir2D * velHorz;
+
+ jumpVel.z = (float)sqrt(2.0f * flGravity * (*pminHeight));
+
+ if (pvecApex)
+ {
+ *pvecApex = startPos + targetDir2D * velHorz * t0 + Vector( 0, 0, *pminHeight );
+ }
+
+ // -----------------------------------------------------------
+ // Make the horizontal jump vector and add vertical component
+ // -----------------------------------------------------------
+
+ return jumpVel;
+}
+
+//-----------------------------------------------------------------------------
+
+bool CAI_MoveProbe::CheckStandPosition( const Vector &vecStart, unsigned int collisionMask ) const
+{
+ // If we're not supposed to do ground checks, always say we can stand there
+ if ( (GetOuter()->CapabilitiesGet() & bits_CAP_SKIP_NAV_GROUND_CHECK) )
+ return true;
+
+ // This is an extra-strong optimization
+ if ( ai_strong_optimizations_no_checkstand.GetBool() )
+ return true;
+
+ if ( UseOldCheckStandPosition() )
+ return OldCheckStandPosition( vecStart, collisionMask );
+
+ AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition );
+
+ Vector contactMin, contactMax;
+
+ // this should assume the model is already standing
+ Vector vecUp = Vector( vecStart.x, vecStart.y, vecStart.z + 0.1 );
+ Vector vecDown = Vector( vecStart.x, vecStart.y, vecStart.z - StepHeight() * GetOuter()->GetStepDownMultiplier() );
+
+ // check a half sized box centered around the foot
+ Vector vHullMins = WorldAlignMins();
+ Vector vHullMaxs = WorldAlignMaxs();
+
+ if ( vHullMaxs == vec3_origin && vHullMins == vHullMaxs )
+ {
+ // "Test hulls" have no collision property
+ vHullMins = GetHullMins();
+ vHullMaxs = GetHullMaxs();
+ }
+
+ contactMin.x = vHullMins.x * 0.75 + vHullMaxs.x * 0.25;
+ contactMax.x = vHullMins.x * 0.25 + vHullMaxs.x * 0.75;
+ contactMin.y = vHullMins.y * 0.75 + vHullMaxs.y * 0.25;
+ contactMax.y = vHullMins.y * 0.25 + vHullMaxs.y * 0.75;
+ contactMin.z = vHullMins.z;
+ contactMax.z = vHullMins.z;
+
+ trace_t trace1, trace2;
+
+ if ( !GetOuter()->IsFlaggedEfficient() )
+ {
+ AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Sides );
+
+ Vector vHullBottomCenter;
+ vHullBottomCenter.Init( 0, 0, vHullMins.z );
+
+ // Try diagonal from lower left to upper right
+ TraceHull( vecUp, vecDown, contactMin, vHullBottomCenter, collisionMask, &trace1 );
+ if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
+ {
+ TraceHull( vecUp, vecDown, vHullBottomCenter, contactMax, collisionMask, &trace2 );
+ if ( trace2.fraction != 1.0 && ( trace1.m_pEnt == trace2.m_pEnt || CanStandOn( trace2.m_pEnt ) ) )
+ {
+ return true;
+ }
+ }
+
+ // Okay, try the other one
+ Vector testMin;
+ Vector testMax;
+ testMin.Init(contactMin.x, 0, vHullMins.z);
+ testMax.Init(0, contactMax.y, vHullMins.z);
+
+ TraceHull( vecUp, vecDown, testMin, testMax, collisionMask, &trace1 );
+ if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
+ {
+ testMin.Init(0, contactMin.y, vHullMins.z);
+ testMax.Init(contactMax.x, 0, vHullMins.z);
+ TraceHull( vecUp, vecDown, testMin, testMax, collisionMask, &trace2 );
+ if ( trace2.fraction != 1.0 && ( trace1.m_pEnt == trace2.m_pEnt || CanStandOn( trace2.m_pEnt ) ) )
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Center );
+ TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace1 );
+ if ( trace1.fraction != 1.0 && CanStandOn( trace1.m_pEnt ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+bool CAI_MoveProbe::OldCheckStandPosition( const Vector &vecStart, unsigned int collisionMask ) const
+{
+ AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition );
+
+ Vector contactMin, contactMax;
+
+ // this should assume the model is already standing
+ Vector vecUp = Vector( vecStart.x, vecStart.y, vecStart.z + 0.1 );
+ Vector vecDown = Vector( vecStart.x, vecStart.y, vecStart.z - StepHeight() * GetOuter()->GetStepDownMultiplier() );
+
+ // check a half sized box centered around the foot
+ const Vector &vHullMins = WorldAlignMins();
+ const Vector &vHullMaxs = WorldAlignMaxs();
+
+ contactMin.x = vHullMins.x * 0.75 + vHullMaxs.x * 0.25;
+ contactMax.x = vHullMins.x * 0.25 + vHullMaxs.x * 0.75;
+ contactMin.y = vHullMins.y * 0.75 + vHullMaxs.y * 0.25;
+ contactMax.y = vHullMins.y * 0.25 + vHullMaxs.y * 0.75;
+ contactMin.z = vHullMins.z;
+ contactMax.z = vHullMins.z;
+
+ trace_t trace;
+
+ AI_PROFILE_SCOPE_BEGIN( CAI_Motor_CheckStandPosition_Center );
+ TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace );
+ AI_PROFILE_SCOPE_END();
+
+ if (trace.fraction == 1.0 || !CanStandOn( trace.m_pEnt ))
+ return false;
+
+ float sumFraction = 0;
+
+ if ( !GetOuter()->IsFlaggedEfficient() )
+ {
+ AI_PROFILE_SCOPE( CAI_Motor_CheckStandPosition_Sides );
+
+ // check a box for each quadrant, allow one failure
+ int already_failed = false;
+ for (int x = 0; x <= 1 ;x++)
+ {
+ for (int y = 0; y <= 1; y++)
+ {
+ // create bounding boxes for each quadrant
+ contactMin[0] = x ? 0 :vHullMins.x;
+ contactMax[0] = x ? vHullMaxs.x : 0;
+ contactMin[1] = y ? 0 : vHullMins.y;
+ contactMax[1] = y ? vHullMaxs.y : 0;
+
+ TraceHull( vecUp, vecDown, contactMin, contactMax, collisionMask, &trace );
+
+ sumFraction += trace.fraction;
+
+ // this should hit something, if it doesn't allow one failure
+ if (trace.fraction == 1.0 || !CanStandOn( trace.m_pEnt ))
+ {
+ if (already_failed)
+ return false;
+ else
+ {
+ already_failed = true;
+ }
+ }
+ else
+ {
+ if ( sumFraction > 2.0 )
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes a point on the floor below the start point, somewhere
+// between vecStart.z + flStartZ and vecStart.z + flEndZ
+//-----------------------------------------------------------------------------
+bool CAI_MoveProbe::FloorPoint( const Vector &vecStart, unsigned int collisionMask,
+ float flStartZ, float flEndZ, Vector *pVecResult ) const
+{
+ AI_PROFILE_SCOPE( CAI_Motor_FloorPoint );
+
+ // make a pizzabox shaped bounding hull
+ Vector mins = WorldAlignMins();
+ Vector maxs( WorldAlignMaxs().x, WorldAlignMaxs().y, mins.z );
+
+ // trace down step height and a bit more
+ Vector vecUp( vecStart.x, vecStart.y, vecStart.z + flStartZ + MOVE_HEIGHT_EPSILON );
+ Vector vecDown( vecStart.x, vecStart.y, vecStart.z + flEndZ );
+
+ trace_t trace;
+ TraceHull( vecUp, vecDown, mins, maxs, collisionMask, &trace );
+
+ bool fStartedInObject = false;
+
+ if (trace.startsolid)
+ {
+ if ( trace.m_pEnt &&
+ ( trace.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS || trace.m_pEnt->IsNPC() ) &&
+ ( vecStart - GetLocalOrigin() ).Length() < 0.1 )
+ {
+ fStartedInObject = true;
+ }
+
+ vecUp.z = vecStart.z + MOVE_HEIGHT_EPSILON;
+ TraceHull( vecUp, vecDown, mins, maxs, collisionMask, &trace );
+ }
+
+ // this should have hit a solid surface by now
+ if (trace.fraction == 1 || trace.allsolid || ( fStartedInObject && trace.startsolid ) )
+ {
+ // set result to start position if it doesn't work
+ *pVecResult = vecStart;
+ if ( fStartedInObject )
+ return true; // in this case, probably got intruded on by a physics object. Try ignoring it...
+ return false;
+ }
+
+ *pVecResult = trace.endpos;
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// A floorPoint that is useful only in the context of iterative movement
+//-----------------------------------------------------------------------------
+bool CAI_MoveProbe::IterativeFloorPoint( const Vector &vecStart, unsigned int collisionMask, Vector *pVecResult ) const
+{
+ return IterativeFloorPoint( vecStart, collisionMask, 0, pVecResult );
+}
+
+//-----------------------------------------------------------------------------
+bool CAI_MoveProbe::IterativeFloorPoint( const Vector &vecStart, unsigned int collisionMask, float flAddedStep, Vector *pVecResult ) const
+{
+ // Used by the movement code, it guarantees we don't move outside a step
+ // height from our current position
+ return FloorPoint( vecStart, collisionMask, StepHeight() * GetOuter()->GetStepDownMultiplier() + flAddedStep, -(12*60), pVecResult );
+}
+
+//-----------------------------------------------------------------------------
+
+float CAI_MoveProbe::StepHeight() const
+{
+ return GetOuter()->StepHeight();
+}
+
+//-----------------------------------------------------------------------------
+
+bool CAI_MoveProbe::CanStandOn( CBaseEntity *pSurface ) const
+{
+ return GetOuter()->CanStandOn( pSurface );
+}
+
+//-----------------------------------------------------------------------------
+
+bool CAI_MoveProbe::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos ) const
+{
+ return GetOuter()->IsJumpLegal( startPos, apex, endPos );
+}
+
+//-----------------------------------------------------------------------------
+