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

1696 lines
53 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "trains.h"
#include "ai_trackpather.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define TRACKPATHER_DEBUG_LEADING 1
#define TRACKPATHER_DEBUG_PATH 2
#define TRACKPATHER_DEBUG_TRACKS 3
ConVar g_debug_trackpather( "g_debug_trackpather", "0", FCVAR_CHEAT );
//------------------------------------------------------------------------------
BEGIN_DATADESC( CAI_TrackPather )
DEFINE_FIELD( m_vecDesiredPosition, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_vecGoalOrientation, FIELD_VECTOR ),
DEFINE_FIELD( m_pCurrentPathTarget, FIELD_CLASSPTR ),
DEFINE_FIELD( m_pDestPathTarget, FIELD_CLASSPTR ),
DEFINE_FIELD( m_pLastPathTarget, FIELD_CLASSPTR ),
DEFINE_FIELD( m_pTargetNearestPath, FIELD_CLASSPTR ),
DEFINE_FIELD( m_strCurrentPathName, FIELD_STRING ),
DEFINE_FIELD( m_strDestPathName, FIELD_STRING ),
DEFINE_FIELD( m_strLastPathName, FIELD_STRING ),
DEFINE_FIELD( m_strTargetNearestPathName, FIELD_STRING ),
DEFINE_FIELD( m_vecLastGoalCheckPosition, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_flEnemyPathUpdateTime, FIELD_TIME ),
DEFINE_FIELD( m_bForcedMove, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bPatrolling, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bPatrolBreakable, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bLeading, FIELD_BOOLEAN ),
// Derived class pathing data
DEFINE_FIELD( m_flTargetDistanceThreshold, FIELD_FLOAT ),
DEFINE_FIELD( m_flAvoidDistance, FIELD_FLOAT ),
DEFINE_FIELD( m_flTargetTolerance, FIELD_FLOAT ),
DEFINE_FIELD( m_vecSegmentStartPoint, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_vecSegmentStartSplinePoint, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_bMovingForward, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bChooseFarthestPoint, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flFarthestPathDist, FIELD_FLOAT ),
DEFINE_FIELD( m_flPathMaxSpeed, FIELD_FLOAT ),
DEFINE_FIELD( m_flTargetDistFromPath, FIELD_FLOAT ),
DEFINE_FIELD( m_flLeadDistance, FIELD_FLOAT ),
DEFINE_FIELD( m_vecTargetPathDir, FIELD_VECTOR ),
DEFINE_FIELD( m_vecTargetPathPoint, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_nPauseState, FIELD_INTEGER ),
// Inputs
DEFINE_INPUTFUNC( FIELD_STRING, "SetTrack", InputSetTrack ),
DEFINE_INPUTFUNC( FIELD_STRING, "FlyToSpecificTrackViaPath", InputFlyToPathTrack ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrol", InputStartPatrol ),
DEFINE_INPUTFUNC( FIELD_VOID, "StopPatrol", InputStopPatrol ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartBreakableMovement", InputStartBreakableMovement ),
DEFINE_INPUTFUNC( FIELD_VOID, "StopBreakableMovement", InputStopBreakableMovement ),
DEFINE_INPUTFUNC( FIELD_VOID, "ChooseFarthestPathPoint", InputChooseFarthestPathPoint ),
DEFINE_INPUTFUNC( FIELD_VOID, "ChooseNearestPathPoint", InputChooseNearestPathPoint ),
DEFINE_INPUTFUNC( FIELD_INTEGER,"InputStartLeading", InputStartLeading ),
DEFINE_INPUTFUNC( FIELD_VOID, "InputStopLeading", InputStopLeading ),
// Obsolete, for backwards compatibility
DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrolBreakable", InputStartPatrolBreakable ),
DEFINE_INPUTFUNC( FIELD_STRING, "FlyToPathTrack", InputFlyToPathTrack ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Initialize pathing data
//-----------------------------------------------------------------------------
void CAI_TrackPather::InitPathingData( float flTrackArrivalTolerance, float flTargetDistance, float flAvoidDistance )
{
m_flTargetTolerance = flTrackArrivalTolerance;
m_flTargetDistanceThreshold = flTargetDistance;
m_flAvoidDistance = flAvoidDistance;
m_pCurrentPathTarget = NULL;
m_pDestPathTarget = NULL;
m_pLastPathTarget = NULL;
m_pTargetNearestPath = NULL;
m_bLeading = false;
m_flEnemyPathUpdateTime = gpGlobals->curtime;
m_bForcedMove = false;
m_bPatrolling = false;
m_bPatrolBreakable = false;
m_flLeadDistance = 0.0f;
m_bMovingForward = true;
m_vecSegmentStartPoint = m_vecSegmentStartSplinePoint = m_vecDesiredPosition = GetAbsOrigin();
m_bChooseFarthestPoint = true;
m_flFarthestPathDist = 1e10;
m_flPathMaxSpeed = 0;
m_nPauseState = PAUSE_NO_PAUSE;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_TrackPather::OnRestore( void )
{
BaseClass::OnRestore();
// Restore current path
if ( m_strCurrentPathName != NULL_STRING )
{
m_pCurrentPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strCurrentPathName );
}
else
{
m_pCurrentPathTarget = NULL;
}
// Restore destination path
if ( m_strDestPathName != NULL_STRING )
{
m_pDestPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strDestPathName );
}
else
{
m_pDestPathTarget = NULL;
}
// Restore last path
if ( m_strLastPathName != NULL_STRING )
{
m_pLastPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strLastPathName );
}
else
{
m_pLastPathTarget = NULL;
}
// Restore target nearest path
if ( m_strTargetNearestPathName != NULL_STRING )
{
m_pTargetNearestPath = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strTargetNearestPathName );
}
else
{
m_pTargetNearestPath = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_TrackPather::OnSave( IEntitySaveUtils *pUtils )
{
BaseClass::OnSave( pUtils );
// Stash all the paths into strings for restoration later
m_strCurrentPathName = ( m_pCurrentPathTarget != NULL ) ? m_pCurrentPathTarget->GetEntityName() : NULL_STRING;
m_strDestPathName = ( m_pDestPathTarget != NULL ) ? m_pDestPathTarget->GetEntityName() : NULL_STRING;
m_strLastPathName = ( m_pLastPathTarget != NULL ) ? m_pLastPathTarget->GetEntityName() : NULL_STRING;
m_strTargetNearestPathName = ( m_pTargetNearestPath != NULL ) ? m_pTargetNearestPath->GetEntityName() : NULL_STRING;
}
//-----------------------------------------------------------------------------
// Leading distance
//-----------------------------------------------------------------------------
void CAI_TrackPather::EnableLeading( bool bEnable )
{
bool bWasLeading = m_bLeading;
m_bLeading = bEnable;
if ( m_bLeading )
{
m_bPatrolling = false;
}
else if ( bWasLeading )
{
// Going from leading to not leading. Refresh the desired position
// to prevent us from hovering around our old, no longer valid lead position.
if ( m_pCurrentPathTarget )
{
SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() );
}
}
}
void CAI_TrackPather::SetLeadingDistance( float flLeadDistance )
{
m_flLeadDistance = flLeadDistance;
}
float CAI_TrackPather::GetLeadingDistance( ) const
{
return m_flLeadDistance;
}
//-----------------------------------------------------------------------------
// Returns the next path along our current path
//-----------------------------------------------------------------------------
inline CPathTrack *CAI_TrackPather::NextAlongCurrentPath( CPathTrack *pPath ) const
{
return CPathTrack::ValidPath( m_bMovingForward ? pPath->GetNext() : pPath->GetPrevious() );
}
inline CPathTrack *CAI_TrackPather::PreviousAlongCurrentPath( CPathTrack *pPath ) const
{
return CPathTrack::ValidPath( m_bMovingForward ? pPath->GetPrevious() : pPath->GetNext() );
}
inline CPathTrack *CAI_TrackPather::AdjustForMovementDirection( CPathTrack *pPath ) const
{
if ( !m_bMovingForward && CPathTrack::ValidPath( pPath->GetPrevious( ) ) )
{
pPath = CPathTrack::ValidPath( pPath->GetPrevious() );
}
return pPath;
}
//-----------------------------------------------------------------------------
// Enemy visibility check
//-----------------------------------------------------------------------------
CBaseEntity *CAI_TrackPather::FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos )
{
trace_t tr;
AI_TraceHull( vecViewPoint, vecTargetPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
return (tr.fraction != 1.0f) ? tr.m_pEnt : NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &targetPos -
// Output : CBaseEntity
//-----------------------------------------------------------------------------
CPathTrack *CAI_TrackPather::BestPointOnPath( CPathTrack *pPath, const Vector &targetPos, float flAvoidRadius, bool visible, bool bFarthestPoint )
{
// Find the node nearest to the destination path target if a path is not specified
if ( pPath == NULL )
{
pPath = m_pDestPathTarget;
}
// If the path node we're trying to use is not valid, then we're done.
if ( CPathTrack::ValidPath( pPath ) == NULL )
{
//FIXME: Implement
Assert(0);
return NULL;
}
// Our target may be in a vehicle
CBaseEntity *pVehicle = NULL;
CBaseEntity *pTargetEnt = GetTrackPatherTargetEnt();
if ( pTargetEnt != NULL )
{
CBaseCombatCharacter *pCCTarget = pTargetEnt->MyCombatCharacterPointer();
if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() )
{
pVehicle = pCCTarget->GetVehicleEntity();
}
}
// Faster math...
flAvoidRadius *= flAvoidRadius;
// Find the nearest node to the target (going forward)
CPathTrack *pNearestPath = NULL;
float flNearestDist = bFarthestPoint ? 0 : 999999999;
float flPathDist;
float flFarthestDistSqr = ( m_flFarthestPathDist - 2.0f * m_flTargetDistanceThreshold );
flFarthestDistSqr *= flFarthestDistSqr;
// NOTE: Gotta do it this crazy way because paths can be one-way.
for ( int i = 0; i < 2; ++i )
{
int loopCheck = 0;
CPathTrack *pTravPath = pPath;
CPathTrack *pNextPath;
BEGIN_PATH_TRACK_ITERATION();
for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath, loopCheck++ )
{
// Circular loop checking
if ( pTravPath->HasBeenVisited() )
break;
pTravPath->Visit();
pNextPath = (i == 0) ? pTravPath->GetPrevious() : pTravPath->GetNext();
// Find the distance between this test point and our goal point
flPathDist = ( pTravPath->GetAbsOrigin() - targetPos ).LengthSqr();
// See if it's closer and it's also not within our avoidance radius
if ( bFarthestPoint )
{
if ( ( flPathDist <= flNearestDist ) && ( flNearestDist <= flFarthestDistSqr ) )
continue;
}
else
{
if ( flPathDist >= flNearestDist )
continue;
}
// Don't choose points that are within the avoid radius
if ( flAvoidRadius && ( pTravPath->GetAbsOrigin() - targetPos ).Length2DSqr() <= flAvoidRadius )
continue;
if ( visible )
{
// If it has to be visible, run those checks
CBaseEntity *pBlocker = FindTrackBlocker( pTravPath->GetAbsOrigin(), targetPos );
// Check to see if we've hit the target, or the player's vehicle if it's a player in a vehicle
bool bHitTarget = ( pTargetEnt && ( pTargetEnt == pBlocker ) ) ||
( pVehicle && ( pVehicle == pBlocker ) );
// If we hit something, and it wasn't the target or his vehicle, then no dice
// If we hit the target and forced move was set, *still* no dice
if ( (pBlocker != NULL) && ( !bHitTarget || m_bForcedMove ) )
continue;
}
pNearestPath = pTravPath;
flNearestDist = flPathDist;
}
}
return pNearestPath;
}
//-----------------------------------------------------------------------------
// Compute a point n units along a path
//-----------------------------------------------------------------------------
CPathTrack *CAI_TrackPather::ComputeLeadingPointAlongPath( const Vector &vecStartPoint,
CPathTrack *pFirstTrack, float flDistance, Vector *pTarget )
{
bool bMovingForward = (flDistance > 0.0f);
flDistance = fabs(flDistance);
CPathTrack *pTravPath = pFirstTrack;
if ( (!bMovingForward) && pFirstTrack->GetPrevious() )
{
pTravPath = pFirstTrack->GetPrevious();
}
*pTarget = vecStartPoint;
CPathTrack *pNextPath;
// No circular loop checking needed; eventually, it'll run out of distance
for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath )
{
pNextPath = bMovingForward ? pTravPath->GetNext() : pTravPath->GetPrevious();
// Find the distance between this test point and our goal point
float flPathDist = pTarget->DistTo( pTravPath->GetAbsOrigin() );
// Find the distance between this test point and our goal point
if ( flPathDist <= flDistance )
{
flDistance -= flPathDist;
*pTarget = pTravPath->GetAbsOrigin();
if ( !CPathTrack::ValidPath(pNextPath) )
return bMovingForward ? pTravPath : pTravPath->GetNext();
continue;
}
ComputeClosestPoint( *pTarget, flDistance, pTravPath->GetAbsOrigin(), pTarget );
return bMovingForward ? pTravPath : pTravPath->GetNext();
}
return NULL;
}
//-----------------------------------------------------------------------------
// Compute the distance to a particular point on the path
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputeDistanceAlongPathToPoint( CPathTrack *pStartTrack,
CPathTrack *pDestTrack, const Vector &vecDestPosition, bool bMovingForward )
{
float flTotalDist = 0.0f;
Vector vecPoint;
ClosestPointToCurrentPath( &vecPoint );
CPathTrack *pTravPath = pStartTrack;
CPathTrack *pNextPath, *pTestPath;
BEGIN_PATH_TRACK_ITERATION();
for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath )
{
// Circular loop checking
if ( pTravPath->HasBeenVisited() )
break;
// Mark it as being visited.
pTravPath->Visit();
pNextPath = bMovingForward ? pTravPath->GetNext() : pTravPath->GetPrevious();
pTestPath = pTravPath;
Assert( pTestPath );
if ( pTravPath == pDestTrack )
{
Vector vecDelta;
Vector vecPathDelta;
VectorSubtract( vecDestPosition, vecPoint, vecDelta );
ComputePathDirection( pTravPath, &vecPathDelta );
float flDot = DotProduct( vecDelta, vecPathDelta );
flTotalDist += (flDot > 0.0f ? 1.0f : -1.0f) * vecDelta.Length2D();
break;
}
// NOTE: This would be made more accurate if we did the path direction check here too.
// The starting vecPoint is sometimes *not* within the bounds of the line segment.
// Find the distance between this test point and our goal point
flTotalDist += (bMovingForward ? 1.0f : -1.0f) * vecPoint.AsVector2D().DistTo( pTestPath->GetAbsOrigin().AsVector2D() );
vecPoint = pTestPath->GetAbsOrigin();
}
return flTotalDist;
}
//------------------------------------------------------------------------------
// Track debugging info
//------------------------------------------------------------------------------
void CAI_TrackPather::VisualizeDebugInfo( const Vector &vecNearestPoint, const Vector &vecTarget )
{
if ( g_debug_trackpather.GetInt() == TRACKPATHER_DEBUG_PATH )
{
NDebugOverlay::Line( m_vecSegmentStartPoint, vecTarget, 0, 0, 255, true, 0.1f );
NDebugOverlay::Cross3D( vecNearestPoint, -Vector(16,16,16), Vector(16,16,16), 255, 0, 0, true, 0.1f );
NDebugOverlay::Cross3D( m_pCurrentPathTarget->GetAbsOrigin(), -Vector(16,16,16), Vector(16,16,16), 0, 255, 0, true, 0.1f );
NDebugOverlay::Cross3D( m_vecDesiredPosition, -Vector(16,16,16), Vector(16,16,16), 0, 0, 255, true, 0.1f );
NDebugOverlay::Cross3D( m_pDestPathTarget->GetAbsOrigin(), -Vector(16,16,16), Vector(16,16,16), 255, 255, 255, true, 0.1f );
if ( m_pTargetNearestPath )
{
NDebugOverlay::Cross3D( m_pTargetNearestPath->GetAbsOrigin(), -Vector(24,24,24), Vector(24,24,24), 255, 0, 255, true, 0.1f );
}
}
if ( g_debug_trackpather.GetInt() == TRACKPATHER_DEBUG_TRACKS )
{
if ( m_pCurrentPathTarget )
{
CPathTrack *pPathTrack = m_pCurrentPathTarget;
for ( ; CPathTrack::ValidPath( pPathTrack ); pPathTrack = pPathTrack->GetNext() )
{
NDebugOverlay::Box( pPathTrack->GetAbsOrigin(), -Vector(2,2,2), Vector(2,2,2), 0,255, 0, 8, 0.1 );
if ( CPathTrack::ValidPath( pPathTrack->GetNext() ) )
{
NDebugOverlay::Line( pPathTrack->GetAbsOrigin(), pPathTrack->GetNext()->GetAbsOrigin(), 0,255,0, true, 0.1 );
}
if ( pPathTrack->GetNext() == m_pCurrentPathTarget )
break;
}
}
}
}
//------------------------------------------------------------------------------
// Does this path track have LOS to the target?
//------------------------------------------------------------------------------
bool CAI_TrackPather::HasLOSToTarget( CPathTrack *pTrack )
{
CBaseEntity *pTargetEnt = GetTrackPatherTargetEnt();
if ( !pTargetEnt )
return true;
Vector targetPos;
if ( !GetTrackPatherTarget( &targetPos ) )
return true;
// Translate driver into vehicle for testing
CBaseEntity *pVehicle = NULL;
CBaseCombatCharacter *pCCTarget = pTargetEnt->MyCombatCharacterPointer();
if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() )
{
pVehicle = pCCTarget->GetVehicleEntity();
}
// If it has to be visible, run those checks
CBaseEntity *pBlocker = FindTrackBlocker( pTrack->GetAbsOrigin(), targetPos );
// Check to see if we've hit the target, or the player's vehicle if it's a player in a vehicle
bool bHitTarget = ( pTargetEnt && ( pTargetEnt == pBlocker ) ) ||
( pVehicle && ( pVehicle == pBlocker ) );
return (pBlocker == NULL) || bHitTarget;
}
//------------------------------------------------------------------------------
// Moves to the track
//------------------------------------------------------------------------------
void CAI_TrackPather::UpdateCurrentTarget()
{
// Find the point along the line that we're closest to.
const Vector &vecTarget = m_pCurrentPathTarget->GetAbsOrigin();
Vector vecPoint;
float t = ClosestPointToCurrentPath( &vecPoint );
if ( (t < 1.0f) && ( vecPoint.DistToSqr( vecTarget ) > m_flTargetTolerance * m_flTargetTolerance ) )
goto visualizeDebugInfo;
// Forced move is gone as soon as we've reached the first point on our path
if ( m_bLeading )
{
m_bForcedMove = false;
}
// Trip our "path_track reached" output
if ( m_pCurrentPathTarget != m_pLastPathTarget )
{
// Get the path's specified max speed
m_flPathMaxSpeed = m_pCurrentPathTarget->m_flSpeed;
variant_t emptyVariant;
m_pCurrentPathTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 );
m_pLastPathTarget = m_pCurrentPathTarget;
}
if ( m_nPauseState == PAUSED_AT_POSITION )
return;
if ( m_nPauseState == PAUSE_AT_NEXT_LOS_POSITION )
{
if ( HasLOSToTarget(m_pCurrentPathTarget) )
{
m_nPauseState = PAUSED_AT_POSITION;
return;
}
}
// Update our dest path target, if appropriate...
if ( m_pCurrentPathTarget == m_pDestPathTarget )
{
m_bForcedMove = false;
SelectNewDestTarget();
}
// Did SelectNewDestTarget give us a new point to move to?
if ( m_pCurrentPathTarget != m_pDestPathTarget )
{
// Update to the next path, if there is one...
m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget );
if ( !m_pCurrentPathTarget )
{
m_pCurrentPathTarget = m_pLastPathTarget;
}
}
else
{
// We're at rest (no patrolling behavior), which means we're moving forward now.
m_bMovingForward = true;
}
SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() );
m_vecSegmentStartSplinePoint = m_vecSegmentStartPoint;
m_vecSegmentStartPoint = m_pLastPathTarget->GetAbsOrigin();
visualizeDebugInfo:
VisualizeDebugInfo( vecPoint, vecTarget );
}
//-----------------------------------------------------------------------------
//
// NOTE: All code below is used exclusively for leading/trailing behavior
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Compute the distance to the leading position
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputeDistanceToLeadingPosition()
{
return ComputeDistanceAlongPathToPoint( m_pCurrentPathTarget, m_pDestPathTarget, GetDesiredPosition(), m_bMovingForward );
}
//-----------------------------------------------------------------------------
// Compute the distance to the *target* position
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputeDistanceToTargetPosition()
{
Assert( m_pTargetNearestPath );
CPathTrack *pDest = m_bMovingForward ? m_pTargetNearestPath.Get() : m_pTargetNearestPath->GetPrevious();
if ( !pDest )
{
pDest = m_pTargetNearestPath;
}
bool bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pDest );
CPathTrack *pStart = m_pCurrentPathTarget;
if ( bMovingForward != m_bMovingForward )
{
if (bMovingForward)
{
if ( pStart->GetNext() )
{
pStart = pStart->GetNext();
}
if ( pDest->GetNext() )
{
pDest = pDest->GetNext();
}
}
else
{
if ( pStart->GetPrevious() )
{
pStart = pStart->GetPrevious();
}
if ( pDest->GetPrevious() )
{
pDest = pDest->GetPrevious();
}
}
}
return ComputeDistanceAlongPathToPoint( pStart, pDest, m_vecTargetPathPoint, bMovingForward );
}
//-----------------------------------------------------------------------------
// Compute a path direction
//-----------------------------------------------------------------------------
void CAI_TrackPather::ComputePathDirection( CPathTrack *pPath, Vector *pVecPathDir )
{
if ( pPath->GetPrevious() )
{
VectorSubtract( pPath->GetAbsOrigin(), pPath->GetPrevious()->GetAbsOrigin(), *pVecPathDir );
}
else
{
if ( pPath->GetNext() )
{
VectorSubtract( pPath->GetNext()->GetAbsOrigin(), pPath->GetAbsOrigin(), *pVecPathDir );
}
else
{
pVecPathDir->Init( 1, 0, 0 );
}
}
VectorNormalize( *pVecPathDir );
}
//-----------------------------------------------------------------------------
// What's the current path direction?
//-----------------------------------------------------------------------------
void CAI_TrackPather::CurrentPathDirection( Vector *pVecPathDir )
{
if ( m_pCurrentPathTarget )
{
ComputePathDirection( m_pCurrentPathTarget, pVecPathDir );
}
else
{
pVecPathDir->Init( 0, 0, 1 );
}
}
//-----------------------------------------------------------------------------
// Compute a point n units along the current path from our current position
// (but don't pass the desired target point)
//-----------------------------------------------------------------------------
void CAI_TrackPather::ComputePointAlongCurrentPath( float flDistance, float flPerpDist, Vector *pTarget )
{
Vector vecPathDir;
Vector vecStartPoint;
ClosestPointToCurrentPath( &vecStartPoint );
*pTarget = vecStartPoint;
if ( flDistance != 0.0f )
{
Vector vecPrevPoint = vecStartPoint;
CPathTrack *pTravPath = m_pCurrentPathTarget;
CPathTrack *pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget );
for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = NextAlongCurrentPath( pTravPath ) )
{
if ( pTravPath == pAdjustedDest )
{
ComputePathDirection( pTravPath, &vecPathDir );
float flPathDist = pTarget->DistTo( GetDesiredPosition() );
if ( flDistance > flPathDist )
{
*pTarget = GetDesiredPosition();
}
else
{
ComputeClosestPoint( *pTarget, flDistance, GetDesiredPosition(), pTarget );
}
break;
}
// Find the distance between this test point and our goal point
float flPathDist = pTarget->DistTo( pTravPath->GetAbsOrigin() );
// Find the distance between this test point and our goal point
if ( flPathDist <= flDistance )
{
flDistance -= flPathDist;
*pTarget = pTravPath->GetAbsOrigin();
// FIXME: Reduce the distance further based on the angle between this segment + the next
continue;
}
ComputePathDirection( pTravPath, &vecPathDir );
ComputeClosestPoint( *pTarget, flDistance, pTravPath->GetAbsOrigin(), pTarget );
break;
}
}
else
{
VectorSubtract( m_pCurrentPathTarget->GetAbsOrigin(), m_vecSegmentStartPoint, vecPathDir );
VectorNormalize( vecPathDir );
}
// Add in the horizontal component
ComputePointFromPerpDistance( *pTarget, vecPathDir, flPerpDist, pTarget );
}
//-----------------------------------------------------------------------------
// Methods to find a signed perp distance from the track
// and to compute a point off the path based on the signed perp distance
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputePerpDistanceFromPath( const Vector &vecPointOnPath, const Vector &vecPathDir, const Vector &vecPointOffPath )
{
// Make it be a signed distance of the target from the path
// Positive means on the right side, negative means on the left side
Vector vecAcross, vecDelta;
CrossProduct( vecPathDir, Vector( 0, 0, 1 ), vecAcross );
VectorSubtract( vecPointOffPath, vecPointOnPath, vecDelta );
VectorMA( vecDelta, -DotProduct( vecPathDir, vecDelta ), vecPathDir, vecDelta );
float flDistanceFromPath = vecDelta.Length2D();
if ( DotProduct2D( vecAcross.AsVector2D(), vecDelta.AsVector2D() ) < 0.0f )
{
flDistanceFromPath *= -1.0f;
}
return flDistanceFromPath;
}
void CAI_TrackPather::ComputePointFromPerpDistance( const Vector &vecPointOnPath, const Vector &vecPathDir, float flPerpDist, Vector *pResult )
{
Vector vecAcross;
CrossProduct( vecPathDir, Vector( 0, 0, 1 ), vecAcross );
VectorMA( vecPointOnPath, flPerpDist, vecAcross, *pResult );
}
//-----------------------------------------------------------------------------
// Finds the closest point on the path, returns a signed perpendicular distance
// where negative means on the left side of the path (when travelled from prev to next)
// and positive means on the right side
//-----------------------------------------------------------------------------
CPathTrack *CAI_TrackPather::FindClosestPointOnPath( CPathTrack *pPath,
const Vector &targetPos, Vector *pVecClosestPoint, Vector *pVecPathDir, float *pDistanceFromPath )
{
// Find the node nearest to the destination path target if a path is not specified
if ( pPath == NULL )
{
pPath = m_pDestPathTarget;
}
// If the path node we're trying to use is not valid, then we're done.
if ( CPathTrack::ValidPath( pPath ) == NULL )
{
//FIXME: Implement
Assert(0);
return NULL;
}
// Find the nearest node to the target (going forward)
CPathTrack *pNearestPath = NULL;
float flNearestDist2D = 999999999;
float flNearestDist = 999999999;
float flPathDist, flPathDist2D;
// NOTE: Gotta do it this crazy way because paths can be one-way.
Vector vecNearestPoint;
Vector vecNearestPathSegment;
for ( int i = 0; i < 2; ++i )
{
int loopCheck = 0;
CPathTrack *pTravPath = pPath;
CPathTrack *pNextPath;
BEGIN_PATH_TRACK_ITERATION();
for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath, loopCheck++ )
{
// Circular loop checking
if ( pTravPath->HasBeenVisited() )
break;
// Mark it as being visited.
pTravPath->Visit();
pNextPath = (i == 0) ? pTravPath->GetPrevious() : pTravPath->GetNext();
// No alt paths allowed in leading mode.
if ( pTravPath->m_paltpath )
{
Warning( "%s: Alternative paths in path_track not allowed when using the leading behavior!\n", GetEntityName().ToCStr() );
}
// Need line segments
if ( !CPathTrack::ValidPath(pNextPath) )
break;
// Find the closest point on the line segment on the path
Vector vecClosest;
CalcClosestPointOnLineSegment( targetPos, pTravPath->GetAbsOrigin(), pNextPath->GetAbsOrigin(), vecClosest );
// Find the distance between this test point and our goal point
flPathDist2D = vecClosest.AsVector2D().DistToSqr( targetPos.AsVector2D() );
if ( flPathDist2D > flNearestDist2D )
continue;
flPathDist = vecClosest.z - targetPos.z;
flPathDist *= flPathDist;
flPathDist += flPathDist2D;
if (( flPathDist2D == flNearestDist2D ) && ( flPathDist >= flNearestDist ))
continue;
pNearestPath = (i == 0) ? pTravPath : pNextPath;
flNearestDist2D = flPathDist2D;
flNearestDist = flPathDist;
vecNearestPoint = vecClosest;
VectorSubtract( pNextPath->GetAbsOrigin(), pTravPath->GetAbsOrigin(), vecNearestPathSegment );
if ( i == 0 )
{
vecNearestPathSegment *= -1.0f;
}
}
}
VectorNormalize( vecNearestPathSegment );
*pDistanceFromPath = ComputePerpDistanceFromPath( vecNearestPoint, vecNearestPathSegment, targetPos );
*pVecClosestPoint = vecNearestPoint;
*pVecPathDir = vecNearestPathSegment;
return pNearestPath;
}
//-----------------------------------------------------------------------------
// Breakable paths?
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputStartBreakableMovement( inputdata_t &inputdata )
{
m_bPatrolBreakable = true;
}
void CAI_TrackPather::InputStopBreakableMovement( inputdata_t &inputdata )
{
m_bPatrolBreakable = false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputStartPatrol( inputdata_t &inputdata )
{
m_bPatrolling = true;
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputStopPatrol( inputdata_t &inputdata )
{
m_bPatrolling = false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputStartPatrolBreakable( inputdata_t &inputdata )
{
m_bPatrolBreakable = true;
m_bPatrolling = true;
}
//------------------------------------------------------------------------------
// Leading behaviors
//------------------------------------------------------------------------------
void CAI_TrackPather::InputStartLeading( inputdata_t &inputdata )
{
EnableLeading( true );
SetLeadingDistance( inputdata.value.Int() );
}
void CAI_TrackPather::InputStopLeading( inputdata_t &inputdata )
{
EnableLeading( false );
}
//------------------------------------------------------------------------------
// Selects a new destination target
//------------------------------------------------------------------------------
void CAI_TrackPather::SelectNewDestTarget()
{
if ( !m_bPatrolling )
return;
// NOTE: This version is bugged, but I didn't want to make the fix
// here for fear of breaking a lot of maps late in the day.
// So, only the chopper does the "right" thing.
#ifdef HL2_EPISODIC
// Episodic uses the fixed logic for all trackpathers
if ( 1 )
#else
if ( ShouldUseFixedPatrolLogic() )
#endif
{
CPathTrack *pOldDest = m_pDestPathTarget;
// Only switch polarity of movement if we're at the *end* of the path
// This is really useful for initial conditions of patrolling
// NOTE: We've got to do some extra work for circular paths
bool bIsCircular = false;
{
BEGIN_PATH_TRACK_ITERATION();
CPathTrack *pTravPath = m_pDestPathTarget;
while( CPathTrack::ValidPath( pTravPath ) )
{
// Circular loop checking
if ( pTravPath->HasBeenVisited() )
{
bIsCircular = true;
break;
}
pTravPath->Visit();
pTravPath = NextAlongCurrentPath( pTravPath );
}
}
if ( bIsCircular || (NextAlongCurrentPath( m_pDestPathTarget ) == NULL) )
{
m_bMovingForward = !m_bMovingForward;
}
BEGIN_PATH_TRACK_ITERATION();
while ( true )
{
CPathTrack *pNextTrack = NextAlongCurrentPath( m_pDestPathTarget );
if ( !pNextTrack || (pNextTrack == pOldDest) || pNextTrack->HasBeenVisited() )
break;
pNextTrack->Visit();
m_pDestPathTarget = pNextTrack;
}
}
else
{
CPathTrack *pOldDest = m_pDestPathTarget;
// For patrolling, switch the polarity of movement
m_bMovingForward = !m_bMovingForward;
int loopCount = 0;
while ( true )
{
CPathTrack *pNextTrack = NextAlongCurrentPath( m_pDestPathTarget );
if ( !pNextTrack )
break;
if ( ++loopCount > 1024 )
{
DevMsg(1,"WARNING: Looping path for %s\n", GetDebugName() );
break;
}
m_pDestPathTarget = pNextTrack;
}
if ( m_pDestPathTarget == pOldDest )
{
// This can occur if we move to the first point on the path
SelectNewDestTarget();
}
}
}
//------------------------------------------------------------------------------
// Moves to the track
//------------------------------------------------------------------------------
void CAI_TrackPather::UpdateCurrentTargetLeading()
{
bool bRestingAtDest = false;
CPathTrack *pAdjustedDest;
// Find the point along the line that we're closest to.
const Vector &vecTarget = m_pCurrentPathTarget->GetAbsOrigin();
Vector vecPoint;
float t = ClosestPointToCurrentPath( &vecPoint );
if ( (t < 1.0f) && ( vecPoint.DistToSqr( vecTarget ) > m_flTargetTolerance * m_flTargetTolerance ) )
goto visualizeDebugInfo;
// Trip our "path_track reached" output
if ( m_pCurrentPathTarget != m_pLastPathTarget )
{
// Get the path's specified max speed
m_flPathMaxSpeed = m_pCurrentPathTarget->m_flSpeed;
variant_t emptyVariant;
m_pCurrentPathTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 );
m_pLastPathTarget = m_pCurrentPathTarget;
}
// NOTE: CurrentPathTarget doesn't mean the same thing as dest path target!
// It's the "next"most when moving forward + "prev"most when moving backward
// Must do the tests in the same space
pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget );
// Update our dest path target, if appropriate...
if ( m_pCurrentPathTarget == pAdjustedDest )
{
m_bForcedMove = false;
SelectNewDestTarget();
// NOTE: Must do this again since SelectNewDestTarget may change m_pDestPathTarget
pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget );
}
if ( m_pCurrentPathTarget != pAdjustedDest )
{
// Update to the next path, if there is one...
m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget );
if ( !m_pCurrentPathTarget )
{
m_pCurrentPathTarget = m_pLastPathTarget;
}
}
else
{
// NOTE: Have to do this here because the NextAlongCurrentPath call above
// could make m_pCurrentPathTarget == m_pDestPathTarget.
// In this case, we're at rest (no patrolling behavior)
bRestingAtDest = true;
}
if ( bRestingAtDest )
{
// NOTE: Must use current path target, instead of dest
// to get the PreviousAlongCurrentPath working correctly
CPathTrack *pSegmentStart = PreviousAlongCurrentPath( m_pCurrentPathTarget );
if ( !pSegmentStart )
{
pSegmentStart = m_pCurrentPathTarget;
}
m_vecSegmentStartPoint = pSegmentStart->GetAbsOrigin();
}
else
{
m_vecSegmentStartPoint = m_pLastPathTarget->GetAbsOrigin();
}
visualizeDebugInfo:
VisualizeDebugInfo( vecPoint, vecTarget );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_TrackPather::UpdateTargetPositionLeading( void )
{
Vector targetPos;
if ( !GetTrackPatherTarget( &targetPos ) )
return;
// NOTE: FindClosestPointOnPath *always* returns the point on the "far",
// end of the line segment containing the closest point (namely the 'next'
// track, as opposed to the 'prev' track)
Vector vecClosestPoint, vecPathDir;
float flTargetDistanceFromPath;
CPathTrack *pNextPath = FindClosestPointOnPath( m_pCurrentPathTarget,
targetPos, &vecClosestPoint, &vecPathDir, &flTargetDistanceFromPath );
// This means that a valid path could not be found to our target!
if ( CPathTrack::ValidPath( pNextPath ) == NULL )
return;
// NDebugOverlay::Cross3D( vecClosestPoint, -Vector(24,24,24), Vector(24,24,24), 0, 255, 255, true, 0.1f );
// NDebugOverlay::Cross3D( pNextPath->GetAbsOrigin(), -Vector(24,24,24), Vector(24,24,24), 255, 255, 0, true, 0.1f );
// Here's how far we are from the path
m_flTargetDistFromPath = flTargetDistanceFromPath;
m_vecTargetPathDir = vecPathDir;
// Here's info about where the target is along the path
m_vecTargetPathPoint = vecClosestPoint;
m_pTargetNearestPath = pNextPath;
// Find the best position to be on our path
// NOTE: This will *also* return a path track on the "far" end of the line segment
// containing the leading position, namely the "next" end of the segment as opposed
// to the "prev" end of the segment.
CPathTrack *pDest = ComputeLeadingPointAlongPath( vecClosestPoint, pNextPath, m_flLeadDistance, &targetPos );
SetDesiredPosition( targetPos );
// We only want to switch movement directions when absolutely necessary
// so convert dest into a more appropriate value based on the current movement direction
if ( pDest != m_pDestPathTarget )
{
// NOTE: This is really tricky + subtle
// For leading, we don't want to ever change direction when the current target == the
// adjusted destination target. Namely, if we're going forward, both dest + curr
// mean the "next"most node so we can compare them directly against eath other.
// If we're moving backward, dest means "next"most, but curr means "prev"most.
// We first have to adjust the dest to mean "prev"most, and then do the comparison.
// If the adjusted dest == curr, then maintain direction. Otherwise, use the forward along path test.
bool bMovingForward = m_bMovingForward;
CPathTrack *pAdjustedDest = AdjustForMovementDirection( pDest );
if ( m_pCurrentPathTarget != pAdjustedDest )
{
bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pAdjustedDest );
}
if ( bMovingForward != m_bMovingForward )
{
// As a result of the tricky note above, this should never occur
Assert( pAdjustedDest != m_pCurrentPathTarget );
// Oops! Need to reverse direction
m_bMovingForward = bMovingForward;
m_vecSegmentStartPoint = m_pCurrentPathTarget->GetAbsOrigin();
m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget );
}
m_pDestPathTarget = pDest;
}
// NDebugOverlay::Cross3D( m_pCurrentPathTarget->GetAbsOrigin(), -Vector(36,36,36), Vector(36,36,36), 255, 0, 0, true, 0.1f );
// NDebugOverlay::Cross3D( m_pDestPathTarget->GetAbsOrigin(), -Vector(48,48,48), Vector(48,48,48), 0, 255, 0, true, 0.1f );
// NDebugOverlay::Cross3D( targetPos, -Vector(36,36,36), Vector(36,36,36), 0, 0, 255, true, 0.1f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_TrackPather::UpdateTargetPosition( void )
{
// Don't update our target if we're being told to go somewhere
if ( m_bForcedMove && !m_bPatrolBreakable )
return;
// Don't update our target if we're patrolling
if ( m_bPatrolling )
{
// If we have an enemy, and our patrol is breakable, stop patrolling
if ( !m_bPatrolBreakable || !GetEnemy() )
return;
m_bPatrolling = false;
}
Vector targetPos;
if ( !GetTrackPatherTarget( &targetPos ) )
return;
// Not time to update again
if ( m_flEnemyPathUpdateTime > gpGlobals->curtime )
return;
// See if the target has moved enough to make us recheck
float flDistSqr = ( targetPos - m_vecLastGoalCheckPosition ).LengthSqr();
if ( flDistSqr < m_flTargetDistanceThreshold * m_flTargetDistanceThreshold )
return;
// Find the best position to be on our path
CPathTrack *pDest = BestPointOnPath( m_pCurrentPathTarget, targetPos, m_flAvoidDistance, true, m_bChooseFarthestPoint );
if ( CPathTrack::ValidPath( pDest ) == NULL )
{
// This means that a valid path could not be found to our target!
// Assert(0);
return;
}
if ( pDest != m_pDestPathTarget )
{
// This is our new destination
bool bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pDest );
if ( bMovingForward != m_bMovingForward )
{
// Oops! Need to reverse direction
m_bMovingForward = bMovingForward;
if ( pDest != m_pCurrentPathTarget )
{
SetupNewCurrentTarget( NextAlongCurrentPath( m_pCurrentPathTarget ) );
}
}
m_pDestPathTarget = pDest;
}
// Keep this goal point for comparisons later
m_vecLastGoalCheckPosition = targetPos;
// Only do this on set intervals
m_flEnemyPathUpdateTime = gpGlobals->curtime + 1.0f;
}
//------------------------------------------------------------------------------
// Returns the direction of the path at the closest point to the target
//------------------------------------------------------------------------------
const Vector &CAI_TrackPather::TargetPathDirection() const
{
return m_vecTargetPathDir;
}
const Vector &CAI_TrackPather::TargetPathAcrossDirection() const
{
static Vector s_Result;
CrossProduct( m_vecTargetPathDir, Vector( 0, 0, 1 ), s_Result );
return s_Result;
}
//------------------------------------------------------------------------------
// Returns the speed of the target relative to the path
//------------------------------------------------------------------------------
float CAI_TrackPather::TargetSpeedAlongPath() const
{
if ( !GetEnemy() || !IsLeading() )
return 0.0f;
Vector vecSmoothedVelocity = GetEnemy()->GetSmoothedVelocity();
return DotProduct( vecSmoothedVelocity, TargetPathDirection() );
}
//------------------------------------------------------------------------------
// Returns the speed of the target *across* the path
//------------------------------------------------------------------------------
float CAI_TrackPather::TargetSpeedAcrossPath() const
{
if ( !GetEnemy() || !IsLeading() )
return 0.0f;
Vector vecSmoothedVelocity = GetEnemy()->GetSmoothedVelocity();
return DotProduct( vecSmoothedVelocity, TargetPathAcrossDirection() );
}
//------------------------------------------------------------------------------
// Returns the max distance we can be from the path
//------------------------------------------------------------------------------
float CAI_TrackPather::MaxDistanceFromCurrentPath() const
{
if ( !IsLeading() || !m_pCurrentPathTarget )
return 0.0f;
CPathTrack *pPrevPath = PreviousAlongCurrentPath( m_pCurrentPathTarget );
if ( !pPrevPath )
{
pPrevPath = m_pCurrentPathTarget;
}
// NOTE: Can't use m_vecSegmentStartPoint because we don't have a radius defined for it
float t;
Vector vecTemp;
CalcClosestPointOnLine( GetAbsOrigin(), pPrevPath->GetAbsOrigin(),
m_pCurrentPathTarget->GetAbsOrigin(), vecTemp, &t );
t = clamp( t, 0.0f, 1.0f );
float flRadius = (1.0f - t) * pPrevPath->GetRadius() + t * m_pCurrentPathTarget->GetRadius();
return flRadius;
}
//------------------------------------------------------------------------------
// Purpose : A different version of the track pather which is more explicit about
// the meaning of dest, current, and prev path points
//------------------------------------------------------------------------------
void CAI_TrackPather::UpdateTrackNavigation( void )
{
// No target? Use the string specified. We have no spawn method (sucky!!) so this is how that works
if ( ( CPathTrack::ValidPath( m_pDestPathTarget ) == NULL ) && ( m_target != NULL_STRING ) )
{
FlyToPathTrack( m_target );
m_target = NULL_STRING;
}
if ( !IsLeading() )
{
if ( !m_pCurrentPathTarget )
return;
// Updates our destination node if we're tracking something
UpdateTargetPosition();
// Move along our path towards our current destination
UpdateCurrentTarget();
}
else
{
// Updates our destination position if we're leading something
UpdateTargetPositionLeading();
// Move along our path towards our current destination
UpdateCurrentTargetLeading();
}
}
//------------------------------------------------------------------------------
// Sets the farthest path distance
//------------------------------------------------------------------------------
void CAI_TrackPather::SetFarthestPathDist( float flMaxPathDist )
{
m_flFarthestPathDist = flMaxPathDist;
}
//------------------------------------------------------------------------------
// Sets up a new current path target
//------------------------------------------------------------------------------
void CAI_TrackPather::SetupNewCurrentTarget( CPathTrack *pTrack )
{
Assert( pTrack );
m_vecSegmentStartPoint = GetAbsOrigin();
VectorMA( m_vecSegmentStartPoint, -2.0f, GetAbsVelocity(), m_vecSegmentStartSplinePoint );
m_pCurrentPathTarget = pTrack;
SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() );
}
//------------------------------------------------------------------------------
// Moves to an explicit track point
//------------------------------------------------------------------------------
void CAI_TrackPather::MoveToTrackPoint( CPathTrack *pTrack )
{
if ( IsOnSameTrack( pTrack, m_pDestPathTarget ) )
{
// The track must be valid
if ( CPathTrack::ValidPath( pTrack ) == NULL )
return;
m_pDestPathTarget = pTrack;
m_bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pTrack );
m_bForcedMove = true;
}
else
{
CPathTrack *pClosestTrack = BestPointOnPath( pTrack, WorldSpaceCenter(), 0.0f, false, false );
// The track must be valid
if ( CPathTrack::ValidPath( pClosestTrack ) == NULL )
return;
SetupNewCurrentTarget( pClosestTrack );
m_pDestPathTarget = pTrack;
m_bMovingForward = IsForwardAlongPath( pClosestTrack, pTrack );
m_bForcedMove = true;
}
}
//------------------------------------------------------------------------------
// Moves to the closest track point
//------------------------------------------------------------------------------
void CAI_TrackPather::MoveToClosestTrackPoint( CPathTrack *pTrack )
{
if ( IsOnSameTrack( pTrack, m_pDestPathTarget ) )
return;
CPathTrack *pClosestTrack = BestPointOnPath( pTrack, WorldSpaceCenter(), 0.0f, false, false );
// The track must be valid
if ( CPathTrack::ValidPath( pClosestTrack ) == NULL )
return;
SetupNewCurrentTarget( pClosestTrack );
m_pDestPathTarget = pClosestTrack;
m_bMovingForward = true;
// Force us to switch tracks if we're leading
if ( IsLeading() )
{
m_bForcedMove = true;
}
}
//-----------------------------------------------------------------------------
// Are the two path tracks connected?
//-----------------------------------------------------------------------------
bool CAI_TrackPather::IsOnSameTrack( CPathTrack *pPath1, CPathTrack *pPath2 ) const
{
if ( pPath1 == pPath2 )
return true;
{
BEGIN_PATH_TRACK_ITERATION();
CPathTrack *pTravPath = pPath1->GetPrevious();
while( CPathTrack::ValidPath( pTravPath ) && (pTravPath != pPath1) )
{
// Circular loop checking
if ( pTravPath->HasBeenVisited() )
break;
pTravPath->Visit();
if ( pTravPath == pPath2 )
return true;
pTravPath = pTravPath->GetPrevious();
}
}
{
BEGIN_PATH_TRACK_ITERATION();
CPathTrack *pTravPath = pPath1->GetNext();
while( CPathTrack::ValidPath( pTravPath ) && (pTravPath != pPath1) )
{
// Circular loop checking
if ( pTravPath->HasBeenVisited() )
break;
pTravPath->Visit();
if ( pTravPath == pPath2 )
return true;
pTravPath = pTravPath->GetNext();
}
}
return false;
}
//-----------------------------------------------------------------------------
// Deal with teleportation
//-----------------------------------------------------------------------------
void CAI_TrackPather::Teleported()
{
// This updates the paths so they are reasonable
CPathTrack *pClosestTrack = BestPointOnPath( GetDestPathTarget(), WorldSpaceCenter(), 0.0f, false, false );
m_pDestPathTarget = NULL;
MoveToClosestTrackPoint( pClosestTrack );
}
//-----------------------------------------------------------------------------
// Returns distance along path to target, returns FLT_MAX if there's no path
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputePathDistance( CPathTrack *pPath, CPathTrack *pDest, bool bForward ) const
{
float flDist = 0.0f;
CPathTrack *pLast = pPath;
BEGIN_PATH_TRACK_ITERATION();
while ( CPathTrack::ValidPath( pPath ) )
{
// Ciruclar loop checking
if ( pPath->HasBeenVisited() )
return FLT_MAX;
pPath->Visit();
flDist += pLast->GetAbsOrigin().DistTo( pPath->GetAbsOrigin() );
if ( pDest == pPath )
return flDist;
pLast = pPath;
pPath = bForward ? pPath->GetNext() : pPath->GetPrevious();
}
return FLT_MAX;
}
//-----------------------------------------------------------------------------
// Is pPathTest in "front" of pPath on the same path? (Namely, does GetNext() get us there?)
//-----------------------------------------------------------------------------
bool CAI_TrackPather::IsForwardAlongPath( CPathTrack *pPath, CPathTrack *pPathTest ) const
{
// Also, in the case of looping paths, we want to return the shortest path
float flForwardDist = ComputePathDistance( pPath, pPathTest, true );
float flReverseDist = ComputePathDistance( pPath, pPathTest, false );
Assert( ( flForwardDist != FLT_MAX ) || ( flReverseDist != FLT_MAX ) );
return ( flForwardDist <= flReverseDist );
}
//-----------------------------------------------------------------------------
// Computes distance + nearest point from the current path..
//-----------------------------------------------------------------------------
float CAI_TrackPather::ClosestPointToCurrentPath( Vector *pVecPoint ) const
{
if (!m_pCurrentPathTarget)
{
*pVecPoint = GetAbsOrigin();
return 0;
}
float t;
CalcClosestPointOnLine( GetAbsOrigin(), m_vecSegmentStartPoint,
m_pCurrentPathTarget->GetAbsOrigin(), *pVecPoint, &t );
return t;
}
//-----------------------------------------------------------------------------
// Computes a "path" velocity at a particular point along the current path
//-----------------------------------------------------------------------------
void CAI_TrackPather::ComputePathTangent( float t, Vector *pVecTangent ) const
{
CPathTrack *pNextTrack = NextAlongCurrentPath(m_pCurrentPathTarget);
if ( !pNextTrack )
{
pNextTrack = m_pCurrentPathTarget;
}
t = clamp( t, 0.0f, 1.0f );
pVecTangent->Init(0,0,0);
Catmull_Rom_Spline_Tangent( m_vecSegmentStartSplinePoint, m_vecSegmentStartPoint,
m_pCurrentPathTarget->GetAbsOrigin(), pNextTrack->GetAbsOrigin(), t, *pVecTangent );
VectorNormalize( *pVecTangent );
}
//-----------------------------------------------------------------------------
// Computes the *normalized* velocity at which the helicopter should approach the final point
//-----------------------------------------------------------------------------
void CAI_TrackPather::ComputeNormalizedDestVelocity( Vector *pVecVelocity ) const
{
if ( m_nPauseState != PAUSE_NO_PAUSE )
{
pVecVelocity->Init(0,0,0);
return;
}
CPathTrack *pNextTrack = NextAlongCurrentPath(m_pCurrentPathTarget);
if ( !pNextTrack )
{
pNextTrack = m_pCurrentPathTarget;
}
if ( ( pNextTrack == m_pCurrentPathTarget ) || ( m_pCurrentPathTarget == m_pDestPathTarget ) )
{
pVecVelocity->Init(0,0,0);
return;
}
VectorSubtract( pNextTrack->GetAbsOrigin(), m_pCurrentPathTarget->GetAbsOrigin(), *pVecVelocity );
VectorNormalize( *pVecVelocity );
// Slow it down if we're approaching a sharp corner
Vector vecDelta;
VectorSubtract( m_pCurrentPathTarget->GetAbsOrigin(), m_vecSegmentStartPoint, vecDelta );
VectorNormalize( vecDelta );
float flDot = DotProduct( *pVecVelocity, vecDelta );
*pVecVelocity *= clamp( flDot, 0.0f, 1.0f );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::SetTrack( CBaseEntity *pGoalEnt )
{
// Ignore this input if we're *already* on that path.
CPathTrack *pTrack = dynamic_cast<CPathTrack *>(pGoalEnt);
if ( !pTrack )
{
DevWarning( "%s: Specified entity '%s' must be a path_track!\n", pGoalEnt->GetClassname(), pGoalEnt->GetEntityName().ToCStr() );
return;
}
MoveToClosestTrackPoint( pTrack );
}
void CAI_TrackPather::SetTrack( string_t strTrackName )
{
// Find our specified target
CBaseEntity *pGoalEnt = gEntList.FindEntityByName( NULL, strTrackName );
if ( pGoalEnt == NULL )
{
DevWarning( "%s: Could not find path_track '%s'!\n", GetClassname(), STRING( strTrackName ) );
return;
}
SetTrack( pGoalEnt );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputSetTrack( inputdata_t &inputdata )
{
string_t strTrackName = MAKE_STRING( inputdata.value.String() );
SetTrack( MAKE_STRING( inputdata.value.String() ) );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : strTrackName -
//-----------------------------------------------------------------------------
void CAI_TrackPather::FlyToPathTrack( string_t strTrackName )
{
CBaseEntity *pGoalEnt = gEntList.FindEntityByName( NULL, strTrackName );
if ( pGoalEnt == NULL )
{
DevWarning( "%s: Could not find path_track '%s'!\n", GetClassname(), STRING( strTrackName ) );
return;
}
// Ignore this input if we're *already* on that path.
CPathTrack *pTrack = dynamic_cast<CPathTrack *>(pGoalEnt);
if ( !pTrack )
{
DevWarning( "%s: Specified entity '%s' must be a path_track!\n", GetClassname(), STRING( strTrackName ) );
return;
}
// Find our specified target
MoveToTrackPoint( pTrack );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputFlyToPathTrack( inputdata_t &inputdata )
{
// Find our specified target
string_t strTrackName = MAKE_STRING( inputdata.value.String() );
m_nPauseState = PAUSE_NO_PAUSE;
FlyToPathTrack( strTrackName );
}
//-----------------------------------------------------------------------------
// Changes the mode used to determine which path point to move to
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputChooseFarthestPathPoint( inputdata_t &inputdata )
{
UseFarthestPathPoint( true );
}
void CAI_TrackPather::InputChooseNearestPathPoint( inputdata_t &inputdata )
{
UseFarthestPathPoint( false );
}
void CAI_TrackPather::UseFarthestPathPoint( bool useFarthest )
{
m_bChooseFarthestPoint = useFarthest;
}