You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1511 lines
49 KiB
1511 lines
49 KiB
//========= Copyright © 1996-2005, 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 ( 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); |
|
|
|
if ( pFuncBrush->m_iszExcludedClass == NULL_STRING ) |
|
return false; |
|
|
|
// 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() |
|
{ |
|
enginetrace->FreeTraceListData( 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 = enginetrace->AllocTraceListData(); |
|
} |
|
enginetrace->SetupLeafAndEntityListRay( ray, (const_cast<CAI_MoveProbe *>(this)->m_pTraceListData) ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Try to step over an obstacle when doing a large crawl |
|
//----------------------------------------------------------------------------- |
|
void CAI_MoveProbe::CheckStepOverLargeCrawl( CheckStepResult_t *pResult, |
|
const CheckStepArgs_t &args, const Vector &vecStart, const Vector &vecEnd, const trace_t &blockedTrace ) const |
|
{ |
|
trace_t stepTrace, upTrace; |
|
Vector vecStepStart = blockedTrace.endpos; |
|
Vector vecStepEnd = vecEnd; |
|
while ( true ) |
|
{ |
|
// First, try to crawl upward. |
|
// Trace up to locate the maximum step up in the space |
|
Vector vecStepUp( vecStepStart ); |
|
vecStepUp.z += args.stepHeight; |
|
TraceHull( vecStepStart, vecStepUp, args.collisionMask, &upTrace ); |
|
bool bHitCeiling = ( upTrace.fraction < 1.0f ); |
|
|
|
// Advance forward |
|
vecStepStart = upTrace.endpos; |
|
vecStepEnd.z = vecStepStart.z; |
|
TraceHull( vecStepStart, vecStepEnd, args.collisionMask, &stepTrace ); |
|
if ( stepTrace.fraction == 1.0f ) |
|
{ |
|
// It's clear; we're done |
|
pResult->bCrawling = true; |
|
pResult->endPoint = vecStepEnd; |
|
pResult->pBlocker = NULL; |
|
pResult->fStartSolid = blockedTrace.startsolid; |
|
break; |
|
} |
|
|
|
// FIXME: if we hit a func_breakable, we want to ignore it for the purposes of nav |
|
|
|
if ( bHitCeiling || ( stepTrace.startsolid && blockedTrace.startsolid ) ) |
|
{ |
|
// Hit the ceiling, or started in solid and never escaped; we're done |
|
pResult->bCrawling = false; |
|
pResult->endPoint = blockedTrace.endpos; |
|
pResult->hitNormal = blockedTrace.plane.normal; |
|
pResult->pBlocker = blockedTrace.m_pEnt; |
|
pResult->fStartSolid = blockedTrace.startsolid; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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; |
|
pResult->bCrawling = false; |
|
|
|
// 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.0f ) ) |
|
{ |
|
if ( args.flags & AITGM_CRAWL_LARGE_STEPS ) |
|
{ |
|
CheckStepOverLargeCrawl( pResult, args, stepStart, stepEnd, trace ); |
|
return ( pResult->pBlocker == NULL ); |
|
} |
|
|
|
// 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(); |
|
|
|
// If we're not crawling up, do some checks to ensure we're on the floor |
|
if ( !pResult->bCrawling ) |
|
{ |
|
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 ); |
|
|
|
if ( (args.flags & AITGM_CRAWL_LARGE_STEPS) && trace.fraction >= 1.0f ) |
|
{ |
|
// Now move backward... since we're crawling we don't need to test for the floor |
|
trace_t stepTrace; |
|
|
|
stepStart = trace.endpos; |
|
stepEnd = args.vecStart; |
|
|
|
stepEnd.z = stepStart.z; |
|
|
|
TraceHull( stepStart, stepEnd, collisionMask, &stepTrace ); |
|
|
|
trace.endpos = stepTrace.endpos; |
|
|
|
pResult->bCrawling = true; |
|
} |
|
|
|
// If we're not crawling we can't allow stepping into empty space |
|
if ( !(args.flags & AITGM_CRAWL_LARGE_STEPS) ) |
|
{ |
|
// 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(); |
|
|
|
// If we're not crawling check that our floor is standable |
|
if ( !(args.flags & AITGM_CRAWL_LARGE_STEPS) ) |
|
{ |
|
// Checks to see if the thing we're on is a *type* of thing we |
|
// are capable of standing on. Always true for 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 |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Confirm 3D connectivity between 2 nodes |
|
//----------------------------------------------------------------------------- |
|
bool CAI_MoveProbe::Confirm3DConnectivity( AIMoveTrace_t *pMoveTrace, unsigned flags, const Vector &vecDesiredEnd ) const |
|
{ |
|
// 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 ) |
|
return true; |
|
|
|
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; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 Confirm3DConnectivity( pMoveTrace, flags, vecDesiredEnd ); |
|
|
|
// 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; |
|
checkStepArgs.flags = flags; |
|
|
|
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(); |
|
|
|
if ( checkStepResult.bCrawling ) |
|
{ |
|
// Weren't not really blocked when crawling up, but need to do it in steps |
|
checkStepResult.pBlocker = NULL; |
|
} |
|
|
|
break; |
|
} |
|
|
|
// If we're not crawling up add on the z change to the distance |
|
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; |
|
} |
|
|
|
return Confirm3DConnectivity( pMoveTrace, flags, vecDesiredEnd ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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, sv_gravity.GetFloat() * 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 flNPCMinJumpHeight = GetOuter()->GetMinJumpHeight(); |
|
if ( flNPCMinJumpHeight && minJumpHeight < flNPCMinJumpHeight ) |
|
{ |
|
minJumpHeight = flNPCMinJumpHeight; |
|
} |
|
|
|
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, GetOuter()->GetAITraceMask(), &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_CRAWL: |
|
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 ( navType == NAV_CRAWL ) |
|
{ |
|
testGroundMoveFlags |= AITGM_CRAWL_LARGE_STEPS; |
|
} |
|
|
|
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 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
|