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