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

1392 lines
45 KiB

//========= 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 );
}
//-----------------------------------------------------------------------------