initial
This commit is contained in:
166
game/server/NextBot/Path/NextBotChasePath.cpp
Normal file
166
game/server/NextBot/Path/NextBotChasePath.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
//========== Copyright <20> 2008, Valve Corporation, All rights reserved. ========
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "NextBotChasePath.h"
|
||||
#include "tier1/fmtstr.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Try to cutoff our chase subject
|
||||
*/
|
||||
Vector ChasePath::PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const
|
||||
{
|
||||
ILocomotion *mover = bot->GetLocomotionInterface();
|
||||
|
||||
const Vector &subjectPos = subject->GetAbsOrigin();
|
||||
|
||||
Vector to = subjectPos - bot->GetPosition();
|
||||
to.z = 0.0f;
|
||||
float flRangeSq = to.LengthSqr();
|
||||
|
||||
// don't lead if subject is very far away
|
||||
float flLeadRadiusSq = GetLeadRadius();
|
||||
flLeadRadiusSq *= flLeadRadiusSq;
|
||||
if ( flRangeSq > flLeadRadiusSq )
|
||||
return subjectPos;
|
||||
|
||||
// Normalize in place
|
||||
float range = sqrt( flRangeSq );
|
||||
to /= ( range + 0.0001f ); // avoid divide by zero
|
||||
|
||||
// estimate time to reach subject, assuming maximum speed
|
||||
float leadTime = 0.5f + ( range / ( mover->GetRunSpeed() + 0.0001f ) );
|
||||
|
||||
// estimate amount to lead the subject
|
||||
Vector lead = leadTime * subject->GetAbsVelocity();
|
||||
lead.z = 0.0f;
|
||||
|
||||
if ( DotProduct( to, lead ) < 0.0f )
|
||||
{
|
||||
// the subject is moving towards us - only pay attention
|
||||
// to his perpendicular velocity for leading
|
||||
Vector2D to2D = to.AsVector2D();
|
||||
to2D.NormalizeInPlace();
|
||||
|
||||
Vector2D perp( -to2D.y, to2D.x );
|
||||
|
||||
float enemyGroundSpeed = lead.x * perp.x + lead.y * perp.y;
|
||||
|
||||
lead.x = enemyGroundSpeed * perp.x;
|
||||
lead.y = enemyGroundSpeed * perp.y;
|
||||
}
|
||||
|
||||
// compute our desired destination
|
||||
Vector pathTarget = subjectPos + lead;
|
||||
|
||||
// validate this destination
|
||||
|
||||
// don't lead through walls
|
||||
if ( lead.LengthSqr() > 36.0f )
|
||||
{
|
||||
float fraction;
|
||||
if ( !mover->IsPotentiallyTraversable( subjectPos, pathTarget, ILocomotion::IMMEDIATELY, &fraction ) )
|
||||
{
|
||||
// tried to lead through an unwalkable area - clip to walkable space
|
||||
pathTarget = subjectPos + fraction * ( pathTarget - subjectPos );
|
||||
}
|
||||
}
|
||||
|
||||
// don't lead over cliffs
|
||||
CNavArea *leadArea = NULL;
|
||||
|
||||
#ifdef NEED_GPGLOBALS_SERVERCOUNT_TO_DO_THIS
|
||||
CBaseCombatCharacter *pBCC = subject->MyCombatCharacterPointer();
|
||||
if ( pBCC && CloseEnough( pathTarget, subjectPos, 3.0 ) )
|
||||
{
|
||||
pathTarget = subjectPos;
|
||||
leadArea = pBCC->GetLastKnownArea(); // can return null?
|
||||
}
|
||||
else
|
||||
{
|
||||
struct CacheEntry_t
|
||||
{
|
||||
CacheEntry_t() : pArea(NULL) {}
|
||||
Vector target;
|
||||
CNavArea *pArea;
|
||||
};
|
||||
|
||||
static int iServer;
|
||||
static CacheEntry_t cache[4];
|
||||
static int iNext;
|
||||
int i;
|
||||
|
||||
bool bFound = false;
|
||||
if ( iServer != gpGlobals->serverCount )
|
||||
{
|
||||
for ( i = 0; i < ARRAYSIZE(cache); i++ )
|
||||
{
|
||||
cache[i].pArea = NULL;
|
||||
}
|
||||
iServer = gpGlobals->serverCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( i = 0; i < ARRAYSIZE(cache); i++ )
|
||||
{
|
||||
if ( cache[i].pArea && CloseEnough( cache[i].target, pathTarget, 2.0 ) )
|
||||
{
|
||||
pathTarget = cache[i].target;
|
||||
leadArea = cache[i].pArea;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !bFound )
|
||||
{
|
||||
leadArea = TheNavMesh->GetNearestNavArea( pathTarget );
|
||||
if ( leadArea )
|
||||
{
|
||||
cache[iNext].target = pathTarget;
|
||||
cache[iNext].pArea = leadArea;
|
||||
iNext = ( iNext + 1 ) % ARRAYSIZE( cache );
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
leadArea = TheNavMesh->GetNearestNavArea( pathTarget );
|
||||
#endif
|
||||
|
||||
|
||||
if ( !leadArea || leadArea->GetZ( pathTarget.x, pathTarget.y ) < pathTarget.z - mover->GetMaxJumpHeight() )
|
||||
{
|
||||
// would fall off a cliff
|
||||
return subjectPos;
|
||||
}
|
||||
|
||||
/** This needs more thought - it is preventing bots from using dropdowns
|
||||
if ( mover->HasPotentialGap( subjectPos, pathTarget, &fraction ) )
|
||||
{
|
||||
// tried to lead over a cliff - clip to safe region
|
||||
pathTarget = subjectPos + fraction * ( pathTarget - subjectPos );
|
||||
}
|
||||
*/
|
||||
|
||||
return pathTarget;
|
||||
}
|
||||
|
||||
// if the victim is a player, poke them so they know they're being chased
|
||||
void DirectChasePath::NotifyVictim( INextBot *me, CBaseEntity *victim )
|
||||
{
|
||||
CBaseCombatCharacter *pBCCVictim = ToBaseCombatCharacter( victim );
|
||||
if ( !pBCCVictim )
|
||||
return;
|
||||
|
||||
pBCCVictim->OnPursuedBy( me );
|
||||
}
|
||||
376
game/server/NextBot/Path/NextBotChasePath.h
Normal file
376
game/server/NextBot/Path/NextBotChasePath.h
Normal file
@@ -0,0 +1,376 @@
|
||||
// NextBotChasePath.h
|
||||
// Maintain and follow a "chase path" to a selected Actor
|
||||
// Author: Michael Booth, September 2006
|
||||
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
|
||||
|
||||
#ifndef _NEXT_BOT_CHASE_PATH_
|
||||
#define _NEXT_BOT_CHASE_PATH_
|
||||
|
||||
#include "nav.h"
|
||||
#include "NextBotInterface.h"
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "NextBotChasePath.h"
|
||||
#include "NextBotUtil.h"
|
||||
#include "NextBotPathFollow.h"
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A ChasePath extends a PathFollower to periodically recompute a path to a chase
|
||||
* subject, and to move along the path towards that subject.
|
||||
*/
|
||||
class ChasePath : public PathFollower
|
||||
{
|
||||
public:
|
||||
enum SubjectChaseType
|
||||
{
|
||||
LEAD_SUBJECT,
|
||||
DONT_LEAD_SUBJECT
|
||||
};
|
||||
ChasePath( SubjectChaseType chaseHow = DONT_LEAD_SUBJECT );
|
||||
|
||||
virtual ~ChasePath() { }
|
||||
|
||||
virtual void Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos = NULL ); // update path to chase target and move bot along path
|
||||
|
||||
virtual float GetLeadRadius( void ) const; // range where movement leading begins - beyond this just head right for the subject
|
||||
virtual float GetMaxPathLength( void ) const; // return maximum path length
|
||||
virtual Vector PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const; // try to cutoff our chase subject, knowing our relative positions and velocities
|
||||
virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const; // return true if situation has changed enough to warrant recomputing the current path
|
||||
|
||||
virtual float GetLifetime( void ) const; // Return duration this path is valid. Path will become invalid at its earliest opportunity once this duration elapses. Zero = infinite lifetime
|
||||
|
||||
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
|
||||
|
||||
private:
|
||||
void RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos );
|
||||
|
||||
CountdownTimer m_failTimer; // throttle re-pathing if last path attempt failed
|
||||
CountdownTimer m_throttleTimer; // require a minimum time between re-paths
|
||||
CountdownTimer m_lifetimeTimer;
|
||||
EHANDLE m_lastPathSubject; // the subject used to compute the current/last path
|
||||
SubjectChaseType m_chaseHow;
|
||||
};
|
||||
|
||||
inline ChasePath::ChasePath( SubjectChaseType chaseHow )
|
||||
{
|
||||
m_failTimer.Invalidate();
|
||||
m_throttleTimer.Invalidate();
|
||||
m_lifetimeTimer.Invalidate();
|
||||
m_lastPathSubject = NULL;
|
||||
m_chaseHow = chaseHow;
|
||||
}
|
||||
|
||||
inline float ChasePath::GetLeadRadius( void ) const
|
||||
{
|
||||
return 500.0f; // 1000.0f;
|
||||
}
|
||||
|
||||
inline float ChasePath::GetMaxPathLength( void ) const
|
||||
{
|
||||
// no limit
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float ChasePath::GetLifetime( void ) const
|
||||
{
|
||||
// infinite duration
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline void ChasePath::Invalidate( void )
|
||||
{
|
||||
// path is gone, repath at earliest opportunity
|
||||
m_throttleTimer.Invalidate();
|
||||
m_lifetimeTimer.Invalidate();
|
||||
|
||||
// extend
|
||||
PathFollower::Invalidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Maintain a path to our chase subject and move along that path
|
||||
*/
|
||||
inline void ChasePath::Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos )
|
||||
{
|
||||
VPROF_BUDGET( "ChasePath::Update", "NextBot" );
|
||||
|
||||
// maintain the path to the subject
|
||||
RefreshPath( bot, subject, cost, pPredictedSubjectPos );
|
||||
|
||||
// move along the path towards the subject
|
||||
PathFollower::Update( bot );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if situation has changed enough to warrant recomputing the current path
|
||||
*/
|
||||
inline bool ChasePath::IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const
|
||||
{
|
||||
// the closer we get, the more accurate our path needs to be
|
||||
Vector to = subject->GetAbsOrigin() - bot->GetPosition();
|
||||
|
||||
const float minTolerance = 0.0f; // 25.0f;
|
||||
const float toleranceRate = 0.33f; // 1.0f; // 0.15f;
|
||||
|
||||
float tolerance = minTolerance + toleranceRate * to.Length();
|
||||
|
||||
return ( subject->GetAbsOrigin() - GetEndPosition() ).IsLengthGreaterThan( tolerance );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Periodically rebuild the path to our victim
|
||||
*/
|
||||
inline void ChasePath::RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos )
|
||||
{
|
||||
VPROF_BUDGET( "ChasePath::RefreshPath", "NextBot" );
|
||||
|
||||
ILocomotion *mover = bot->GetLocomotionInterface();
|
||||
|
||||
// don't change our path if we're on a ladder
|
||||
if ( IsValid() && mover->IsUsingLadder() )
|
||||
{
|
||||
if ( bot->IsDebugging( NEXTBOT_PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
|
||||
// don't allow repath until a moment AFTER we have left the ladder
|
||||
m_throttleTimer.Start( 1.0f );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( subject == NULL )
|
||||
{
|
||||
if ( bot->IsDebugging( NEXTBOT_PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No subject.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !m_failTimer.IsElapsed() )
|
||||
{
|
||||
// if ( bot->IsDebugging( NEXTBOT_PATH ) )
|
||||
// {
|
||||
// DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Fail timer not elapsed.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
// }
|
||||
return;
|
||||
}
|
||||
|
||||
// if our path subject changed, repath immediately
|
||||
if ( subject != m_lastPathSubject )
|
||||
{
|
||||
if ( bot->IsDebugging( NEXTBOT_PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) Chase path subject changed (from %p to %p).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_lastPathSubject.Get(), subject );
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
|
||||
// new subject, fresh attempt
|
||||
m_failTimer.Invalidate();
|
||||
}
|
||||
|
||||
if ( IsValid() && !m_throttleTimer.IsElapsed() )
|
||||
{
|
||||
// require a minimum time between repaths, as long as we have a path to follow
|
||||
// if ( bot->IsDebugging( NEXTBOT_PATH ) )
|
||||
// {
|
||||
// DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
// }
|
||||
return;
|
||||
}
|
||||
|
||||
if ( IsValid() && m_lifetimeTimer.HasStarted() && m_lifetimeTimer.IsElapsed() )
|
||||
{
|
||||
// this path's lifetime has elapsed
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
if ( !IsValid() || IsRepathNeeded( bot, subject ) )
|
||||
{
|
||||
// the situation has changed - try a new path
|
||||
bool isPath;
|
||||
Vector pathTarget = subject->GetAbsOrigin();
|
||||
|
||||
if ( m_chaseHow == LEAD_SUBJECT )
|
||||
{
|
||||
pathTarget = pPredictedSubjectPos ? *pPredictedSubjectPos : PredictSubjectPosition( bot, subject );
|
||||
isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() );
|
||||
}
|
||||
else if ( subject->MyCombatCharacterPointer() && subject->MyCombatCharacterPointer()->GetLastKnownArea() )
|
||||
{
|
||||
isPath = Compute( bot, subject->MyCombatCharacterPointer(), cost, GetMaxPathLength() );
|
||||
}
|
||||
else
|
||||
{
|
||||
isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() );
|
||||
}
|
||||
|
||||
if ( isPath )
|
||||
{
|
||||
if ( bot->IsDebugging( NEXTBOT_PATH ) )
|
||||
{
|
||||
const float size = 20.0f;
|
||||
NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, RandomInt( 0, 200 ), 255, 255, true, 30.0f );
|
||||
|
||||
DevMsg( "%3.2f: bot(#%d) REPATH\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
|
||||
m_lastPathSubject = subject;
|
||||
|
||||
const float minRepathInterval = 0.5f;
|
||||
m_throttleTimer.Start( minRepathInterval );
|
||||
|
||||
// track the lifetime of this new path
|
||||
float lifetime = GetLifetime();
|
||||
if ( lifetime > 0.0f )
|
||||
{
|
||||
m_lifetimeTimer.Start( lifetime );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lifetimeTimer.Invalidate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// can't reach subject - throttle retry based on range to subject
|
||||
m_failTimer.Start( 0.005f * ( bot->GetRangeTo( subject ) ) );
|
||||
|
||||
// allow bot to react to path failure
|
||||
bot->OnMoveToFailure( this, FAIL_NO_PATH_EXISTS );
|
||||
|
||||
if ( bot->IsDebugging( NEXTBOT_PATH ) )
|
||||
{
|
||||
const float size = 20.0f;
|
||||
const float dT = 90.0f;
|
||||
int c = RandomInt( 0, 100 );
|
||||
NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, c, c, 255, true, dT );
|
||||
NDebugOverlay::HorzArrow( bot->GetPosition(), pathTarget, 5.0f, 255, c, c, 255, true, dT );
|
||||
|
||||
DevMsg( "%3.2f: bot(#%d) REPATH FAILED\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Directly beeline toward victim if we have a clear shot, otherwise pathfind.
|
||||
*/
|
||||
class DirectChasePath : public ChasePath
|
||||
{
|
||||
public:
|
||||
|
||||
DirectChasePath( ChasePath::SubjectChaseType chaseHow = ChasePath::DONT_LEAD_SUBJECT ) : ChasePath( chaseHow )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------
|
||||
virtual void Update( INextBot *me, CBaseEntity *victim, const IPathCost &pathCost, Vector *pPredictedSubjectPos = NULL ) // update path to chase target and move bot along path
|
||||
{
|
||||
Assert( !pPredictedSubjectPos );
|
||||
bool bComputedPredictedPosition;
|
||||
Vector vecPredictedPosition;
|
||||
if ( !DirectChase( &bComputedPredictedPosition, &vecPredictedPosition, me, victim ) )
|
||||
{
|
||||
// path around obstacles to reach our victim
|
||||
ChasePath::Update( me, victim, pathCost, bComputedPredictedPosition ? &vecPredictedPosition : NULL );
|
||||
}
|
||||
NotifyVictim( me, victim );
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------
|
||||
bool DirectChase( bool *pPredictedPositionComputed, Vector *pPredictedPos, INextBot *me, CBaseEntity *victim ) // if there is nothing between us and our victim, run directly at them
|
||||
{
|
||||
*pPredictedPositionComputed = false;
|
||||
|
||||
ILocomotion *mover = me->GetLocomotionInterface();
|
||||
|
||||
if ( me->IsImmobile() || mover->IsScrambling() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( IsDiscontinuityAhead( me, CLIMB_UP ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( IsDiscontinuityAhead( me, JUMP_OVER_GAP ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector leadVictimPos = PredictSubjectPosition( me, victim );
|
||||
|
||||
// Don't want to have to compute the predicted position twice.
|
||||
*pPredictedPositionComputed = true;
|
||||
*pPredictedPos = leadVictimPos;
|
||||
|
||||
if ( !mover->IsPotentiallyTraversable( mover->GetFeet(), leadVictimPos ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the way is clear - move directly towards our victim
|
||||
mover->FaceTowards( leadVictimPos );
|
||||
mover->Approach( leadVictimPos );
|
||||
|
||||
me->GetBodyInterface()->AimHeadTowards( victim );
|
||||
|
||||
// old path is no longer useful since we've moved off of it
|
||||
Invalidate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------
|
||||
virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const // return true if situation has changed enough to warrant recomputing the current path
|
||||
{
|
||||
if ( ChasePath::IsRepathNeeded( bot, subject ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return bot->GetLocomotionInterface()->IsStuck() && bot->GetLocomotionInterface()->GetStuckDuration() > 2.0f;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Determine exactly where the path goes between the given two areas
|
||||
* on the path. Return this point in 'crossPos'.
|
||||
*/
|
||||
virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const
|
||||
{
|
||||
Vector center;
|
||||
float halfWidth;
|
||||
from->ComputePortal( to, dir, ¢er, &halfWidth );
|
||||
|
||||
*crossPos = center;
|
||||
}
|
||||
|
||||
void NotifyVictim( INextBot *me, CBaseEntity *victim );
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_CHASE_PATH_
|
||||
1079
game/server/NextBot/Path/NextBotPath.cpp
Normal file
1079
game/server/NextBot/Path/NextBotPath.cpp
Normal file
File diff suppressed because it is too large
Load Diff
870
game/server/NextBot/Path/NextBotPath.h
Normal file
870
game/server/NextBot/Path/NextBotPath.h
Normal file
@@ -0,0 +1,870 @@
|
||||
// NextBotPath.h
|
||||
// Encapsulate and manipulate a path through the world
|
||||
// Author: Michael Booth, February 2006
|
||||
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
|
||||
|
||||
#ifndef _NEXT_BOT_PATH_H_
|
||||
#define _NEXT_BOT_PATH_H_
|
||||
|
||||
#include "NextBotInterface.h"
|
||||
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
#define PATH_NO_LENGTH_LIMIT 0.0f // non-default argument value for Path::Compute()
|
||||
#define PATH_TRUNCATE_INCOMPLETE_PATH false // non-default argument value for Path::Compute()
|
||||
|
||||
class INextBot;
|
||||
class CNavArea;
|
||||
class CNavLadder;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for pathfinding costs.
|
||||
* TODO: Replace all template cost functors with this interface, so we can virtualize and derive from them.
|
||||
*/
|
||||
class IPathCost
|
||||
{
|
||||
public:
|
||||
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const = 0;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for selecting a goal area during "open goal" pathfinding
|
||||
*/
|
||||
class IPathOpenGoalSelector
|
||||
{
|
||||
public:
|
||||
// compare "newArea" to "currentGoal" and return the area that is the better goal area
|
||||
virtual CNavArea *operator() ( CNavArea *currentGoal, CNavArea *newArea ) const = 0;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A Path through the world.
|
||||
* Not only does this encapsulate a path to get from point A to point B,
|
||||
* but also the selecting the decision algorithm for how to build that path.
|
||||
*/
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
Path( void );
|
||||
virtual ~Path() { }
|
||||
|
||||
enum SegmentType
|
||||
{
|
||||
ON_GROUND,
|
||||
DROP_DOWN,
|
||||
CLIMB_UP,
|
||||
JUMP_OVER_GAP,
|
||||
LADDER_UP,
|
||||
LADDER_DOWN,
|
||||
|
||||
NUM_SEGMENT_TYPES
|
||||
};
|
||||
|
||||
// @todo Allow custom Segment classes for different kinds of paths
|
||||
struct Segment
|
||||
{
|
||||
CNavArea *area; // the area along the path
|
||||
NavTraverseType how; // how to enter this area from the previous one
|
||||
Vector pos; // our movement goal position at this point in the path
|
||||
const CNavLadder *ladder; // if "how" refers to a ladder, this is it
|
||||
|
||||
SegmentType type; // how to traverse this segment of the path
|
||||
Vector forward; // unit vector along segment
|
||||
float length; // length of this segment
|
||||
float distanceFromStart; // distance of this node from the start of the path
|
||||
float curvature; // how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
|
||||
|
||||
Vector m_portalCenter; // position of center of 'portal' between previous area and this area
|
||||
float m_portalHalfWidth; // half width of 'portal'
|
||||
};
|
||||
|
||||
virtual float GetLength( void ) const; // return length of path from start to finish
|
||||
virtual const Vector &GetPosition( float distanceFromStart, const Segment *start = NULL ) const; // return a position on the path at the given distance from the path start
|
||||
virtual const Vector &GetClosestPosition( const Vector &pos, const Segment *start = NULL, float alongLimit = 0.0f ) const; // return the closest point on the path to the given position
|
||||
|
||||
virtual const Vector &GetStartPosition( void ) const; // return the position where this path starts
|
||||
virtual const Vector &GetEndPosition( void ) const; // return the position where this path ends
|
||||
virtual CBaseCombatCharacter *GetSubject( void ) const; // return the actor this path leads to, or NULL if there is no subject
|
||||
|
||||
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
|
||||
|
||||
virtual float GetAge( void ) const; // return "age" of this path (time since it was built)
|
||||
|
||||
enum SeekType
|
||||
{
|
||||
SEEK_ENTIRE_PATH, // search the entire path length
|
||||
SEEK_AHEAD, // search from current cursor position forward toward end of path
|
||||
SEEK_BEHIND // search from current cursor position backward toward path start
|
||||
};
|
||||
virtual void MoveCursorToClosestPosition( const Vector &pos, SeekType type = SEEK_ENTIRE_PATH, float alongLimit = 0.0f ) const; // Set cursor position to closest point on path to given position
|
||||
|
||||
enum MoveCursorType
|
||||
{
|
||||
PATH_ABSOLUTE_DISTANCE,
|
||||
PATH_RELATIVE_DISTANCE
|
||||
};
|
||||
virtual void MoveCursorToStart( void ); // set seek cursor to start of path
|
||||
virtual void MoveCursorToEnd( void ); // set seek cursor to end of path
|
||||
virtual void MoveCursor( float value, MoveCursorType type = PATH_ABSOLUTE_DISTANCE ); // change seek cursor position
|
||||
virtual float GetCursorPosition( void ) const; // return position of seek cursor (distance along path)
|
||||
|
||||
struct Data
|
||||
{
|
||||
Vector pos; // the position along the path
|
||||
Vector forward; // unit vector along path direction
|
||||
float curvature; // how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
|
||||
const Segment *segmentPrior; // the segment just before this position
|
||||
};
|
||||
virtual const Data &GetCursorData( void ) const; // return path state at the current cursor position
|
||||
|
||||
virtual bool IsValid( void ) const;
|
||||
virtual void Invalidate( void ); // make path invalid (clear it)
|
||||
|
||||
virtual void Draw( const Path::Segment *start = NULL ) const; // draw the path for debugging
|
||||
virtual void DrawInterpolated( float from, float to ); // draw the path for debugging - MODIFIES cursor position
|
||||
|
||||
virtual const Segment *FirstSegment( void ) const; // return first segment of path
|
||||
virtual const Segment *NextSegment( const Segment *currentSegment ) const; // return next segment of path, given current one
|
||||
virtual const Segment *PriorSegment( const Segment *currentSegment ) const; // return previous segment of path, given current one
|
||||
virtual const Segment *LastSegment( void ) const; // return last segment of path
|
||||
|
||||
enum ResultType
|
||||
{
|
||||
COMPLETE_PATH,
|
||||
PARTIAL_PATH,
|
||||
NO_PATH
|
||||
};
|
||||
virtual void OnPathChanged( INextBot *bot, ResultType result ) { } // invoked when the path is (re)computed (path is valid at the time of this call)
|
||||
|
||||
virtual void Copy( INextBot *bot, const Path &path ); // Replace this path with the given path's data
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Compute shortest path from bot to given actor via A* algorithm.
|
||||
* If returns true, path was found to the subject.
|
||||
* If returns false, path may either be invalid (use IsValid() to check), or valid but
|
||||
* doesn't reach all the way to the subject.
|
||||
*/
|
||||
template< typename CostFunctor >
|
||||
bool Compute( INextBot *bot, CBaseCombatCharacter *subject, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
|
||||
{
|
||||
VPROF_BUDGET( "Path::Compute(subject)", "NextBot" );
|
||||
|
||||
Invalidate();
|
||||
|
||||
m_subject = subject;
|
||||
|
||||
const Vector &start = bot->GetPosition();
|
||||
|
||||
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
|
||||
if ( !startArea )
|
||||
{
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
CNavArea *subjectArea = subject->GetLastKnownArea();
|
||||
if ( !subjectArea )
|
||||
{
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector subjectPos = subject->GetAbsOrigin();
|
||||
|
||||
// if we are already in the subject area, build trivial path
|
||||
if ( startArea == subjectArea )
|
||||
{
|
||||
BuildTrivialPath( bot, subjectPos );
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Compute shortest path to subject
|
||||
//
|
||||
CNavArea *closestArea = NULL;
|
||||
bool pathResult = NavAreaBuildPath( startArea, subjectArea, &subjectPos, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );
|
||||
|
||||
//
|
||||
// Build actual path by following parent links back from subject area
|
||||
//
|
||||
|
||||
// get count
|
||||
int count = 0;
|
||||
CNavArea *area;
|
||||
for( area = closestArea; area; area = area->GetParent() )
|
||||
{
|
||||
++count;
|
||||
|
||||
if ( area == startArea )
|
||||
{
|
||||
// startArea can be re-evaluated during the pathfind and given a parent...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// save room for endpoint
|
||||
if ( count > MAX_PATH_SEGMENTS-1 )
|
||||
{
|
||||
count = MAX_PATH_SEGMENTS-1;
|
||||
}
|
||||
else if ( count == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( count == 1 )
|
||||
{
|
||||
BuildTrivialPath( bot, subjectPos );
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
// assemble path
|
||||
m_segmentCount = count;
|
||||
for( area = closestArea; count && area; area = area->GetParent() )
|
||||
{
|
||||
--count;
|
||||
m_path[ count ].area = area;
|
||||
m_path[ count ].how = area->GetParentHow();
|
||||
m_path[ count ].type = ON_GROUND;
|
||||
}
|
||||
|
||||
if ( pathResult || includeGoalIfPathFails )
|
||||
{
|
||||
// append actual subject position
|
||||
m_path[ m_segmentCount ].area = closestArea;
|
||||
m_path[ m_segmentCount ].pos = subjectPos;
|
||||
m_path[ m_segmentCount ].ladder = NULL;
|
||||
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
|
||||
m_path[ m_segmentCount ].type = ON_GROUND;
|
||||
++m_segmentCount;
|
||||
}
|
||||
|
||||
// compute path positions
|
||||
if ( ComputePathDetails( bot, start ) == false )
|
||||
{
|
||||
Invalidate();
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove redundant nodes and clean up path
|
||||
Optimize( bot );
|
||||
|
||||
PostProcess();
|
||||
|
||||
OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );
|
||||
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Compute shortest path from bot to 'goal' via A* algorithm.
|
||||
* If returns true, path was found to the goal position.
|
||||
* If returns false, path may either be invalid (use IsValid() to check), or valid but
|
||||
* doesn't reach all the way to the goal.
|
||||
*/
|
||||
template< typename CostFunctor >
|
||||
bool Compute( INextBot *bot, const Vector &goal, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
|
||||
{
|
||||
VPROF_BUDGET( "Path::Compute(goal)", "NextBotSpiky" );
|
||||
|
||||
Invalidate();
|
||||
|
||||
const Vector &start = bot->GetPosition();
|
||||
|
||||
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
|
||||
if ( !startArea )
|
||||
{
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
// check line-of-sight to the goal position when finding it's nav area
|
||||
const float maxDistanceToArea = 200.0f;
|
||||
CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal, true, maxDistanceToArea, true );
|
||||
|
||||
// if we are already in the goal area, build trivial path
|
||||
if ( startArea == goalArea )
|
||||
{
|
||||
BuildTrivialPath( bot, goal );
|
||||
return true;
|
||||
}
|
||||
|
||||
// make sure path end position is on the ground
|
||||
Vector pathEndPosition = goal;
|
||||
if ( goalArea )
|
||||
{
|
||||
pathEndPosition.z = goalArea->GetZ( pathEndPosition );
|
||||
}
|
||||
else
|
||||
{
|
||||
TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
|
||||
}
|
||||
|
||||
//
|
||||
// Compute shortest path to goal
|
||||
//
|
||||
CNavArea *closestArea = NULL;
|
||||
bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );
|
||||
|
||||
//
|
||||
// Build actual path by following parent links back from goal area
|
||||
//
|
||||
|
||||
// get count
|
||||
int count = 0;
|
||||
CNavArea *area;
|
||||
for( area = closestArea; area; area = area->GetParent() )
|
||||
{
|
||||
++count;
|
||||
|
||||
if ( area == startArea )
|
||||
{
|
||||
// startArea can be re-evaluated during the pathfind and given a parent...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// save room for endpoint
|
||||
if ( count > MAX_PATH_SEGMENTS-1 )
|
||||
{
|
||||
count = MAX_PATH_SEGMENTS-1;
|
||||
}
|
||||
else if ( count == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( count == 1 )
|
||||
{
|
||||
BuildTrivialPath( bot, goal );
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
// assemble path
|
||||
m_segmentCount = count;
|
||||
for( area = closestArea; count && area; area = area->GetParent() )
|
||||
{
|
||||
--count;
|
||||
m_path[ count ].area = area;
|
||||
m_path[ count ].how = area->GetParentHow();
|
||||
m_path[ count ].type = ON_GROUND;
|
||||
}
|
||||
|
||||
if ( pathResult || includeGoalIfPathFails )
|
||||
{
|
||||
// append actual goal position
|
||||
m_path[ m_segmentCount ].area = closestArea;
|
||||
m_path[ m_segmentCount ].pos = pathEndPosition;
|
||||
m_path[ m_segmentCount ].ladder = NULL;
|
||||
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
|
||||
m_path[ m_segmentCount ].type = ON_GROUND;
|
||||
++m_segmentCount;
|
||||
}
|
||||
|
||||
// compute path positions
|
||||
if ( ComputePathDetails( bot, start ) == false )
|
||||
{
|
||||
Invalidate();
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove redundant nodes and clean up path
|
||||
Optimize( bot );
|
||||
|
||||
PostProcess();
|
||||
|
||||
OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );
|
||||
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Build a path from bot's current location to an undetermined goal area
|
||||
* that minimizes the given cost along the final path and meets the
|
||||
* goal criteria.
|
||||
*/
|
||||
virtual bool ComputeWithOpenGoal( INextBot *bot, const IPathCost &costFunc, const IPathOpenGoalSelector &goalSelector, float maxSearchRadius = 0.0f )
|
||||
{
|
||||
VPROF_BUDGET( "ComputeWithOpenGoal", "NextBot" );
|
||||
|
||||
int teamID = bot->GetEntity()->GetTeamNumber();
|
||||
|
||||
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
|
||||
|
||||
if ( startArea == NULL )
|
||||
return NULL;
|
||||
|
||||
startArea->SetParent( NULL );
|
||||
|
||||
// start search
|
||||
CNavArea::ClearSearchLists();
|
||||
|
||||
float initCost = costFunc( startArea, NULL, NULL, NULL, -1.0f );
|
||||
if ( initCost < 0.0f )
|
||||
return NULL;
|
||||
|
||||
startArea->SetTotalCost( initCost );
|
||||
startArea->AddToOpenList();
|
||||
|
||||
// find our goal as we search
|
||||
CNavArea *goalArea = NULL;
|
||||
|
||||
//
|
||||
// Dijkstra's algorithm (since we don't know our goal).
|
||||
//
|
||||
while( !CNavArea::IsOpenListEmpty() )
|
||||
{
|
||||
// get next area to check
|
||||
CNavArea *area = CNavArea::PopOpenList();
|
||||
|
||||
area->AddToClosedList();
|
||||
|
||||
// don't consider blocked areas
|
||||
if ( area->IsBlocked( teamID ) )
|
||||
continue;
|
||||
|
||||
// build adjacent area array
|
||||
CollectAdjacentAreas( area );
|
||||
|
||||
// search adjacent areas
|
||||
for( int i=0; i<m_adjAreaIndex; ++i )
|
||||
{
|
||||
CNavArea *newArea = m_adjAreaVector[ i ].area;
|
||||
|
||||
// only visit each area once
|
||||
if ( newArea->IsClosed() )
|
||||
continue;
|
||||
|
||||
// don't consider blocked areas
|
||||
if ( newArea->IsBlocked( teamID ) )
|
||||
continue;
|
||||
|
||||
// don't use this area if it is out of range
|
||||
if ( maxSearchRadius > 0.0f && ( newArea->GetCenter() - bot->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( maxSearchRadius ) )
|
||||
continue;
|
||||
|
||||
// determine cost of traversing this area
|
||||
float newCost = costFunc( newArea, area, m_adjAreaVector[ i ].ladder, NULL, -1.0f );
|
||||
|
||||
// don't use adjacent area if cost functor says it is a dead-end
|
||||
if ( newCost < 0.0f )
|
||||
continue;
|
||||
|
||||
if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
|
||||
{
|
||||
// we have already visited this area, and it has a better path
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// whether this area has been visited or not, we now have a better path to it
|
||||
newArea->SetParent( area, m_adjAreaVector[ i ].how );
|
||||
newArea->SetTotalCost( newCost );
|
||||
|
||||
// use 'cost so far' to hold cumulative cost
|
||||
newArea->SetCostSoFar( newCost );
|
||||
|
||||
// tricky bit here - relying on OpenList being sorted by cost
|
||||
if ( newArea->IsOpen() )
|
||||
{
|
||||
// area already on open list, update the list order to keep costs sorted
|
||||
newArea->UpdateOnOpenList();
|
||||
}
|
||||
else
|
||||
{
|
||||
newArea->AddToOpenList();
|
||||
}
|
||||
|
||||
// keep track of best goal so far
|
||||
goalArea = goalSelector( goalArea, newArea );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( goalArea )
|
||||
{
|
||||
// compile the path details into a usable path
|
||||
AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
|
||||
return true;
|
||||
}
|
||||
|
||||
// all adjacent areas are likely too far away
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Given the last area in a path with valid parent pointers,
|
||||
* construct the actual path.
|
||||
*/
|
||||
void AssemblePrecomputedPath( INextBot *bot, const Vector &goal, CNavArea *endArea )
|
||||
{
|
||||
VPROF_BUDGET( "AssemblePrecomputedPath", "NextBot" );
|
||||
|
||||
const Vector &start = bot->GetPosition();
|
||||
|
||||
// get count
|
||||
int count = 0;
|
||||
CNavArea *area;
|
||||
for( area = endArea; area; area = area->GetParent() )
|
||||
{
|
||||
++count;
|
||||
}
|
||||
|
||||
// save room for endpoint
|
||||
if ( count > MAX_PATH_SEGMENTS-1 )
|
||||
{
|
||||
count = MAX_PATH_SEGMENTS-1;
|
||||
}
|
||||
else if ( count == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( count == 1 )
|
||||
{
|
||||
BuildTrivialPath( bot, goal );
|
||||
return;
|
||||
}
|
||||
|
||||
// assemble path
|
||||
m_segmentCount = count;
|
||||
for( area = endArea; count && area; area = area->GetParent() )
|
||||
{
|
||||
--count;
|
||||
m_path[ count ].area = area;
|
||||
m_path[ count ].how = area->GetParentHow();
|
||||
m_path[ count ].type = ON_GROUND;
|
||||
}
|
||||
|
||||
// append actual goal position
|
||||
m_path[ m_segmentCount ].area = endArea;
|
||||
m_path[ m_segmentCount ].pos = goal;
|
||||
m_path[ m_segmentCount ].ladder = NULL;
|
||||
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
|
||||
m_path[ m_segmentCount ].type = ON_GROUND;
|
||||
++m_segmentCount;
|
||||
|
||||
// compute path positions
|
||||
if ( ComputePathDetails( bot, start ) == false )
|
||||
{
|
||||
Invalidate();
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return;
|
||||
}
|
||||
|
||||
// remove redundant nodes and clean up path
|
||||
Optimize( bot );
|
||||
|
||||
PostProcess();
|
||||
|
||||
OnPathChanged( bot, COMPLETE_PATH );
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for when start and goal are in the same area
|
||||
*/
|
||||
bool BuildTrivialPath( INextBot *bot, const Vector &goal );
|
||||
|
||||
/**
|
||||
* Determine exactly where the path goes between the given two areas
|
||||
* on the path. Return this point in 'crossPos'.
|
||||
*/
|
||||
virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const;
|
||||
|
||||
|
||||
private:
|
||||
enum { MAX_PATH_SEGMENTS = 256 };
|
||||
Segment m_path[ MAX_PATH_SEGMENTS ];
|
||||
int m_segmentCount;
|
||||
|
||||
bool ComputePathDetails( INextBot *bot, const Vector &start ); // determine actual path positions
|
||||
|
||||
void Optimize( INextBot *bot );
|
||||
void PostProcess( void );
|
||||
int FindNextOccludedNode( INextBot *bot, int anchor ); // used by Optimize()
|
||||
|
||||
void InsertSegment( Segment newSegment, int i ); // insert new segment at index i
|
||||
|
||||
mutable Vector m_pathPos; // used by GetPosition()
|
||||
mutable Vector m_closePos; // used by GetClosestPosition()
|
||||
|
||||
mutable float m_cursorPos; // current cursor position (distance along path)
|
||||
mutable Data m_cursorData; // used by GetCursorData()
|
||||
mutable bool m_isCursorDataDirty;
|
||||
|
||||
IntervalTimer m_ageTimer; // how old is this path?
|
||||
CHandle< CBaseCombatCharacter > m_subject; // the subject this path leads to
|
||||
|
||||
/**
|
||||
* Build a vector of adjacent areas reachable from the given area
|
||||
*/
|
||||
void CollectAdjacentAreas( CNavArea *area )
|
||||
{
|
||||
m_adjAreaIndex = 0;
|
||||
|
||||
const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
|
||||
FOR_EACH_VEC( adjNorth, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
|
||||
FOR_EACH_VEC( adjSouth, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
|
||||
FOR_EACH_VEC( adjWest, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
|
||||
FOR_EACH_VEC( adjEast, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
|
||||
FOR_EACH_VEC( adjUpLadder, it )
|
||||
{
|
||||
CNavLadder *ladder = adjUpLadder[ it ].ladder;
|
||||
|
||||
if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
}
|
||||
|
||||
const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
|
||||
FOR_EACH_VEC( adjDownLadder, it )
|
||||
{
|
||||
CNavLadder *ladder = adjDownLadder[ it ].ladder;
|
||||
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
if ( ladder->m_bottomArea )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum { MAX_ADJ_AREAS = 64 };
|
||||
|
||||
struct AdjInfo
|
||||
{
|
||||
CNavArea *area;
|
||||
CNavLadder *ladder;
|
||||
NavTraverseType how;
|
||||
};
|
||||
|
||||
AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
|
||||
int m_adjAreaIndex;
|
||||
|
||||
};
|
||||
|
||||
|
||||
inline float Path::GetLength( void ) const
|
||||
{
|
||||
if (m_segmentCount <= 0)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return m_path[ m_segmentCount-1 ].distanceFromStart;
|
||||
}
|
||||
|
||||
inline bool Path::IsValid( void ) const
|
||||
{
|
||||
return (m_segmentCount > 0);
|
||||
}
|
||||
|
||||
inline void Path::Invalidate( void )
|
||||
{
|
||||
m_segmentCount = 0;
|
||||
|
||||
m_cursorPos = 0.0f;
|
||||
|
||||
m_cursorData.pos = vec3_origin;
|
||||
m_cursorData.forward = Vector( 1.0f, 0, 0 );
|
||||
m_cursorData.curvature = 0.0f;
|
||||
m_cursorData.segmentPrior = NULL;
|
||||
|
||||
m_isCursorDataDirty = true;
|
||||
|
||||
m_subject = NULL;
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::FirstSegment( void ) const
|
||||
{
|
||||
return (IsValid()) ? &m_path[0] : NULL;
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::NextSegment( const Segment *currentSegment ) const
|
||||
{
|
||||
if (currentSegment == NULL || !IsValid())
|
||||
return NULL;
|
||||
|
||||
int i = currentSegment - m_path;
|
||||
|
||||
if (i < 0 || i >= m_segmentCount-1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &m_path[ i+1 ];
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::PriorSegment( const Segment *currentSegment ) const
|
||||
{
|
||||
if (currentSegment == NULL || !IsValid())
|
||||
return NULL;
|
||||
|
||||
int i = currentSegment - m_path;
|
||||
|
||||
if (i < 1 || i >= m_segmentCount)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &m_path[ i-1 ];
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::LastSegment( void ) const
|
||||
{
|
||||
return ( IsValid() ) ? &m_path[ m_segmentCount-1 ] : NULL;
|
||||
}
|
||||
|
||||
inline const Vector &Path::GetStartPosition( void ) const
|
||||
{
|
||||
return ( IsValid() ) ? m_path[ 0 ].pos : vec3_origin;
|
||||
}
|
||||
|
||||
inline const Vector &Path::GetEndPosition( void ) const
|
||||
{
|
||||
return ( IsValid() ) ? m_path[ m_segmentCount-1 ].pos : vec3_origin;
|
||||
}
|
||||
|
||||
inline CBaseCombatCharacter *Path::GetSubject( void ) const
|
||||
{
|
||||
return m_subject;
|
||||
}
|
||||
|
||||
inline void Path::MoveCursorToStart( void )
|
||||
{
|
||||
m_cursorPos = 0.0f;
|
||||
m_isCursorDataDirty = true;
|
||||
}
|
||||
|
||||
inline void Path::MoveCursorToEnd( void )
|
||||
{
|
||||
m_cursorPos = GetLength();
|
||||
m_isCursorDataDirty = true;
|
||||
}
|
||||
|
||||
inline void Path::MoveCursor( float value, MoveCursorType type )
|
||||
{
|
||||
if ( type == PATH_ABSOLUTE_DISTANCE )
|
||||
{
|
||||
m_cursorPos = value;
|
||||
}
|
||||
else // relative distance
|
||||
{
|
||||
m_cursorPos += value;
|
||||
}
|
||||
|
||||
if ( m_cursorPos < 0.0f )
|
||||
{
|
||||
m_cursorPos = 0.0f;
|
||||
}
|
||||
else if ( m_cursorPos > GetLength() )
|
||||
{
|
||||
m_cursorPos = GetLength();
|
||||
}
|
||||
|
||||
m_isCursorDataDirty = true;
|
||||
}
|
||||
|
||||
inline float Path::GetCursorPosition( void ) const
|
||||
{
|
||||
return m_cursorPos;
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::GetCurrentGoal( void ) const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
inline float Path::GetAge( void ) const
|
||||
{
|
||||
return m_ageTimer.GetElapsedTime();
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_PATH_H_
|
||||
|
||||
1898
game/server/NextBot/Path/NextBotPathFollow.cpp
Normal file
1898
game/server/NextBot/Path/NextBotPathFollow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
96
game/server/NextBot/Path/NextBotPathFollow.h
Normal file
96
game/server/NextBot/Path/NextBotPathFollow.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// NextBotPathFollow.h
|
||||
// Path following
|
||||
// Author: Michael Booth, April 2005
|
||||
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
|
||||
|
||||
#ifndef _NEXT_BOT_PATH_FOLLOWER_
|
||||
#define _NEXT_BOT_PATH_FOLLOWER_
|
||||
|
||||
#include "nav_mesh.h"
|
||||
#include "nav_pathfind.h"
|
||||
#include "NextBotPath.h"
|
||||
|
||||
class INextBot;
|
||||
class ILocomotion;
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A PathFollower extends a Path to include mechanisms to move along (follow) it
|
||||
*/
|
||||
class PathFollower : public Path
|
||||
{
|
||||
public:
|
||||
PathFollower( void );
|
||||
virtual ~PathFollower();
|
||||
|
||||
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
|
||||
virtual void Draw( const Path::Segment *start = NULL ) const; // (EXTEND) draw the path for debugging
|
||||
virtual void OnPathChanged( INextBot *bot, Path::ResultType result ); // invoked when the path is (re)computed (path is valid at the time of this call)
|
||||
|
||||
virtual void Update( INextBot *bot ); // move bot along path
|
||||
|
||||
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
|
||||
|
||||
virtual void SetMinLookAheadDistance( float value ); // minimum range movement goal must be along path
|
||||
|
||||
virtual CBaseEntity *GetHindrance( void ) const; // returns entity that is hindering our progress along the path
|
||||
|
||||
virtual bool IsDiscontinuityAhead( INextBot *bot, Path::SegmentType type, float range = -1.0f ) const; // return true if there is a the given discontinuity ahead in the path within the given range (-1 = entire remaining path)
|
||||
|
||||
private:
|
||||
const Path::Segment *m_goal; // our current goal along the path
|
||||
float m_minLookAheadRange;
|
||||
|
||||
bool CheckProgress( INextBot *bot );
|
||||
bool IsAtGoal( INextBot *bot ) const; // return true if reached current path goal
|
||||
|
||||
//bool IsOnStairs( INextBot *bot ) const; // return true if bot is standing on a stairway
|
||||
bool m_isOnStairs;
|
||||
|
||||
CountdownTimer m_avoidTimer; // do avoid check more often if we recently avoided
|
||||
|
||||
CountdownTimer m_waitTimer; // for waiting for a blocker to move off our path
|
||||
CHandle< CBaseEntity > m_hindrance;
|
||||
|
||||
// debug display data for avoid volumes
|
||||
bool m_didAvoidCheck;
|
||||
Vector m_leftFrom;
|
||||
Vector m_leftTo;
|
||||
bool m_isLeftClear;
|
||||
Vector m_rightFrom;
|
||||
Vector m_rightTo;
|
||||
bool m_isRightClear;
|
||||
Vector m_hullMin, m_hullMax;
|
||||
|
||||
void AdjustSpeed( INextBot *bot ); // adjust speed based on path curvature
|
||||
|
||||
Vector Avoid( INextBot *bot, const Vector &goalPos, const Vector &forward, const Vector &left ); // avoidance movements for very nearby obstacles. returns modified goal position
|
||||
bool Climbing( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &left, float goalRange ); // climb up ledges
|
||||
bool JumpOverGaps( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &left, float goalRange ); // jump over gaps
|
||||
|
||||
bool LadderUpdate( INextBot *bot ); // move bot along ladder
|
||||
CBaseEntity *FindBlocker( INextBot *bot ); // if entity is returned, it is blocking us from continuing along our path
|
||||
};
|
||||
|
||||
|
||||
inline const Path::Segment *PathFollower::GetCurrentGoal( void ) const
|
||||
{
|
||||
return m_goal;
|
||||
}
|
||||
|
||||
|
||||
inline void PathFollower::SetMinLookAheadDistance( float value )
|
||||
{
|
||||
m_minLookAheadRange = value;
|
||||
}
|
||||
|
||||
inline CBaseEntity *PathFollower::GetHindrance( void ) const
|
||||
{
|
||||
return m_hindrance;
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_PATH_FOLLOWER_
|
||||
|
||||
|
||||
573
game/server/NextBot/Path/NextBotRetreatPath.h
Normal file
573
game/server/NextBot/Path/NextBotRetreatPath.h
Normal file
@@ -0,0 +1,573 @@
|
||||
// NextBotRetreatPath.h
|
||||
// Maintain and follow a path that leads safely away from the given Actor
|
||||
// Author: Michael Booth, February 2007
|
||||
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
|
||||
|
||||
#ifndef _NEXT_BOT_RETREAT_PATH_
|
||||
#define _NEXT_BOT_RETREAT_PATH_
|
||||
|
||||
#include "nav.h"
|
||||
#include "NextBotInterface.h"
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "NextBotRetreatPath.h"
|
||||
#include "NextBotUtil.h"
|
||||
#include "NextBotPathFollow.h"
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A RetreatPath extends a PathFollower to periodically recompute a path
|
||||
* away from a threat, and to move along the path away from that threat.
|
||||
*/
|
||||
class RetreatPath : public PathFollower
|
||||
{
|
||||
public:
|
||||
RetreatPath( void );
|
||||
virtual ~RetreatPath() { }
|
||||
|
||||
void Update( INextBot *bot, CBaseEntity *threat ); // update path away from threat and move bot along path
|
||||
|
||||
virtual float GetMaxPathLength( void ) const; // return maximum path length
|
||||
|
||||
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
|
||||
|
||||
private:
|
||||
void RefreshPath( INextBot *bot, CBaseEntity *threat );
|
||||
|
||||
CountdownTimer m_throttleTimer; // require a minimum time between re-paths
|
||||
EHANDLE m_pathThreat; // the threat of our existing path
|
||||
Vector m_pathThreatPos; // where the threat was when the path was built
|
||||
};
|
||||
|
||||
inline RetreatPath::RetreatPath( void )
|
||||
{
|
||||
m_throttleTimer.Invalidate();
|
||||
m_pathThreat = NULL;
|
||||
}
|
||||
|
||||
inline float RetreatPath::GetMaxPathLength( void ) const
|
||||
{
|
||||
return 1000.0f;
|
||||
}
|
||||
|
||||
inline void RetreatPath::Invalidate( void )
|
||||
{
|
||||
// path is gone, repath at earliest opportunity
|
||||
m_throttleTimer.Invalidate();
|
||||
m_pathThreat = NULL;
|
||||
|
||||
// extend
|
||||
PathFollower::Invalidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Maintain a path to our chase threat and move along that path
|
||||
*/
|
||||
inline void RetreatPath::Update( INextBot *bot, CBaseEntity *threat )
|
||||
{
|
||||
VPROF_BUDGET( "RetreatPath::Update", "NextBot" );
|
||||
|
||||
if ( threat == NULL )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if our path threat changed, repath immediately
|
||||
if ( threat != m_pathThreat )
|
||||
{
|
||||
if ( bot->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) Chase path threat changed (from %X to %X).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_pathThreat.Get(), threat );
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// maintain the path away from the threat
|
||||
RefreshPath( bot, threat );
|
||||
|
||||
// move along the path towards the threat
|
||||
PathFollower::Update( bot );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Build a path away from retreatFromArea up to retreatRange in length.
|
||||
*/
|
||||
class RetreatPathBuilder
|
||||
{
|
||||
public:
|
||||
RetreatPathBuilder( INextBot *me, CBaseEntity *threat, float retreatRange = 500.0f )
|
||||
{
|
||||
m_me = me;
|
||||
m_mover = me->GetLocomotionInterface();
|
||||
|
||||
m_threat = threat;
|
||||
m_retreatRange = retreatRange;
|
||||
}
|
||||
|
||||
CNavArea *ComputePath( void )
|
||||
{
|
||||
VPROF_BUDGET( "NavAreaBuildRetreatPath", "NextBot" );
|
||||
|
||||
if ( m_mover == NULL )
|
||||
return NULL;
|
||||
|
||||
CNavArea *startArea = m_me->GetEntity()->GetLastKnownArea();
|
||||
|
||||
if ( startArea == NULL )
|
||||
return NULL;
|
||||
|
||||
CNavArea *retreatFromArea = TheNavMesh->GetNearestNavArea( m_threat->GetAbsOrigin() );
|
||||
if ( retreatFromArea == NULL )
|
||||
return NULL;
|
||||
|
||||
startArea->SetParent( NULL );
|
||||
|
||||
// start search
|
||||
CNavArea::ClearSearchLists();
|
||||
|
||||
float initCost = Cost( startArea, NULL, NULL );
|
||||
if ( initCost < 0.0f )
|
||||
return NULL;
|
||||
|
||||
int teamID = m_me->GetEntity()->GetTeamNumber();
|
||||
|
||||
startArea->SetTotalCost( initCost );
|
||||
|
||||
startArea->AddToOpenList();
|
||||
|
||||
// keep track of the area farthest away from the threat
|
||||
CNavArea *farthestArea = NULL;
|
||||
float farthestRange = 0.0f;
|
||||
|
||||
//
|
||||
// Dijkstra's algorithm (since we don't know our goal).
|
||||
// Build a path as far away from the retreat area as possible.
|
||||
// Minimize total path length and danger.
|
||||
// Maximize distance to threat of end of path.
|
||||
//
|
||||
while( !CNavArea::IsOpenListEmpty() )
|
||||
{
|
||||
// get next area to check
|
||||
CNavArea *area = CNavArea::PopOpenList();
|
||||
|
||||
area->AddToClosedList();
|
||||
|
||||
// don't consider blocked areas
|
||||
if ( area->IsBlocked( teamID ) )
|
||||
continue;
|
||||
|
||||
// build adjacent area array
|
||||
CollectAdjacentAreas( area );
|
||||
|
||||
// search adjacent areas
|
||||
for( int i=0; i<m_adjAreaIndex; ++i )
|
||||
{
|
||||
CNavArea *newArea = m_adjAreaVector[ i ].area;
|
||||
|
||||
// only visit each area once
|
||||
if ( newArea->IsClosed() )
|
||||
continue;
|
||||
|
||||
// don't consider blocked areas
|
||||
if ( newArea->IsBlocked( teamID ) )
|
||||
continue;
|
||||
|
||||
// don't use this area if it is out of range
|
||||
if ( ( newArea->GetCenter() - m_me->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( m_retreatRange ) )
|
||||
continue;
|
||||
|
||||
// determine cost of traversing this area
|
||||
float newCost = Cost( newArea, area, m_adjAreaVector[ i ].ladder );
|
||||
|
||||
// don't use adjacent area if cost functor says it is a dead-end
|
||||
if ( newCost < 0.0f )
|
||||
continue;
|
||||
|
||||
if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
|
||||
{
|
||||
// we have already visited this area, and it has a better path
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// whether this area has been visited or not, we now have a better path
|
||||
newArea->SetParent( area, m_adjAreaVector[ i ].how );
|
||||
newArea->SetTotalCost( newCost );
|
||||
|
||||
// use 'cost so far' to hold cumulative cost
|
||||
newArea->SetCostSoFar( newCost );
|
||||
|
||||
// tricky bit here - relying on OpenList being sorted by cost
|
||||
if ( newArea->IsOpen() )
|
||||
{
|
||||
// area already on open list, update the list order to keep costs sorted
|
||||
newArea->UpdateOnOpenList();
|
||||
}
|
||||
else
|
||||
{
|
||||
newArea->AddToOpenList();
|
||||
}
|
||||
|
||||
// keep track of area farthest from threat
|
||||
float threatRange = ( newArea->GetCenter() - m_threat->GetAbsOrigin() ).Length();
|
||||
if ( threatRange > farthestRange )
|
||||
{
|
||||
farthestArea = newArea;
|
||||
farthestRange = threatRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return farthestArea;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a vector of adjacent areas reachable from the given area
|
||||
*/
|
||||
void CollectAdjacentAreas( CNavArea *area )
|
||||
{
|
||||
m_adjAreaIndex = 0;
|
||||
|
||||
const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
|
||||
FOR_EACH_VEC( adjNorth, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
|
||||
FOR_EACH_VEC( adjSouth, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
|
||||
FOR_EACH_VEC( adjWest, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
|
||||
FOR_EACH_VEC( adjEast, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
|
||||
FOR_EACH_VEC( adjUpLadder, it )
|
||||
{
|
||||
CNavLadder *ladder = adjUpLadder[ it ].ladder;
|
||||
|
||||
if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
}
|
||||
|
||||
const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
|
||||
FOR_EACH_VEC( adjDownLadder, it )
|
||||
{
|
||||
CNavLadder *ladder = adjDownLadder[ it ].ladder;
|
||||
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
if ( ladder->m_bottomArea )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cost minimizes path length traveled thus far and "danger" (proximity to threat(s))
|
||||
*/
|
||||
float Cost( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
|
||||
{
|
||||
// check if we can use this area
|
||||
if ( !m_mover->IsAreaTraversable( area ) )
|
||||
{
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
int teamID = m_me->GetEntity()->GetTeamNumber();
|
||||
if ( area->IsBlocked( teamID ) )
|
||||
{
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
const float debugDeltaT = 3.0f;
|
||||
|
||||
float cost;
|
||||
|
||||
const float maxThreatRange = 500.0f;
|
||||
const float dangerDensity = 1000.0f;
|
||||
|
||||
if ( fromArea == NULL )
|
||||
{
|
||||
cost = 0.0f;
|
||||
|
||||
if ( area->Contains( m_threat->GetAbsOrigin() ) )
|
||||
{
|
||||
// maximum danger - threat is in the area with us
|
||||
cost += 10.0f * dangerDensity;
|
||||
|
||||
if ( m_me->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
area->DrawFilled( 255, 0, 0, 128 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// danger proportional to range to us
|
||||
float rangeToThreat = ( m_threat->GetAbsOrigin() - m_me->GetEntity()->GetAbsOrigin() ).Length();
|
||||
|
||||
if ( rangeToThreat < maxThreatRange )
|
||||
{
|
||||
cost += dangerDensity * ( 1.0f - ( rangeToThreat / maxThreatRange ) );
|
||||
|
||||
if ( m_me->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
NDebugOverlay::Line( m_me->GetEntity()->GetAbsOrigin(), m_threat->GetAbsOrigin(), 255, 0, 0, true, debugDeltaT );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// compute distance traveled along path so far
|
||||
float dist;
|
||||
|
||||
if ( ladder )
|
||||
{
|
||||
const float ladderCostFactor = 100.0f;
|
||||
dist = ladderCostFactor * ladder->m_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector to = area->GetCenter() - fromArea->GetCenter();
|
||||
|
||||
dist = to.Length();
|
||||
|
||||
// check for vertical discontinuities
|
||||
Vector closeFrom, closeTo;
|
||||
area->GetClosestPointOnArea( fromArea->GetCenter(), &closeTo );
|
||||
fromArea->GetClosestPointOnArea( area->GetCenter(), &closeFrom );
|
||||
|
||||
float deltaZ = closeTo.z - closeFrom.z;
|
||||
|
||||
if ( deltaZ > m_mover->GetMaxJumpHeight() )
|
||||
{
|
||||
// too high to jump
|
||||
return -1.0f;
|
||||
}
|
||||
else if ( -deltaZ > m_mover->GetDeathDropHeight() )
|
||||
{
|
||||
// too far down to drop
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
// prefer to maintain our level
|
||||
const float climbCost = 10.0f;
|
||||
dist += climbCost * fabs( deltaZ );
|
||||
}
|
||||
|
||||
cost = dist + fromArea->GetTotalCost();
|
||||
|
||||
|
||||
// Add in danger cost due to threat
|
||||
// Assume straight line between areas and find closest point
|
||||
// to the threat along that line segment. The distance between
|
||||
// the threat and closest point on the line is the danger cost.
|
||||
|
||||
// path danger is CUMULATIVE
|
||||
float dangerCost = fromArea->GetCostSoFar();
|
||||
|
||||
Vector close;
|
||||
float t;
|
||||
CalcClosestPointOnLineSegment( m_threat->GetAbsOrigin(), area->GetCenter(), fromArea->GetCenter(), close, &t );
|
||||
if ( t < 0.0f )
|
||||
{
|
||||
close = area->GetCenter();
|
||||
}
|
||||
else if ( t > 1.0f )
|
||||
{
|
||||
close = fromArea->GetCenter();
|
||||
}
|
||||
|
||||
float rangeToThreat = ( m_threat->GetAbsOrigin() - close ).Length();
|
||||
|
||||
if ( rangeToThreat < maxThreatRange )
|
||||
{
|
||||
float dangerFactor = 1.0f - ( rangeToThreat / maxThreatRange );
|
||||
dangerCost = dangerDensity * dangerFactor;
|
||||
|
||||
if ( m_me->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( fromArea->GetCenter(), area->GetCenter(), 5, 255 * dangerFactor, 0, 0, 255, true, debugDeltaT );
|
||||
|
||||
Vector to = close - m_threat->GetAbsOrigin();
|
||||
to.NormalizeInPlace();
|
||||
|
||||
NDebugOverlay::Line( close, close - 50.0f * to, 255, 0, 0, true, debugDeltaT );
|
||||
}
|
||||
}
|
||||
|
||||
cost += dangerCost;
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
private:
|
||||
INextBot *m_me;
|
||||
ILocomotion *m_mover;
|
||||
|
||||
CBaseEntity *m_threat;
|
||||
float m_retreatRange;
|
||||
|
||||
enum { MAX_ADJ_AREAS = 64 };
|
||||
|
||||
struct AdjInfo
|
||||
{
|
||||
CNavArea *area;
|
||||
CNavLadder *ladder;
|
||||
NavTraverseType how;
|
||||
};
|
||||
|
||||
AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
|
||||
int m_adjAreaIndex;
|
||||
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Periodically rebuild the path away from our threat
|
||||
*/
|
||||
inline void RetreatPath::RefreshPath( INextBot *bot, CBaseEntity *threat )
|
||||
{
|
||||
VPROF_BUDGET( "RetreatPath::RefreshPath", "NextBot" );
|
||||
|
||||
if ( threat == NULL )
|
||||
{
|
||||
if ( bot->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No threat.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// don't change our path if we're on a ladder
|
||||
ILocomotion *mover = bot->GetLocomotionInterface();
|
||||
if ( IsValid() && mover && mover->IsUsingLadder() )
|
||||
{
|
||||
if ( bot->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// the closer we get, the more accurate our path needs to be
|
||||
Vector to = threat->GetAbsOrigin() - bot->GetPosition();
|
||||
|
||||
const float minTolerance = 0.0f;
|
||||
const float toleranceRate = 0.33f;
|
||||
|
||||
float tolerance = minTolerance + toleranceRate * to.Length();
|
||||
|
||||
if ( !IsValid() || ( threat->GetAbsOrigin() - m_pathThreatPos ).IsLengthGreaterThan( tolerance ) )
|
||||
{
|
||||
if ( !m_throttleTimer.IsElapsed() )
|
||||
{
|
||||
// require a minimum time between repaths, as long as we have a path to follow
|
||||
if ( bot->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// remember our path threat
|
||||
m_pathThreat = threat;
|
||||
m_pathThreatPos = threat->GetAbsOrigin();
|
||||
|
||||
RetreatPathBuilder retreat( bot, threat, GetMaxPathLength() );
|
||||
|
||||
CNavArea *goalArea = retreat.ComputePath();
|
||||
|
||||
if ( goalArea )
|
||||
{
|
||||
AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
|
||||
}
|
||||
else
|
||||
{
|
||||
// all adjacent areas are too far away - just move directly away from threat
|
||||
Vector to = threat->GetAbsOrigin() - bot->GetPosition();
|
||||
|
||||
BuildTrivialPath( bot, bot->GetPosition() - to );
|
||||
}
|
||||
|
||||
const float minRepathInterval = 0.5f;
|
||||
m_throttleTimer.Start( minRepathInterval );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_RETREAT_PATH_
|
||||
Reference in New Issue
Block a user