1
0
mirror of https://github.com/alliedmodders/hl2sdk.git synced 2024-12-23 01:59:43 +08:00
hl2sdk/dlls/ai_behavior_follow.cpp

2839 lines
77 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "tier1/utlfixedlinkedlist.h"
#include "bitstring.h"
#include "utlvector.h"
#include "ai_navigator.h"
#include "scripted.h"
#include "ai_hint.h"
#include "ai_behavior_follow.h"
#include "ai_memory.h"
#include "ai_squad.h"
#include "ai_tacticalservices.h"
#include "ndebugoverlay.h"
#include "ai_senses.h"
#ifdef HL2_EPISODIC
#include "info_darknessmode_lightsource.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar ai_debug_follow( "ai_debug_follow", "0" );
ConVar ai_follow_use_points( "ai_follow_use_points", "1" );
ConVar ai_follow_use_points_when_moving( "ai_follow_use_points_when_moving", "1" );
#define FollowMsg(s) if ( !GetOuter() || !ai_debug_follow.GetBool() ) ; else DevMsg( GetOuter(), "Follow: " s )
#define WAIT_HINT_MIN_DIST (16*16) // Was: Square(GetHullWidth())
//-----------------------------------------------------------------------------
//
// Purpose: Formation management
//
// Right now, this is in a very preliminary sketch state. (toml 03-03-03)
//-----------------------------------------------------------------------------
struct AI_FollowSlot_t;
struct AI_FollowFormation_t;
struct AI_FollowGroup_t;
struct AI_Follower_t
{
AI_Follower_t()
{
slot = -1;
memset( &navInfo, 0, sizeof(navInfo) );
pGroup = NULL;
}
AIHANDLE hFollower;
int slot;
AI_FollowNavInfo_t navInfo;
AI_FollowGroup_t * pGroup; // backpointer for efficiency
};
struct AI_FollowGroup_t
{
AI_FollowFormation_t * pFormation;
EHANDLE hFollowTarget;
CUtlFixedLinkedList<AI_Follower_t> followers;
CBitString slotUsage;
};
//-------------------------------------
class CAI_FollowManager
{
public:
~CAI_FollowManager()
{
for ( int i = 0; i < m_groups.Count(); i++ )
delete m_groups[i];
}
bool AddFollower( CBaseEntity *pTarget, CAI_BaseNPC *pFollower, AI_Formations_t formation, AI_FollowManagerInfoHandle_t *pHandle );
void ChangeFormation( AI_FollowManagerInfoHandle_t &handle, AI_Formations_t formation );
void RemoveFollower( AI_FollowManagerInfoHandle_t &handle );
bool CalcFollowPosition( AI_FollowManagerInfoHandle_t &handle, AI_FollowNavInfo_t *pNavInfo );
int CountFollowersInGroup( CAI_BaseNPC *pMember )
{
AI_FollowGroup_t *pGroup = FindFollowerGroup( pMember );
if( !pGroup )
{
return 0;
}
return pGroup->followers.Count();
}
int GetFollowerSlot( CAI_BaseNPC *pFollower )
{
AI_FollowGroup_t *pGroup = FindFollowerGroup( pFollower );
if( !pGroup )
{
return 0;
}
int h = pGroup->followers.Head();
while( h != pGroup->followers.InvalidIndex() )
{
AI_Follower_t *it = &pGroup->followers[h];
if ( it->hFollower.Get() == pFollower )
{
return it->slot;
}
h = pGroup->followers.Next( h );
}
return 0;
}
private:
bool RedistributeSlots( AI_FollowGroup_t *pGroup );
int FindBestSlot( AI_FollowGroup_t *pGroup );
void CalculateFieldsFromSlot( AI_FollowSlot_t *pSlot, AI_FollowNavInfo_t *pFollowerInfo );
AI_FollowGroup_t *FindCreateGroup( CBaseEntity *pTarget, AI_Formations_t formation );
AI_FollowGroup_t *FindGroup( CBaseEntity *pTarget );
AI_FollowGroup_t *FindFollowerGroup( CBaseEntity *pFollower );
void RemoveGroup( AI_FollowGroup_t * );
//---------------------------------
CUtlVector<AI_FollowGroup_t *> m_groups;
};
//-------------------------------------
CAI_FollowManager g_AIFollowManager;
//-----------------------------------------------------------------------------
//
// CAI_FollowBehavior
//
//-----------------------------------------------------------------------------
BEGIN_SIMPLE_DATADESC( AI_FollowNavInfo_t )
DEFINE_FIELD( flags, FIELD_INTEGER ),
DEFINE_FIELD( position, FIELD_VECTOR ),
DEFINE_FIELD( range, FIELD_FLOAT ),
DEFINE_FIELD( Zrange, FIELD_FLOAT ),
DEFINE_FIELD( tolerance, FIELD_FLOAT ),
DEFINE_FIELD( followPointTolerance, FIELD_FLOAT ),
DEFINE_FIELD( targetMoveTolerance, FIELD_FLOAT ),
DEFINE_FIELD( repathOnRouteTolerance, FIELD_FLOAT ),
DEFINE_FIELD( walkTolerance, FIELD_FLOAT ),
DEFINE_FIELD( coverTolerance, FIELD_FLOAT ),
DEFINE_FIELD( enemyLOSTolerance, FIELD_FLOAT ),
DEFINE_FIELD( chaseEnemyTolerance, FIELD_FLOAT ),
END_DATADESC();
BEGIN_SIMPLE_DATADESC( AI_FollowParams_t )
DEFINE_FIELD( formation, FIELD_INTEGER ),
END_DATADESC();
BEGIN_DATADESC( CAI_FollowBehavior )
DEFINE_FIELD( m_hFollowTarget, FIELD_EHANDLE ),
DEFINE_EMBEDDED( m_FollowNavGoal ),
DEFINE_FIELD( m_flTimeUpdatedFollowPosition, FIELD_TIME ),
DEFINE_FIELD( m_bFirstFacing, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flTimeFollowTargetVisible, FIELD_TIME ),
DEFINE_EMBEDDED( m_TargetMonitor ),
DEFINE_FIELD( m_bTargetUnreachable, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bMovingToCover, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flOriginalEnemyDiscardTime, FIELD_FLOAT ),
DEFINE_EMBEDDED( m_FollowDelay ),
DEFINE_CUSTOM_FIELD( m_CurrentFollowActivity, ActivityDataOps() ),
DEFINE_EMBEDDED( m_TimeBlockUseWaitPoint ),
DEFINE_EMBEDDED( m_TimeCheckForWaitPoint ),
DEFINE_FIELD( m_pInterruptWaitPoint, FIELD_CLASSPTR ),
DEFINE_EMBEDDED( m_TimeBeforeSpreadFacing ),
DEFINE_EMBEDDED( m_TimeNextSpreadFacing ),
// m_hFollowManagerInfo (reset on load)
DEFINE_EMBEDDED( m_params ),
DEFINE_FIELD( m_hFollowGoalEnt, FIELD_EHANDLE ),
DEFINE_FIELD( m_nFailedFollowAttempts, FIELD_INTEGER ),
DEFINE_FIELD( m_flTimeFailFollowStarted, FIELD_TIME ),
DEFINE_FIELD( m_vFollowMoveAnchor, FIELD_POSITION_VECTOR ),
END_DATADESC();
//-------------------------------------
CAI_FollowBehavior::CAI_FollowBehavior( const AI_FollowParams_t &params )
{
memset( &m_FollowNavGoal, 0, sizeof( m_FollowNavGoal ) );
m_FollowDelay.Set( 1.0, 3.0 );
m_hFollowManagerInfo.m_pGroup = NULL;
m_hFollowManagerInfo.m_hFollower = 0;
m_TimeBlockUseWaitPoint.Set( 0.5, 1.5 );
m_TimeCheckForWaitPoint.Set( 1.0 );
m_pInterruptWaitPoint = NULL;
m_TimeBeforeSpreadFacing.Set( 2.0, 4.0 );
m_TimeNextSpreadFacing.Set( 3.0, 12.0 );
m_params = params;
NoteSuccessfulFollow();
}
//-------------------------------------
CAI_FollowBehavior::~CAI_FollowBehavior()
{
Assert( !m_hFollowManagerInfo.m_pGroup );
}
//-----------------------------------------------------------------------------
// Purpose: Draw any text overlays
// Input : Previous text offset from the top
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CAI_FollowBehavior::DrawDebugTextOverlays( int text_offset )
{
char tempstr[ 512 ];
int offset;
CBaseEntity * followEnt;
offset = BaseClass::DrawDebugTextOverlays( text_offset );
if ( GetOuter()->m_debugOverlays & OVERLAY_TEXT_BIT )
{
followEnt = GetFollowTarget();
if ( followEnt != NULL )
{
Q_snprintf( tempstr, sizeof(tempstr), "Follow: (%d) %s (%s)", followEnt->entindex(), followEnt->GetDebugName(), followEnt->GetClassname() );
}
else
{
Q_snprintf( tempstr, sizeof(tempstr), "Follow: NULL" );
}
GetOuter()->EntityText( offset, tempstr, 0 );
offset++;
}
return offset;
}
//-------------------------------------
void CAI_FollowBehavior::SetParameters( const AI_FollowParams_t &params )
{
m_params = params;
if ( m_hFollowManagerInfo.m_pGroup )
{
g_AIFollowManager.ChangeFormation( m_hFollowManagerInfo, params.formation );
m_flTimeUpdatedFollowPosition = 0;
}
}
//-------------------------------------
CBaseEntity * CAI_FollowBehavior::GetFollowTarget()
{
return m_hFollowTarget;
}
//-------------------------------------
// Returns true if the NPC is actively following a target.
bool CAI_FollowBehavior::IsActive( void )
{
if ( IsRunning() && GetFollowTarget() )
{
// Only true if we're running a follow schedule
return IsCurScheduleFollowSchedule();
}
return false;
}
//-------------------------------------
void CAI_FollowBehavior::SetFollowTarget( CBaseEntity *pLeader, bool fFinishCurSchedule )
{
if ( pLeader == m_hFollowTarget )
return;
m_flTimeUpdatedFollowPosition = 0;
if ( m_hFollowTarget )
{
g_AIFollowManager.RemoveFollower( m_hFollowManagerInfo );
m_hFollowTarget = NULL;
m_hFollowManagerInfo.m_pGroup = NULL;
if ( IsRunning() )
{
if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT )
{
GetNavigator()->StopMoving(); // Stop him from walking toward the player
}
if ( GetEnemy() != NULL )
{
GetOuter()->SetIdealState( NPC_STATE_COMBAT );
}
}
}
if ( pLeader )
{
if ( g_AIFollowManager.AddFollower( pLeader, GetOuter(), m_params.formation, &m_hFollowManagerInfo ) )
{
m_hFollowTarget = pLeader;
m_bFirstFacing = true;
m_flTimeFollowTargetVisible = 0;
SetCondition( COND_TARGET_MOVED_FROM_MARK );
m_TargetMonitor.ClearMark();
NoteSuccessfulFollow();
}
}
NotifyChangeBehaviorStatus(fFinishCurSchedule);
}
//-------------------------------------
void CAI_FollowBehavior::SetFollowGoalDirect( CAI_FollowGoal *pGoal )
{
m_hFollowGoalEnt = pGoal;
m_flTimeUpdatedFollowPosition = 0;
}
//-------------------------------------
bool CAI_FollowBehavior::SetFollowGoal( CAI_FollowGoal *pGoal, bool fFinishCurSchedule )
{
if ( GetOuter()->ShouldAcceptGoal( this, pGoal ) )
{
GetOuter()->ClearCommandGoal();
if( hl2_episodic.GetBool() )
{
// Poke the NPC to interrupt any stubborn schedules
GetOuter()->SetCondition(COND_PROVOKED);
}
SetFollowTarget( pGoal->GetGoalEntity() );
Assert( pGoal->m_iFormation == AIF_SIMPLE || pGoal->m_iFormation == AIF_WIDE || pGoal->m_iFormation == AIF_MEDIUM || pGoal->m_iFormation == AIF_SIDEKICK );
SetParameters( AI_FollowParams_t( (AI_Formations_t)pGoal->m_iFormation ) );
m_hFollowGoalEnt = pGoal;
m_flTimeUpdatedFollowPosition = 0;
return true;
}
return false;
}
//-------------------------------------
void CAI_FollowBehavior::ClearFollowGoal( CAI_FollowGoal *pGoal )
{
GetOuter()->OnClearGoal( this, pGoal );
if ( pGoal == m_hFollowGoalEnt )
{
SetFollowTarget( NULL );
m_hFollowGoalEnt = NULL;
m_flTimeUpdatedFollowPosition = 0;
}
}
//-------------------------------------
bool CAI_FollowBehavior::UpdateFollowPosition()
{
AI_PROFILE_SCOPE( CAI_FollowBehavior_UpdateFollowPosition );
if ( m_flTimeUpdatedFollowPosition == gpGlobals->curtime )
{
return true;
}
if (m_hFollowTarget == NULL)
return false;
if ( !g_AIFollowManager.CalcFollowPosition( m_hFollowManagerInfo, &m_FollowNavGoal ) )
{
return false;
}
#if TODO
// @TODO (toml 07-27-03): this is too simplistic. fails when the new point is an inappropriate target
CBasePlayer *pPlayer = dynamic_cast<CBasePlayer *>(m_hFollowTarget.Get());
Vector targetVelocity = pPlayer->GetSmoothedVelocity();
m_FollowNavGoal.position += targetVelocity * 0.5;
#endif
m_flTimeUpdatedFollowPosition = gpGlobals->curtime;
return true;
}
//-------------------------------------
bool CAI_FollowBehavior::IsMovingToFollowTarget()
{
return ( IsRunning() && ( IsCurSchedule(SCHED_FOLLOW, false) || IsCurSchedule(SCHED_FOLLOWER_GO_TO_WAIT_POINT, false) ) );
}
//-------------------------------------
bool CAI_FollowBehavior::CanSelectSchedule()
{
if ( !GetOuter()->IsInterruptable() )
return false;
return ShouldFollow();
}
//-------------------------------------
bool CAI_FollowBehavior::PlayerIsPushing()
{
return (m_hFollowTarget && m_hFollowTarget->IsPlayer() && HasCondition( COND_PLAYER_PUSHING ) );
}
//-------------------------------------
bool CAI_FollowBehavior::IsFollowTargetInRange()
{
if ( !GetFollowTarget()->IsPlayer() && HasCondition( COND_RECEIVED_ORDERS ) )
return false;
if( GetNpcState() == NPC_STATE_COMBAT )
{
if( IsFollowGoalInRange( MAX( m_FollowNavGoal.coverTolerance, m_FollowNavGoal.enemyLOSTolerance ), GetGoalZRange(), GetGoalFlags() ) )
{
return true;
}
}
else
{
if( IsFollowGoalInRange( MAX( m_FollowNavGoal.tolerance, GetGoalRange() ), GetGoalZRange(), GetGoalFlags() ) )
{
if ( m_FollowNavGoal.flags & AIFF_REQUIRE_LOS_OUTSIDE_COMBAT )
{
//trace_t tr;
//AI_TraceLOS( vecStart, vecStart + vecDir * 8192, m_hFollowTarget, &tr );
//if ( AI_TraceLOS m_FollowNavGoal.position
if ( !HasCondition(COND_SEE_PLAYER) )
return false;
}
return true;
}
}
return false;
}
//-------------------------------------
bool CAI_FollowBehavior::IsFollowGoalInRange( float tolerance, float zTolerance, int flags )
{
const Vector &origin = WorldSpaceCenter();
const Vector &goal = GetGoalPosition();
if ( zTolerance == -1 )
zTolerance = GetHullHeight();
float distanceSq = ( goal.AsVector2D() - origin.AsVector2D() ).LengthSqr();
tolerance += 0.1;
// Increase Z tolerance slightly as XY distance decreases
float flToleranceSq = (tolerance*tolerance);
float flIncreaseRange = flToleranceSq * 0.25;
zTolerance += zTolerance * clamp((distanceSq / flIncreaseRange), 0, 1 );
if ( fabs( origin.z - goal.z ) > zTolerance )
return false;
if ( distanceSq > flToleranceSq )
return false;
if ( flags & AIFF_REQUIRE_LOS_OUTSIDE_COMBAT && m_hFollowTarget.Get() )
{
if ( !GetOuter()->GetSenses()->DidSeeEntity( m_hFollowTarget ) )
return false;
}
return true;
}
//-------------------------------------
bool CAI_FollowBehavior::IsChaseGoalInRange()
{
if ( GetEnemy() && ( GetEnemy()->WorldSpaceCenter() - m_FollowNavGoal.position ).LengthSqr() > Square( m_FollowNavGoal.chaseEnemyTolerance ) )
return false;
return true;
}
//-------------------------------------
void CAI_FollowBehavior::NoteFailedFollow()
{
m_nFailedFollowAttempts++;
if ( m_flTimeFailFollowStarted == FLT_MAX )
m_flTimeFailFollowStarted = gpGlobals->curtime;
if ( GetOuter() && ai_debug_follow.GetBool() )
DevMsg( GetOuter(), "Follow: NoteFailedFollow() (%d, %f)\n", m_nFailedFollowAttempts, m_flTimeFailFollowStarted );
}
//-------------------------------------
void CAI_FollowBehavior::NoteSuccessfulFollow()
{
m_nFailedFollowAttempts = 0;
m_flTimeFailFollowStarted = FLT_MAX;
FollowMsg( "NoteSuccessfulFollow()\n" );
}
//-------------------------------------
void CAI_FollowBehavior::BeginScheduleSelection()
{
if ( GetOuter()->m_hCine )
GetOuter()->m_hCine->CancelScript();
m_TimeBeforeSpreadFacing.Reset();
SetCondition( COND_TARGET_MOVED_FROM_MARK );
m_TargetMonitor.ClearMark();
NoteSuccessfulFollow();
// Forget about enemies that I haven't seen for >5 seconds
m_flOriginalEnemyDiscardTime = GetOuter()->GetEnemies()->GetEnemyDiscardTime();
GetOuter()->GetEnemies()->SetEnemyDiscardTime( 5.0f );
BaseClass::BeginScheduleSelection();
}
//-------------------------------------
void CAI_FollowBehavior::EndScheduleSelection()
{
// Restore our original enemy discard time
GetOuter()->GetEnemies()->SetEnemyDiscardTime( m_flOriginalEnemyDiscardTime );
BaseClass::EndScheduleSelection();
}
//-------------------------------------
void CAI_FollowBehavior::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput )
{
if ( m_hFollowManagerInfo.m_pGroup )
{
g_AIFollowManager.RemoveFollower( m_hFollowManagerInfo );
m_hFollowManagerInfo.m_pGroup = NULL;
m_hFollowTarget = NULL;
}
BaseClass::CleanupOnDeath( pCulprit, bFireDeathOutput );
}
//-------------------------------------
void CAI_FollowBehavior::Precache()
{
if ( m_hFollowTarget != NULL && m_hFollowManagerInfo.m_pGroup == NULL )
{
// Post load fixup
if ( !g_AIFollowManager.AddFollower( m_hFollowTarget, GetOuter(), m_params.formation, &m_hFollowManagerInfo ) )
{
m_hFollowTarget = NULL;
}
}
}
//-------------------------------------
void CAI_FollowBehavior::GatherConditions( void )
{
BaseClass::GatherConditions();
if ( !GetFollowTarget() )
{
ClearCondition( COND_FOLLOW_PLAYER_IS_LIT );
ClearCondition( COND_FOLLOW_PLAYER_IS_NOT_LIT );
ClearCondition( COND_FOLLOW_TARGET_VISIBLE );
ClearCondition( COND_FOLLOW_TARGET_NOT_VISIBLE );
ClearCondition( COND_FOLLOW_DELAY_EXPIRED );
ClearCondition( COND_TARGET_MOVED_FROM_MARK );
ClearFollowPoint();
m_pInterruptWaitPoint = NULL;
m_bTargetUnreachable = false;
m_flTimeFollowTargetVisible = 0;
return;
}
if ( !m_TargetMonitor.IsMarkSet() )
{
FollowMsg( "No mark set\n" );
}
if ( m_FollowDelay.IsRunning() && m_FollowDelay.Expired())
{
SetCondition( COND_FOLLOW_DELAY_EXPIRED );
m_FollowDelay.Stop();
}
if ( m_TargetMonitor.TargetMoved2D( GetFollowTarget() ) )
{
FollowMsg( "Target moved\n" );
m_TargetMonitor.ClearMark();
SetCondition( COND_TARGET_MOVED_FROM_MARK );
m_bTargetUnreachable = false;
}
if ( !m_TargetMonitor.IsMarkSet() )
m_bTargetUnreachable = false;
m_pInterruptWaitPoint = NULL;
if ( GetHintNode() == NULL )
{
if ( ShouldUseFollowPoints() && m_TimeBlockUseWaitPoint.Expired() && m_TimeCheckForWaitPoint.Expired() )
{
m_TimeCheckForWaitPoint.Reset();
m_pInterruptWaitPoint = FindFollowPoint();
if ( m_pInterruptWaitPoint )
SetCondition( COND_FOUND_WAIT_POINT );
}
}
if ( m_flTimeUpdatedFollowPosition == 0 || gpGlobals->curtime - m_flTimeUpdatedFollowPosition > 2.0 )
UpdateFollowPosition();
if ( IsFollowTargetInRange() )
{
NoteSuccessfulFollow();
}
else if ( GetOuter()->GetTask() && !IsCurScheduleFollowSchedule() )
{
if ( !m_FollowDelay.IsRunning() || m_FollowDelay.Expired() )
{
switch ( GetOuter()->GetTask()->iTask )
{
case TASK_WAIT_RANDOM:
case TASK_WAIT_INDEFINITE:
case TASK_WAIT:
case TASK_WAIT_FACE_ENEMY:
case TASK_WAIT_FACE_ENEMY_RANDOM:
{
m_TargetMonitor.ClearMark();
if ( !HasCondition(COND_FOLLOW_PLAYER_IS_NOT_LIT) )
{
SetCondition( COND_TARGET_MOVED_FROM_MARK );
}
}
}
}
}
#if 0
else if ( !IsFollowPointInRange() )
{
GetHintNode()->Unlock();
SetHintNode( NULL );
}
#endif
#ifdef HL2_EPISODIC
// Let followers know if the player is lit in the darkness
if ( GetFollowTarget()->IsPlayer() && HL2GameRules()->IsAlyxInDarknessMode() )
{
if ( LookerCouldSeeTargetInDarkness( GetOuter(), GetFollowTarget() ) )
{
SetCondition( COND_FOLLOW_PLAYER_IS_LIT );
ClearCondition( COND_FOLLOW_PLAYER_IS_NOT_LIT );
}
else
{
SetCondition( COND_FOLLOW_PLAYER_IS_NOT_LIT );
ClearCondition( COND_FOLLOW_PLAYER_IS_LIT );
}
}
#endif
// Set our follow target visibility state
if ( (GetFollowTarget()->IsPlayer() && HasCondition( COND_SEE_PLAYER )) || GetOuter()->FVisible( GetFollowTarget()) )
{
SetCondition( COND_FOLLOW_TARGET_VISIBLE );
ClearCondition( COND_FOLLOW_TARGET_NOT_VISIBLE );
m_flTimeFollowTargetVisible = gpGlobals->curtime;
}
else
{
ClearCondition( COND_FOLLOW_TARGET_VISIBLE );
SetCondition( COND_FOLLOW_TARGET_NOT_VISIBLE );
}
if ( HasFollowPoint() && ( m_flTimeFollowTargetVisible != 0 && gpGlobals->curtime - m_flTimeFollowTargetVisible > 5.0 ) )
SetCondition( COND_FOLLOW_WAIT_POINT_INVALID );
else
ClearCondition( COND_FOLLOW_WAIT_POINT_INVALID );
}
//-------------------------------------
int CAI_FollowBehavior::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
{
if ( failedTask == TASK_MOVE_TO_FOLLOW_POSITION || failedTask == TASK_GET_PATH_TO_FOLLOW_POSITION )
{
if ( m_hFollowTarget )
{
m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance * 0.5 );
m_FollowDelay.Start();
NoteFailedFollow();
}
}
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
}
//-------------------------------------
bool CAI_FollowBehavior::ShouldFollow()
{
if ( !GetFollowTarget() )
return false;
return !( GetFollowTarget()->GetFlags() & FL_NOTARGET);
}
//-------------------------------------
bool CAI_FollowBehavior::ShouldMoveToFollowTarget()
{
if( m_bTargetUnreachable )
return false;
#ifdef HL2_EPISODIC
if ( HL2GameRules()->IsAlyxInDarknessMode() )
{
// If we're in darkness mode, the player needs to be lit by
// darkness, but we don't need line of sight to him.
if ( HasCondition(COND_FOLLOW_PLAYER_IS_NOT_LIT) )
return false;
}
#endif
if ( HasFollowPoint() )
{
if ( IsFollowPointInRange() )
return false;
}
else if ( IsFollowTargetInRange() )
return false;
if( m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() && !HasCondition( COND_TARGET_MOVED_FROM_MARK ) )
return false;
return true;
}
//-------------------------------------
int CAI_FollowBehavior::SelectScheduleManagePosition()
{
if ( PlayerIsPushing() )
return SCHED_MOVE_AWAY;
if ( !UpdateFollowPosition() )
return SCHED_FAIL;
return SCHED_NONE;
}
//-------------------------------------
bool CAI_FollowBehavior::ShouldUseFollowPoints()
{
if ( !ai_follow_use_points.GetBool() || GetEnemy() != NULL )
return false;
return true;
}
//-------------------------------------
bool CAI_FollowBehavior::HasFollowPoint()
{
return ( GetHintNode() && GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT );
}
//-------------------------------------
void CAI_FollowBehavior::ClearFollowPoint()
{
if ( GetHintNode() && GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT )
{
GetHintNode()->Unlock();
SetHintNode( NULL );
}
}
//-------------------------------------
const Vector &CAI_FollowBehavior::GetFollowPoint()
{
static Vector invalid = vec3_invalid;
if ( GetHintNode() && GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT )
return GetHintNode()->GetAbsOrigin();
return invalid;
}
//-------------------------------------
CAI_Hint *CAI_FollowBehavior::FindFollowPoint()
{
if ( !m_TimeBlockUseWaitPoint.Expired() )
return NULL;
CHintCriteria hintCriteria;
hintCriteria.SetHintType( HINT_FOLLOW_WAIT_POINT );
hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE | bits_HINT_NODE_NEAREST );
// Add the search position
hintCriteria.AddIncludePosition( GetGoalPosition(), MAX( m_FollowNavGoal.followPointTolerance, GetGoalRange() ) );
hintCriteria.AddExcludePosition( GetGoalPosition(), (GetFollowTarget()->WorldAlignMins().AsVector2D() - GetFollowTarget()->WorldAlignMaxs().AsVector2D()).Length());
return CAI_HintManager::FindHint( GetOuter(), hintCriteria );
}
//-------------------------------------
bool CAI_FollowBehavior::IsFollowPointInRange()
{
return ( GetHintNode() &&
GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT &&
(GetHintNode()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() < Square(MAX(m_FollowNavGoal.followPointTolerance, GetGoalRange())) );
}
//-------------------------------------
bool CAI_FollowBehavior::ShouldIgnoreFollowPointFacing()
{
if ( !GetHintNode() )
return true;
HintIgnoreFacing_t hintSetting = GetHintNode()->GetIgnoreFacing();
if ( hintSetting == HIF_DEFAULT )
return ( GetHintNode()->HintActivityName() == NULL_STRING );
return ( hintSetting == HIF_YES );
}
//-------------------------------------
void CAI_FollowBehavior::SetFollowPoint( CAI_Hint *pHintNode )
{
if ( !pHintNode )
return;
Assert( pHintNode->HintType() == HINT_FOLLOW_WAIT_POINT );
if ( GetHintNode() == pHintNode )
return;
if ( GetHintNode() )
GetHintNode()->Unlock();
if ( !pHintNode->Lock( GetOuter() ) )
{
SetHintNode( NULL );
m_TimeBlockUseWaitPoint.Reset();
}
else
SetHintNode( pHintNode );
}
//-------------------------------------
int CAI_FollowBehavior::SelectScheduleFollowPoints()
{
bool bShouldUseFollowPoints = ( ShouldUseFollowPoints() && IsFollowGoalInRange( m_FollowNavGoal.followPointTolerance + 0.1, GetGoalZRange(), GetGoalFlags() ) );
float distSqToPoint = FLT_MAX;
bool bHasFollowPoint = HasFollowPoint();
if ( bHasFollowPoint )
{
distSqToPoint = (GetHintNode()->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
if ( !bShouldUseFollowPoints ||
distSqToPoint > Square(2.0 * GetHullWidth()) ||
HasCondition( COND_FOLLOW_WAIT_POINT_INVALID ) )
{
GetHintNode()->Unlock();
SetHintNode( NULL );
m_TimeBlockUseWaitPoint.Reset();
bShouldUseFollowPoints = false;
}
}
if ( bShouldUseFollowPoints )
{
bool bNewHint = false;
if ( GetHintNode() && !bHasFollowPoint )
{
GetHintNode()->Unlock();
SetHintNode( NULL );
}
if (!GetHintNode())
{
bNewHint = true;
SetFollowPoint( ( m_pInterruptWaitPoint ) ? m_pInterruptWaitPoint : FindFollowPoint() );
if ( GetHintNode() )
distSqToPoint = (GetHintNode()->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
}
if ( GetHintNode() )
{
if ( bNewHint || distSqToPoint > WAIT_HINT_MIN_DIST )
return SCHED_FOLLOWER_GO_TO_WAIT_POINT;
if ( !ShouldIgnoreFollowPointFacing() )
return SCHED_FOLLOWER_STAND_AT_WAIT_POINT;
}
}
else
ClearFollowPoint();
return SCHED_NONE;
}
//-------------------------------------
int CAI_FollowBehavior::SelectScheduleMoveToFormation()
{
if( ( GetNpcState() != NPC_STATE_COMBAT && !( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ))) ||
!IsFollowGoalInRange( GetGoalRange(), GetGoalZRange(), GetGoalFlags() ) )
{
AISquadIter_t iter;
CAI_Squad *pSquad = GetOuter()->GetSquad();
if ( pSquad )
{
for ( CAI_BaseNPC *pSquadMember = pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = pSquad->GetNextMember( &iter ) )
{
if ( pSquadMember->HasCondition( COND_PLAYER_PUSHING ) )
{
return SCHED_NONE;
}
}
}
if ( ShouldMoveToFollowTarget() || m_bFirstFacing )
{
return SCHED_TARGET_FACE; // Code for "SCHED_MOVE_TO_FACE_FOLLOW_TARGET". Used by Talker clients to interject comment
}
}
return SCHED_NONE;
}
//-------------------------------------
int CAI_FollowBehavior::SelectSchedule()
{
// Allow a range attack if we need to do it
if ( hl2_episodic.GetBool() )
{
// Range attack
if ( GetOuter()->ShouldMoveAndShoot() == false && HasCondition( COND_CAN_RANGE_ATTACK1 ) )
return SCHED_RANGE_ATTACK1;
}
if ( GetFollowTarget() )
{
if ( !GetFollowTarget()->IsAlive() )
{
// UNDONE: Comment about the recently dead player here?
SetFollowTarget( NULL );
}
else if ( ShouldFollow() )
{
int result = SCHED_NONE;
result = SelectScheduleManagePosition();
if ( result != SCHED_NONE )
return result;
result = SelectScheduleFollowPoints();
if ( result != SCHED_NONE )
return result;
result = SelectScheduleMoveToFormation();
if ( result != SCHED_NONE )
return result;
if ( HasCondition ( COND_NO_PRIMARY_AMMO ) && HaveSequenceForActivity( GetOuter()->TranslateActivity( ACT_RUN_AIM ) ) )
return SCHED_HIDE_AND_RELOAD;
}
if ( PlayerIsPushing() )
return SCHED_MOVE_AWAY;
}
else
{
// Should not have landed here. Follow target ent must have been destroyed
NotifyChangeBehaviorStatus();
}
if ( HasCondition( COND_TARGET_MOVED_FROM_MARK ) )
{
m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance * 0.5 );
}
return BaseClass::SelectSchedule();
}
//-------------------------------------
int CAI_FollowBehavior::TranslateSchedule( int scheduleType )
{
switch( scheduleType )
{
case SCHED_IDLE_STAND:
{
if ( ShouldMoveToFollowTarget() && !IsFollowGoalInRange( GetGoalRange(), GetGoalZRange(), GetGoalFlags() ) )
{
return SCHED_MOVE_TO_FACE_FOLLOW_TARGET;
}
if ( HasFollowPoint() && !ShouldIgnoreFollowPointFacing() )
return SCHED_FOLLOWER_GO_TO_WAIT_POINT;
return SCHED_FOLLOWER_IDLE_STAND;
}
case SCHED_COMBAT_STAND:
case SCHED_ALERT_STAND:
{
if ( ShouldMoveToFollowTarget() && !IsFollowGoalInRange( GetGoalRange(), GetGoalZRange(), GetGoalFlags() ) )
{
return SCHED_MOVE_TO_FACE_FOLLOW_TARGET;
}
break;
}
case SCHED_TARGET_FACE:
{
if ( ShouldMoveToFollowTarget() && !IsFollowGoalInRange( GetGoalRange(), GetGoalZRange(), GetGoalFlags() ) )
{
return SCHED_MOVE_TO_FACE_FOLLOW_TARGET;
}
if ( HasFollowPoint() && !ShouldIgnoreFollowPointFacing() )
return SCHED_FOLLOWER_GO_TO_WAIT_POINT;
if ( !m_TargetMonitor.IsMarkSet() )
m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
return SCHED_FACE_FOLLOW_TARGET; // @TODO (toml 03-03-03): should select a facing sched
}
case SCHED_TARGET_CHASE:
{
return SCHED_FOLLOW;
}
case SCHED_CHASE_ENEMY:
{
if ( IsChaseGoalInRange() == false )
return SCHED_FOLLOWER_IDLE_STAND;
break;
}
case SCHED_RANGE_ATTACK1:
{
if ( GetOuter()->GetShotRegulator()->IsInRestInterval() )
return SCHED_FOLLOWER_IDLE_STAND; // @TODO (toml 07-02-03): Should do something more tactically sensible
break;
}
case SCHED_CHASE_ENEMY_FAILED:
{
if (HasMemory(bits_MEMORY_INCOVER))
{
// Make sure I don't get too far from the player
if ( GetFollowTarget() )
{
float fDist = (GetLocalOrigin() - GetFollowTarget()->GetAbsOrigin()).Length();
if (fDist > 500)
{
return SCHED_FOLLOW;
}
}
}
break;
}
case SCHED_MOVE_AWAY_FAIL:
{
return SCHED_FOLLOWER_MOVE_AWAY_FAIL;
}
case SCHED_MOVE_AWAY_END:
{
return SCHED_FOLLOWER_MOVE_AWAY_END;
}
}
return BaseClass::TranslateSchedule( scheduleType );
}
//-------------------------------------
void CAI_FollowBehavior::OnStartSchedule( int scheduleType )
{
if ( !IsRunning() && HasFollowPoint() )
{
ClearHintNode( 0.5 );
}
if ( !m_TargetMonitor.IsMarkSet() && !IsCurScheduleFollowSchedule() )
{
m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
}
}
//-------------------------------------
void CAI_FollowBehavior::GetFollowTargetViewLoc( Vector *pResult )
{
if ( !dynamic_cast<CPointEntity *>(m_hFollowTarget.Get()) )
{
trace_t tr;
Vector vecStart, vecDir;
ASSERT( m_hFollowTarget != NULL );
vecStart = m_hFollowTarget->EyePosition();
CBasePlayer *pPlayer;
pPlayer = dynamic_cast<CBasePlayer *>(m_hFollowTarget.Get());
if( pPlayer )
{
// Follow target is a player.
pPlayer->EyeVectors( &vecDir, NULL, NULL );
}
else
{
// Not a player.
m_hFollowTarget->GetVectors( &vecDir, NULL, NULL );
}
AI_TraceLOS( vecStart, vecStart + vecDir * 8192, m_hFollowTarget, &tr );
*pResult = tr.endpos;
}
else
*pResult = m_hFollowTarget->GetAbsOrigin();
}
//-------------------------------------
bool CAI_FollowBehavior::ValidateFaceTarget( Vector *pFaceTarget )
{
if ( *pFaceTarget == vec3_invalid )
{
if ( m_hFollowTarget != NULL )
{
*pFaceTarget = m_hFollowTarget->GetAbsOrigin();
}
return false;
}
Vector testPoint = *pFaceTarget - GetAbsOrigin();
testPoint.z = 0;
VectorNormalize( testPoint );
testPoint *= 48;
testPoint += GetOuter()->EyePosition();
trace_t tr;
AI_TraceLine( GetOuter()->EyePosition(), testPoint, MASK_OPAQUE, m_hFollowTarget, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction < 1.0 )
{
*pFaceTarget = m_hFollowTarget->GetAbsOrigin();
return false;
}
return true;
}
//-------------------------------------
bool CAI_FollowBehavior::FindCoverFromEnemyAtFollowTarget( float coverRadius, Vector *pResult )
{
CBaseEntity *pEntity = GetEnemy();
return GetOuter()->FindCoverPosInRadius( pEntity, m_FollowNavGoal.position, coverRadius, pResult );
}
//-------------------------------------
void CAI_FollowBehavior::StartTask( const Task_t *pTask )
{
AI_PROFILE_SCOPE( CAI_FollowBehavior_StartTask );
switch ( pTask->iTask )
{
case TASK_RANGE_ATTACK1:
BaseClass::StartTask( pTask );
break;
case TASK_GET_PATH_TO_FOLLOW_POSITION:
{
if ( !UpdateFollowPosition() )
{
TaskFail(FAIL_NO_TARGET);
}
else
{
m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
m_bMovingToCover = false;
GetOuter()->m_vInterruptSavePosition = vec3_invalid;
}
break;
}
case TASK_CANT_FOLLOW:
{
SetFollowTarget( NULL, true );
TaskComplete();
break;
}
case TASK_FOLLOWER_FACE_TACTICAL:
case TASK_FACE_FOLLOW_TARGET:
{
if ( !m_TimeBeforeSpreadFacing.Expired() )
{
m_TimeNextSpreadFacing.Reset();
}
Vector faceTarget = vec3_invalid;
bool bFollowingPoint = ( dynamic_cast<CPointEntity *>(m_hFollowTarget.Get()) != NULL );
if ( GetNpcState() == NPC_STATE_COMBAT )
{
if( gpGlobals->curtime - GetOuter()->GetEnemyLastTimeSeen() < 5.0 )
{
faceTarget = GetEnemyLKP();
}
else if ( !bFollowingPoint )
{
GetFollowTargetViewLoc( &faceTarget );
}
}
else if ( m_hFollowTarget && !bFollowingPoint )
{
if ( m_bFirstFacing && m_hFollowTarget->IsPlayer() )
{
faceTarget = m_hFollowTarget->GetAbsOrigin();
}
else if ( m_TimeNextSpreadFacing.Expired() )
{
m_TimeNextSpreadFacing.Reset();
bool bIsEpisodicVitalAlly;
#ifdef HL2_DLL
bIsEpisodicVitalAlly = (hl2_episodic.GetBool() && GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL);
#else
bIsEpisodicVitalAlly = false;
#endif//HL2_DLL
if( bIsEpisodicVitalAlly )
{
faceTarget = m_hFollowTarget->GetAbsOrigin();
}
else
{
int roll = random->RandomInt(1, 4);
if ( roll == 1 )
{
GetFollowTargetViewLoc( &faceTarget );
}
else if ( roll == 2 )
{
faceTarget = m_hFollowTarget->GetAbsOrigin();
}
else
{
// Fan out and face to cover all directions.
int count = g_AIFollowManager.CountFollowersInGroup( GetOuter() );
if( count > 0 )
{
// Slice up the directions among followers and leader. ( +1 because we count the leader!)
float flSlice = 360.0 / (count + 1);
// Add one to slots so then are 1 to N instead of 0 to N - 1.
int slot = random->RandomInt( 0, count );
QAngle angle = m_hFollowTarget->GetAbsAngles();
// split up the remaining angles among followers in my group.
angle.y = UTIL_AngleMod( angle.y + ( flSlice * slot ) );
Vector vecDir;
AngleVectors( angle, &vecDir );
faceTarget = GetOuter()->GetAbsOrigin() + vecDir * 128;
}
}
}
}
else
{
// Stay where we are
TaskComplete();
break;
}
}
m_bFirstFacing = false;
if ( ValidateFaceTarget( &faceTarget ) )
{
Assert( faceTarget != vec3_invalid );
if ( !GetOuter()->FInAimCone( faceTarget ) )
{
GetMotor()->SetIdealYawToTarget( faceTarget, 30 );
GetOuter()->SetTurnActivity();
}
else
TaskComplete();
}
else
ChainStartTask( TASK_FACE_REASONABLE );
break;
}
case TASK_MOVE_TO_FOLLOW_POSITION:
{
if ( m_hFollowTarget == NULL)
{
TaskFail(FAIL_NO_TARGET);
}
else if ( (m_hFollowTarget->GetAbsOrigin() - GetAbsOrigin()).Length() < 1 )
{
TaskComplete();
}
else if ( !GetNavigator()->IsGoalActive() )
{
TaskFail(FAIL_NO_ROUTE);
}
else
{
m_vFollowMoveAnchor = GetAbsOrigin();
m_CurrentFollowActivity = ACT_INVALID;
}
break;
}
case TASK_SET_FOLLOW_TARGET_MARK:
{
if ( m_hFollowTarget == NULL)
{
TaskFail(FAIL_NO_TARGET);
}
else
{
FollowMsg( "TASK_SET_FOLLOW_TARGET_MARK\n" );
m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
TaskComplete();
}
break;
}
case TASK_SET_FOLLOW_DELAY:
{
m_FollowDelay.Start( pTask->flTaskData );
TaskComplete();
break;
}
case TASK_FIND_COVER_FROM_ENEMY:
{
CBaseEntity *pLeader = GetFollowTarget();
if ( pLeader )
{
Vector coverPos = vec3_invalid;
float coverRadius = MIN( GetOuter()->CoverRadius(), m_FollowNavGoal.coverTolerance );
if ( FindCoverFromEnemyAtFollowTarget( coverRadius, &coverPos ) )
{
AI_NavGoal_t goal(GOALTYPE_COVER, coverPos, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS);
GetNavigator()->SetGoal( goal );
GetOuter()->m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
TaskComplete();
}
else
TaskFail(FAIL_NO_COVER);
}
else
BaseClass::StartTask( pTask );
break;
}
case TASK_GET_PATH_TO_FOLLOW_POINT:
{
ChainStartTask( TASK_GET_PATH_TO_HINTNODE, ShouldIgnoreFollowPointFacing() );
break;
}
case TASK_ARRIVE_AT_FOLLOW_POINT:
{
if ( GetHintNode() && !ShouldIgnoreFollowPointFacing() )
ChainStartTask( TASK_FACE_HINTNODE, 0 );
else
TaskComplete();
break;
}
case TASK_SET_FOLLOW_POINT_STAND_SCHEDULE:
{
if ( GetHintNode() && !ShouldIgnoreFollowPointFacing() )
{
float distSqToPoint = (GetHintNode()->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
if ( distSqToPoint < WAIT_HINT_MIN_DIST )
{
GetOuter()->SetSchedule( SCHED_FOLLOWER_STAND_AT_WAIT_POINT );
}
else
{
GetHintNode()->Unlock();
SetHintNode( NULL );
m_TimeBlockUseWaitPoint.Reset();
TaskFail("Couldn't get to wait node." );
}
}
else
{
GetOuter()->SetSchedule( SCHED_FACE_FOLLOW_TARGET );
}
break;
}
case TASK_BEGIN_STAND_AT_WAIT_POINT:
{
if ( !m_TargetMonitor.IsMarkSet() && IsFollowPointInRange() )
m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
if ( GetHintNode() && !ShouldIgnoreFollowPointFacing() )
ChainStartTask( TASK_FACE_HINTNODE, 0 );
else
TaskComplete();
break;
}
default:
BaseClass::StartTask( pTask );
}
}
//-------------------------------------
void CAI_FollowBehavior::RunTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_GET_PATH_TO_FOLLOW_POSITION:
{
switch( GetOuter()->GetTaskInterrupt() )
{
case 0:
{
if ( GetEnemy() )
{
Assert( GetOuter()->m_vInterruptSavePosition == vec3_invalid );
Vector coverPos = vec3_invalid;
float coverRadius = MIN( (float)12*12, m_FollowNavGoal.coverTolerance );
if ( FindCoverFromEnemyAtFollowTarget( coverRadius, &coverPos ) )
{
GetOuter()->m_vInterruptSavePosition = coverPos;
}
GetOuter()->TaskInterrupt();
break;
}
}
// Fall through...
case 1:
{
if ( GetOuter()->m_vInterruptSavePosition != vec3_invalid )
{
AI_NavGoal_t goal(GOALTYPE_COVER, GetOuter()->m_vInterruptSavePosition, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS);
if ( GetNavigator()->SetGoal( goal, AIN_NO_PATH_TASK_FAIL ) )
{
TaskComplete();
m_bMovingToCover = true;
}
else
{
GetOuter()->TaskInterrupt();
}
break;
}
// Fall through...
}
case 2:
{
Assert( !m_bMovingToCover );
Vector vGoalPosition;
if ( HasFollowPoint() && IsFollowPointInRange() )
vGoalPosition = GetFollowPoint();
else
vGoalPosition = GetGoalPosition();
AI_NavGoal_t goal( GetGoalPosition(), AIN_DEF_ACTIVITY, GetGoalTolerance() );
goal.pTarget = m_hFollowTarget;
if ( !GetNavigator()->SetGoal( goal ) )
{
m_FollowDelay.Start( 2.0, 5.0 );
}
}
}
break;
}
case TASK_FOLLOWER_FACE_TACTICAL:
case TASK_FACE_FOLLOW_TARGET:
{
ChainRunTask( TASK_FACE_REASONABLE );
break;
}
case TASK_MOVE_TO_FOLLOW_POSITION:
{
if ( m_hFollowTarget == NULL )
{
TaskFail(FAIL_NO_TARGET);
}
else
{
if ( m_bMovingToCover )
{
ChainRunTask( TASK_WAIT_FOR_MOVEMENT );
NoteSuccessfulFollow();
return;
}
// Re-evaluate when you think your finished, or the target has moved too far
if ( !UpdateFollowPosition() )
{
TaskFail(FAIL_NO_TARGET);
break;
}
if ( ShouldUseFollowPoints() && ai_follow_use_points_when_moving.GetBool() )
{
if ( HasFollowPoint() )
{
if ( !IsFollowPointInRange() )
{
ClearFollowPoint();
GetNavigator()->SetArrivalDirection( vec3_origin );
GetNavigator()->SetArrivalActivity( ACT_INVALID );
m_TimeBlockUseWaitPoint.Reset();
m_TimeCheckForWaitPoint.Reset();
}
}
if ( GetNavigator()->GetNavType() != NAV_JUMP && !HasFollowPoint() && m_pInterruptWaitPoint )
{
SetFollowPoint( m_pInterruptWaitPoint );
}
}
else
{
ClearFollowPoint();
if ( GetNavigator()->IsGoalActive() )
{
GetNavigator()->SetArrivalDirection( vec3_origin );
GetNavigator()->SetArrivalActivity( ACT_INVALID );
}
}
if ( !GetNavigator()->IsGoalActive() )
{
// What this probably means is that the navigation failed but within tolerance
// So for now, just call it good and block another attempt for a bit
TaskComplete();
if ( !IsFollowPointInRange() )
ClearFollowPoint();
if ( !IsFollowGoalInRange( m_FollowNavGoal.tolerance, GetGoalZRange(), GetGoalFlags() ) )
m_FollowDelay.Start( 0.25, 0.75 );
else
{
m_TargetMonitor.SetMark( GetFollowTarget(), m_FollowNavGoal.targetMoveTolerance );
m_bTargetUnreachable = false;
}
break;
}
if ( !HasFollowPoint() )
{
float range = GetGoalRange();
Vector vVelocity =- GetFollowTarget()->GetSmoothedVelocity();
bool bDoSlowdown = ( vVelocity.LengthSqr() < Square(4*12) );
if ( bDoSlowdown )
{
range += GetMotor()->MinStoppingDist(12) - 12;
}
if ( IsFollowGoalInRange( range, GetGoalZRange(), GetGoalFlags() ) )
{
m_TimeBeforeSpreadFacing.Reset();
TaskComplete();
GetNavigator()->StopMoving( !bDoSlowdown ); // Stop moving
m_TargetMonitor.SetMark( GetFollowTarget(), m_FollowNavGoal.targetMoveTolerance );
break;
}
// Update the nav goal if needed
if ( (GetNavigator()->GetGoalPos() - GetGoalPosition()).LengthSqr() > Square( m_FollowNavGoal.repathOnRouteTolerance ) )
{
if ( GetNavigator()->GetNavType() != NAV_JUMP )
{
if ( !GetNavigator()->UpdateGoalPos( GetGoalPosition() ) )
{
TaskFail(FAIL_NO_ROUTE);
m_bTargetUnreachable = true;
break;
}
NoteSuccessfulFollow();
}
}
}
else
{
const Vector &vFollowPoint = GetFollowPoint();
if ( GetNavigator()->GetGoalPos() != vFollowPoint )
{
if ( !GetNavigator()->UpdateGoalPos( vFollowPoint ) )
{
TaskFail(FAIL_NO_ROUTE);
m_bTargetUnreachable = true;
break;
}
NoteSuccessfulFollow();
if ( !ShouldIgnoreFollowPointFacing() )
GetNavigator()->SetArrivalDirection( GetHintNode()->GetDirection() );
if ( GetHintNode()->HintActivityName() != NULL_STRING )
{
Activity hintActivity = (Activity)CAI_BaseNPC::GetActivityID( STRING(GetHintNode()->HintActivityName()) );
if ( hintActivity != ACT_INVALID )
{
GetNavigator()->SetArrivalActivity( GetOuter()->GetHintActivity(GetHintNode()->HintType(), hintActivity ) );
}
else
{
int iSequence = GetOuter()->LookupSequence(STRING(GetHintNode()->HintActivityName()));
if ( iSequence != ACT_INVALID )
{
GetNavigator()->SetArrivalSequence( iSequence );
}
}
}
}
}
// Set the appropriate activity based on an overlapping range
// overlap the range to prevent oscillation
// BUGBUG: this is checking linear distance (ie. through walls) and not path distance or even visibility
// Never stop running once started
if ( m_CurrentFollowActivity != ACT_RUN )
{
float distToTargetSq = ( GetNavigator()->GetGoalPos() - GetLocalOrigin() ).Length2DSqr();
// Pick the right movement activity.
Activity followActivity = ( distToTargetSq < Square(m_FollowNavGoal.walkTolerance) && GetOuter()->GetState() != NPC_STATE_COMBAT ) ? ACT_WALK : ACT_RUN;
// If we're supposed to have LOS, run to catch up
if ( m_FollowNavGoal.flags & AIFF_REQUIRE_LOS_OUTSIDE_COMBAT )
{
if ( !GetOuter()->GetSenses()->DidSeeEntity( m_hFollowTarget ) )
{
followActivity = ACT_RUN;
}
}
if ( followActivity != m_CurrentFollowActivity )
{
m_CurrentFollowActivity = followActivity;
GetNavigator()->SetMovementActivity(followActivity);
}
}
if ( ( m_vFollowMoveAnchor - GetAbsOrigin() ).LengthSqr() > Square( 15.0 * 12.0 ) )
{
m_vFollowMoveAnchor = GetAbsOrigin();
NoteSuccessfulFollow();
}
}
break;
}
case TASK_ARRIVE_AT_FOLLOW_POINT:
{
ChainRunTask( TASK_FACE_HINTNODE, 0 );
break;
}
case TASK_BEGIN_STAND_AT_WAIT_POINT:
{
ChainRunTask( TASK_FACE_HINTNODE, 0 );
break;
}
default:
BaseClass::RunTask( pTask );
}
}
//-------------------------------------
void CAI_FollowBehavior::TaskComplete( bool fIgnoreSetFailedCondition )
{
const Task_t *pTask = GetCurTask();
if ( pTask->iTask == TASK_MOVE_TO_FOLLOW_POSITION || pTask->iTask == TASK_GET_PATH_TO_FOLLOW_POSITION )
NoteSuccessfulFollow();
BaseClass::TaskComplete( fIgnoreSetFailedCondition );
}
//-------------------------------------
void CAI_FollowBehavior::BuildScheduleTestBits()
{
BaseClass::BuildScheduleTestBits();
bool bIsTakeCover = false;
bool bIsHideAndReload = false;
bool bIsReload = false;
bool bIgnoreMovedMark = false;
if ( ( GetOuter()->ConditionInterruptsCurSchedule( COND_GIVE_WAY ) ||
GetOuter()->ConditionInterruptsCurSchedule( COND_IDLE_INTERRUPT ) ||
( bIsHideAndReload = IsCurSchedule(SCHED_HIDE_AND_RELOAD ) ) == true ||
( bIsReload = IsCurSchedule(SCHED_RELOAD ) ) == true ||
IsCurSchedule(SCHED_STANDOFF ) ||
( bIsTakeCover = IsCurSchedule(SCHED_TAKE_COVER_FROM_ENEMY ) ) == true ||
IsCurSchedule(SCHED_COMBAT_FACE ) ||
IsCurSchedule(SCHED_ALERT_FACE ) ||
IsCurSchedule(SCHED_COMBAT_STAND ) ||
IsCurSchedule(SCHED_ALERT_STAND) ) ||
IsCurSchedule(SCHED_ALERT_FACE_BESTSOUND ) )
{
#ifdef HL2_EPISODIC
if( IsCurSchedule(SCHED_RELOAD, false) && GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL )
{
// Alyx and Barney do not stop reloading because the player has moved.
// Citizens and other regular allies do.
bIgnoreMovedMark = true;
}
#endif//HL2_EPISODIC
if( !bIgnoreMovedMark )
{
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_TARGET_MOVED_FROM_MARK ) );
}
if ( !bIsTakeCover && !bIsHideAndReload && !bIsReload )
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_FOLLOW_DELAY_EXPIRED) );
}
// Add logic for NPCs not able to move and shoot
if ( hl2_episodic.GetBool() )
{
if ( IsCurScheduleFollowSchedule() && GetOuter()->ShouldMoveAndShoot() == false )
{
GetOuter()->SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
}
#ifdef HL2_EPISODIC
// In Alyx darkness mode, break on the player turning their flashlight off
if ( HL2GameRules()->IsAlyxInDarknessMode() )
{
if ( IsCurSchedule(SCHED_FOLLOW, false) || IsCurSchedule(SCHED_MOVE_TO_FACE_FOLLOW_TARGET, false) ||
IsCurSchedule(SCHED_FACE_FOLLOW_TARGET, false) )
{
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_FOLLOW_PLAYER_IS_NOT_LIT ) );
}
}
#endif // HL2_EPISODIC
}
if ( GetNpcState() == NPC_STATE_COMBAT && IsCurScheduleFollowSchedule() )
{
GetOuter()->ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
}
}
//-------------------------------------
Activity CAI_FollowBehavior::NPC_TranslateActivity( Activity activity )
{
if ( activity == ACT_IDLE && HasFollowPoint() && GetHintNode()->HintActivityName() != NULL_STRING )
{
return GetOuter()->GetHintActivity(GetHintNode()->HintType(), (Activity)CAI_BaseNPC::GetActivityID( STRING(GetHintNode()->HintActivityName()) ) );
}
return BaseClass::NPC_TranslateActivity( activity );
}
//-------------------------------------
bool CAI_FollowBehavior::IsCurScheduleFollowSchedule()
{
int curScheduleId = ( GetOuter()->GetCurSchedule() ) ? GetOuter()->GetCurSchedule()->GetId() : SCHED_NONE;
if ( curScheduleId >= GetClassScheduleIdSpace()->ScheduleLocalToGlobal( SCHED_FOLLOWER_MOVE_AWAY_FAIL ) &&
curScheduleId <= GetClassScheduleIdSpace()->ScheduleLocalToGlobal( SCHED_FOLLOWER_STAND_AT_WAIT_POINT ) )
{
return true;
}
return false;
}
//-------------------------------------
bool CAI_FollowBehavior::IsCurTaskContinuousMove()
{
const Task_t *pCurTask = GetCurTask();
if ( pCurTask && pCurTask->iTask == TASK_MOVE_TO_FOLLOW_POSITION )
return true;
return BaseClass::IsCurTaskContinuousMove();
}
//-------------------------------------
void CAI_FollowBehavior::OnMovementFailed()
{
float acceptDist = m_FollowNavGoal.range;
if ( m_FollowNavGoal.tolerance > acceptDist )
acceptDist = m_FollowNavGoal.tolerance;
if ( GetNpcState() == NPC_STATE_COMBAT )
{
if ( m_FollowNavGoal.coverTolerance > acceptDist )
acceptDist = m_FollowNavGoal.coverTolerance;
if (m_FollowNavGoal.enemyLOSTolerance > acceptDist )
acceptDist = m_FollowNavGoal.enemyLOSTolerance;
}
float flZRange = GetGoalZRange();
if ( GetGoalZRange() == -1 )
{
flZRange = GetHullHeight() * 2;
}
if ( IsFollowGoalInRange( acceptDist * 1.5, flZRange, GetGoalFlags() ) )
m_bTargetUnreachable = true;
else
m_FollowDelay.Start();
}
//-------------------------------------
void CAI_FollowBehavior::OnMovementComplete()
{
if ( !IsCurSchedule(SCHED_FOLLOWER_GO_TO_WAIT_POINT) )
m_TimeBeforeSpreadFacing.Reset();
else
{
m_TimeBeforeSpreadFacing.Force();
m_TimeNextSpreadFacing.Force();
}
}
//-------------------------------------
bool CAI_FollowBehavior::FValidateHintType( CAI_Hint *pHint )
{
if ( pHint->HintType() == HINT_FOLLOW_WAIT_POINT )
{
if ( GetFollowTarget() && GetFollowTarget()->FVisible( pHint->GetAbsOrigin() + Vector( 0, 0, 0.1 ) ) )
return true;
else
return false;
}
return BaseClass::FValidateHintType( pHint );
}
//-------------------------------------
bool CAI_FollowBehavior::IsValidCover( const Vector &vLocation, CAI_Hint const *pHint )
{
if ( (vLocation - m_FollowNavGoal.position).LengthSqr() > Square( m_FollowNavGoal.coverTolerance + 0.1 ) )
return false;
return BaseClass::IsValidCover( vLocation, pHint );
}
//-------------------------------------
bool CAI_FollowBehavior::IsValidShootPosition( const Vector &vLocation, CAI_Node *pNode, CAI_Hint const *pHint )
{
if ( (vLocation - m_FollowNavGoal.position).LengthSqr() > Square( m_FollowNavGoal.enemyLOSTolerance + 0.1 ) )
return false;
return BaseClass::IsValidShootPosition( vLocation, pNode, pHint );
}
//-------------------------------------
bool CAI_FollowBehavior::ShouldAlwaysThink()
{
return ( m_hFollowTarget && m_hFollowTarget->IsPlayer() );
}
//-----------------------------------------------------------------------------
//
// CAI_FollowGoal
//
// Purpose: A level tool to control the follow behavior. Use is not required
// in order to use behavior.
//
//-----------------------------------------------------------------------------
BEGIN_DATADESC( CAI_FollowGoal )
DEFINE_KEYFIELD( m_iFormation, FIELD_INTEGER, "Formation" ),
DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
END_DATADESC()
//-------------------------------------
LINK_ENTITY_TO_CLASS( ai_goal_follow, CAI_FollowGoal );
//-------------------------------------
void CAI_FollowGoal::EnableGoal( CAI_BaseNPC *pAI )
{
CAI_FollowBehavior *pBehavior;
if ( !pAI->GetBehavior( &pBehavior ) )
return;
CBaseEntity *pGoalEntity = GetGoalEntity();
if ( !pGoalEntity && AI_IsSinglePlayer() )
{
if ( pAI->IRelationType(UTIL_GetLocalPlayer()) == D_LI )
{
pGoalEntity = UTIL_GetLocalPlayer();
SetGoalEntity( pGoalEntity );
}
}
if ( pGoalEntity )
pBehavior->SetFollowGoal( this );
}
//-------------------------------------
void CAI_FollowGoal::DisableGoal( CAI_BaseNPC *pAI )
{
CAI_FollowBehavior *pBehavior;
if ( !pAI || !pAI->GetBehavior( &pBehavior ) )
return;
pBehavior->ClearFollowGoal( this );
}
//-------------------------------------
void CAI_FollowGoal::InputOutsideTransition( inputdata_t &inputdata )
{
EnterDormant();
}
//-----------------------------------------------------------------------------
//
// CAI_FollowManager
//
//-----------------------------------------------------------------------------
//-------------------------------------
//
// Purpose: Formation definitions
//
// @TODO (toml 11-21-03): rework follow so we don't have to have class specifc formations in this file
struct AI_FollowSlot_t
{
int priority;
TableVector position;
float positionVariability;
float rangeMin;
float rangeMax;
float Zrange;
float tolerance;
// @Q (toml 02-28-03): facing?
};
struct AI_FollowFormation_t
{
const char * pszName;
unsigned flags;
int nSlots;
// Range within which can exit formation to seek a follow point
float followPointTolerance;
// Distance target must move to reset formation
float targetMoveTolerance;
// Distance from current move goal target must move to force a repathfind
float repathOnRouteTolerance;
// Distance from target within which should walk, not run to formation
float walkTolerance;
// Distance within which can exit formation to seek cover
float coverTolerance;
// Distance within which can exit formation to seek LOS to enemy
float enemyLOSTolerance;
// Distance within which can exit formation to chase enemy
float chaseEnemyTolerance;
AI_FollowSlot_t * pSlots;
};
//-------------------------------------
static AI_FollowSlot_t g_SimpleFollowFormationSlots[] =
{
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
};
static AI_FollowFormation_t g_SimpleFollowFormation =
{
"Simple",
AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
ARRAYSIZE(g_SimpleFollowFormationSlots),
168, // followPointTolerance
36, // targetMoveTolerance
60, // repathOnRouteTolerance
190, // walkTolerance
300, // coverTolerance
300, // enemyLOSTolerance
300, // chaseEnemyTolerance
g_SimpleFollowFormationSlots,
};
//-------------------------------------
static AI_FollowSlot_t g_WideFollowFormationSlots[] =
{
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
};
static AI_FollowFormation_t g_WideFollowFormation =
{
"Wide",
AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
ARRAYSIZE(g_WideFollowFormationSlots),
168, // followPointTolerance
72, // targetMoveTolerance
60, // repathOnRouteTolerance
190, // walkTolerance
600, // coverTolerance
600, // enemyLOSTolerance
600, // chaseEnemyTolerance
g_WideFollowFormationSlots,
};
//---------------------------------------------
// Antlion use very loose following criteria
static AI_FollowSlot_t g_AntlionFollowFormationSlots[] =
{
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
};
static AI_FollowFormation_t g_AntlionFollowFormation =
{
"Antlion",
AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
ARRAYSIZE(g_AntlionFollowFormationSlots),
168, // followPointTolerance
36, // targetMoveTolerance
60, // repathOnRouteTolerance
190, // walkTolerance
1024, // coverTolerance
1024, // enemyLOSTolerance
1024, // chaseEnemyTolerance
g_AntlionFollowFormationSlots,
};
//-------------------------------------
#define COMMANDER_TOLERANCE (13.0 * 1.415)
static AI_FollowSlot_t g_CommanderFollowFormationSlots[] =
{
{ 2, { 0, 0, 0 }, 0, COMMANDER_TOLERANCE, COMMANDER_TOLERANCE, -1, 48 },
{ 1, { 0, 0, 0 }, 0, COMMANDER_TOLERANCE, COMMANDER_TOLERANCE, -1, 48 },
{ 1, { 0, 0, 0 }, 0, COMMANDER_TOLERANCE, COMMANDER_TOLERANCE, -1, 48 },
{ 1, { 0, 0, 0 }, 0, COMMANDER_TOLERANCE, COMMANDER_TOLERANCE, -1, 48 },
};
static AI_FollowFormation_t g_CommanderFollowFormation =
{
"Commander",
AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
ARRAYSIZE(g_CommanderFollowFormationSlots),
168, // followPointTolerance
6, // targetMoveTolerance
60, // repathOnRouteTolerance
12, // walkTolerance
300, // coverTolerance
300, // enemyLOSTolerance
300, // chaseEnemyTolerance
g_CommanderFollowFormationSlots,
};
//-------------------------------------
static AI_FollowSlot_t g_TightFollowFormationSlots[] =
{
{ 1, { 0, 0, 0 }, 0, 0, 0, -1, 48 },
{ 1, { 0, 0, 0 }, 0, 0, 0, -1, 48 },
{ 1, { 0, 0, 0 }, 0, 0, 0, -1, 48 },
{ 1, { 0, 0, 0 }, 0, 0, 0, -1, 48 },
};
static AI_FollowFormation_t g_TightFollowFormation =
{
"Tight",
AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
ARRAYSIZE(g_CommanderFollowFormationSlots),
48, // followPointTolerance
6, // targetMoveTolerance
60, // repathOnRouteTolerance
12, // walkTolerance
300, // coverTolerance
32, // enemyLOSTolerance
32, // chaseEnemyTolerance
g_TightFollowFormationSlots,
};
//-------------------------------------
static AI_FollowSlot_t g_MediumFollowFormationSlots[] =
{
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
{ 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
};
static AI_FollowFormation_t g_MediumFollowFormation =
{
"Medium",
AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
ARRAYSIZE(g_MediumFollowFormationSlots),
168, // followPointTolerance
36, // targetMoveTolerance
60, // repathOnRouteTolerance
190, // walkTolerance
300, // coverTolerance
300, // enemyLOSTolerance
300, // chaseEnemyTolerance
g_MediumFollowFormationSlots,
};
//-------------------------------------
static AI_FollowSlot_t g_SidekickFollowFormationSlots[] =
{
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
{ 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
};
static AI_FollowFormation_t g_SidekickFollowFormation =
{
"Sidekick",
AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS | AIFF_REQUIRE_LOS_OUTSIDE_COMBAT,
ARRAYSIZE(g_SidekickFollowFormationSlots),
168, // followPointTolerance
36, // targetMoveTolerance
60, // repathOnRouteTolerance
190, // walkTolerance
300, // coverTolerance
300, // enemyLOSTolerance
300, // chaseEnemyTolerance
g_SidekickFollowFormationSlots,
};
//-------------------------------------
AI_FollowFormation_t *g_AI_Formations[] =
{
&g_SimpleFollowFormation,
&g_WideFollowFormation,
&g_AntlionFollowFormation,
&g_CommanderFollowFormation,
&g_TightFollowFormation,
&g_MediumFollowFormation,
&g_SidekickFollowFormation
};
AI_FollowFormation_t *AIGetFormation( AI_Formations_t formation )
{
if ( formation < 0 )
formation = (AI_Formations_t)0;
else if ( static_cast<size_t>(formation) >= ARRAYSIZE( g_AI_Formations ) )
formation = (AI_Formations_t)(ARRAYSIZE( g_AI_Formations ) - 1 );
return g_AI_Formations[formation];
}
//---------------------------------------------------------
bool CAI_FollowManager::AddFollower( CBaseEntity *pTarget, CAI_BaseNPC *pFollower, AI_Formations_t formation, AI_FollowManagerInfoHandle_t *pHandle )
{
AI_FollowGroup_t *pGroup = FindCreateGroup( pTarget, formation );
int slot = FindBestSlot( pGroup );
if ( slot != -1 )
{
MEM_ALLOC_CREDIT();
AI_FollowSlot_t *pSlot = &pGroup->pFormation->pSlots[slot];
int i = pGroup->followers.AddToTail( );
AI_Follower_t *iterNode = &pGroup->followers[i];
iterNode->hFollower = pFollower;
iterNode->slot = slot;
iterNode->pGroup = pGroup;
pGroup->slotUsage.SetBit( slot );
CalculateFieldsFromSlot( pSlot, &iterNode->navInfo );
pHandle->m_hFollower = i;
pHandle->m_pGroup = pGroup;
return true;
}
pHandle->m_hFollower = 0;
pHandle->m_pGroup = NULL;
return false;
}
//-------------------------------------
bool CAI_FollowManager::CalcFollowPosition( AI_FollowManagerInfoHandle_t& hInfo, AI_FollowNavInfo_t *pNavInfo )
{
if ( hInfo.m_pGroup && hInfo.m_hFollower )
{
AI_FollowGroup_t *pGroup = hInfo.m_pGroup;
Assert( pGroup->hFollowTarget.Get() );
CBaseEntity *pTarget = pGroup->hFollowTarget;
AI_Follower_t *iterNode = &pGroup->followers[hInfo.m_hFollower];
if ( iterNode->navInfo.position != vec3_origin )
{
QAngle angles = pTarget->GetLocalAngles();
angles.x = angles.z = 0;
matrix3x4_t fRotateMatrix;
AngleMatrix(angles, fRotateMatrix);
VectorRotate( iterNode->navInfo.position, fRotateMatrix, pNavInfo->position);
pNavInfo->position += pTarget->WorldSpaceCenter();
}
else
{
pNavInfo->position = iterNode->navInfo.position + pTarget->WorldSpaceCenter();
}
pNavInfo->tolerance = iterNode->navInfo.tolerance;
pNavInfo->range = iterNode->navInfo.range;
pNavInfo->Zrange = iterNode->navInfo.Zrange;
pNavInfo->flags = pGroup->pFormation->flags;
pNavInfo->followPointTolerance = pGroup->pFormation->followPointTolerance;
pNavInfo->targetMoveTolerance = pGroup->pFormation->targetMoveTolerance;
pNavInfo->repathOnRouteTolerance = pGroup->pFormation->repathOnRouteTolerance;
pNavInfo->walkTolerance = pGroup->pFormation->walkTolerance;
pNavInfo->coverTolerance = pGroup->pFormation->coverTolerance;
pNavInfo->enemyLOSTolerance = pGroup->pFormation->enemyLOSTolerance;
pNavInfo->chaseEnemyTolerance = pGroup->pFormation->chaseEnemyTolerance;
return true;
}
return false;
}
//-------------------------------------
bool CAI_FollowManager::RedistributeSlots( AI_FollowGroup_t *pGroup )
{
bool result = false;
CUtlRBTree<CBaseEntity *> movedFollowers;
SetDefLessFunc( movedFollowers );
const Vector &originFollowed = pGroup->hFollowTarget->GetAbsOrigin();
int bestSlot;
while ( ( bestSlot = FindBestSlot( pGroup ) ) != -1 && ((int)movedFollowers.Count() < pGroup->followers.Count()) )
{
AI_FollowSlot_t * pSlot = &pGroup->pFormation->pSlots[bestSlot];
Vector slotPos = originFollowed + pSlot->position;
int h = pGroup->followers.Head();
int hBest = pGroup->followers.InvalidIndex();
float distSqBest = FLT_MAX;
while ( h != pGroup->followers.InvalidIndex() )
{
AI_Follower_t *p = &pGroup->followers[h];
if ( movedFollowers.Find( p->hFollower ) == movedFollowers.InvalidIndex() &&
( p->slot == -1 || pSlot->priority > pGroup->pFormation->pSlots[p->slot].priority ) )
{
float distSqCur = ( p->hFollower->GetAbsOrigin() - slotPos ).LengthSqr();
if ( distSqCur < distSqBest )
{
hBest = h;
}
}
h = pGroup->followers.Next( h );
}
if ( hBest == pGroup->followers.InvalidIndex() )
break;
AI_Follower_t *pBest = &pGroup->followers[hBest];
if ( pBest->slot != -1 )
{
pGroup->slotUsage.ClearBit( pBest->slot );
}
pBest->slot = bestSlot;
CalculateFieldsFromSlot( pSlot, &pBest->navInfo );
pGroup->slotUsage.SetBit( bestSlot );
movedFollowers.Insert( pBest->hFollower );
result = true;
}
return result;
}
//-------------------------------------
void CAI_FollowManager::ChangeFormation( AI_FollowManagerInfoHandle_t& hInfo, AI_Formations_t formation )
{
if ( !hInfo.m_pGroup || !hInfo.m_hFollower )
return;
AI_FollowGroup_t *pGroup = hInfo.m_pGroup;
AI_FollowFormation_t *pNewFormation = AIGetFormation( formation );
if ( pNewFormation == pGroup->pFormation )
return;
int h = pGroup->followers.Head();
while ( h != pGroup->followers.InvalidIndex() )
{
CAI_FollowBehavior *pFollowBehavior;
AI_Follower_t *p = &pGroup->followers[h];
p->slot = -1;
p->hFollower->GetBehavior( &pFollowBehavior );
Assert( pFollowBehavior );
if ( pFollowBehavior )
{
pFollowBehavior->m_params.formation = formation;
pFollowBehavior->m_TargetMonitor.ClearMark();
pFollowBehavior->SetCondition( CAI_FollowBehavior::COND_TARGET_MOVED_FROM_MARK );
pFollowBehavior->m_bTargetUnreachable = false;
}
h = pGroup->followers.Next( h );
}
pGroup->slotUsage.ClearAllBits();
pGroup->pFormation = pNewFormation;
pGroup->slotUsage.Resize( pGroup->pFormation->nSlots );
RedistributeSlots( pGroup );
#ifdef DEBUG
h = pGroup->followers.Head();
while ( h != pGroup->followers.InvalidIndex() )
{
AI_Follower_t *p = &pGroup->followers[h];
Assert( p->slot != -1 );
h = pGroup->followers.Next( h );
}
#endif
}
//-------------------------------------
void CAI_FollowManager::RemoveFollower( AI_FollowManagerInfoHandle_t& hInfo )
{
if ( hInfo.m_pGroup && hInfo.m_hFollower )
{
AI_FollowGroup_t *pGroup = hInfo.m_pGroup;
AI_Follower_t* iterNode = &pGroup->followers[hInfo.m_hFollower];
int slot = iterNode->slot;
pGroup->slotUsage.ClearBit( slot );
pGroup->followers.Remove( hInfo.m_hFollower );
if ( pGroup->followers.Count() == 0 )
{
RemoveGroup( pGroup );
}
else
{
if ( pGroup->hFollowTarget != NULL ) // NULL on level unload
{
RedistributeSlots( pGroup );
}
}
}
}
//-------------------------------------
int CAI_FollowManager::FindBestSlot( AI_FollowGroup_t *pGroup )
{
// @TODO (toml 02-28-03): crude placeholder
int nSlots = pGroup->pFormation->nSlots;
int best = -1;
int bestPriority = -1;
for ( int i = 0; i < nSlots; i++ )
{
if ( !pGroup->slotUsage.GetBit( i ) && pGroup->pFormation->pSlots[i].priority > bestPriority )
{
bestPriority = pGroup->pFormation->pSlots[i].priority;
best = i;
}
}
return best;
}
//-------------------------------------
void CAI_FollowManager::CalculateFieldsFromSlot( AI_FollowSlot_t *pSlot, AI_FollowNavInfo_t *pFollowerInfo )
{
// @TODO (toml 02-28-03): placeholder. Force break if someone tries to actually use
Assert( pSlot->positionVariability == 0.0 );
//Assert( pSlot->tolerance == AIN_DEF_TOLERANCE );
pFollowerInfo->position = pSlot->position;
pFollowerInfo->range = random->RandomFloat( pSlot->rangeMin, pSlot->rangeMax );
pFollowerInfo->Zrange = pSlot->Zrange;
pFollowerInfo->tolerance = pSlot->tolerance;
}
//-------------------------------------
AI_FollowGroup_t *CAI_FollowManager::FindCreateGroup( CBaseEntity *pTarget, AI_Formations_t formation )
{
AI_FollowGroup_t *pGroup = FindGroup( pTarget );
if ( !pGroup )
{
{
MEM_ALLOC_CREDIT();
pGroup = new AI_FollowGroup_t;
}
pGroup->pFormation = AIGetFormation( formation );
pGroup->slotUsage.Resize( pGroup->pFormation->nSlots );
pGroup->hFollowTarget = pTarget;
m_groups.AddToHead( pGroup );
}
return pGroup;
}
//-------------------------------------
void CAI_FollowManager::RemoveGroup( AI_FollowGroup_t *pGroup )
{
for ( int i = 0; i < m_groups.Count(); i++ )
{
if ( m_groups[i] == pGroup )
{
delete m_groups[i];
m_groups.FastRemove(i);
return;
}
}
}
//-------------------------------------
AI_FollowGroup_t *CAI_FollowManager::FindGroup( CBaseEntity *pTarget )
{
for ( int i = 0; i < m_groups.Count(); i++ )
{
if ( m_groups[i]->hFollowTarget == pTarget )
return m_groups[i];
}
return NULL;
}
//-------------------------------------
AI_FollowGroup_t *CAI_FollowManager::FindFollowerGroup( CBaseEntity *pFollower )
{
for ( int i = 0; i < m_groups.Count(); i++ )
{
int h = m_groups[i]->followers.Head();
while( h != m_groups[i]->followers.InvalidIndex() )
{
AI_Follower_t *p = &m_groups[i]->followers[h];
if ( p->hFollower.Get() == pFollower )
return m_groups[i];
h = m_groups[i]->followers.Next( h );
}
}
return NULL;
}
//-----------------------------------------------------------------------------
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER(CAI_FollowBehavior)
DECLARE_TASK(TASK_CANT_FOLLOW)
DECLARE_TASK(TASK_FACE_FOLLOW_TARGET)
DECLARE_TASK(TASK_MOVE_TO_FOLLOW_POSITION)
DECLARE_TASK(TASK_GET_PATH_TO_FOLLOW_POSITION)
DECLARE_TASK(TASK_SET_FOLLOW_TARGET_MARK)
DECLARE_TASK(TASK_FOLLOWER_FACE_TACTICAL)
DECLARE_TASK(TASK_SET_FOLLOW_DELAY)
DECLARE_TASK(TASK_GET_PATH_TO_FOLLOW_POINT)
DECLARE_TASK(TASK_ARRIVE_AT_FOLLOW_POINT)
DECLARE_TASK(TASK_BEGIN_STAND_AT_WAIT_POINT)
DECLARE_TASK(TASK_SET_FOLLOW_POINT_STAND_SCHEDULE)
DECLARE_CONDITION(COND_TARGET_MOVED_FROM_MARK)
DECLARE_CONDITION(COND_FOUND_WAIT_POINT)
DECLARE_CONDITION(COND_FOLLOW_DELAY_EXPIRED)
DECLARE_CONDITION(COND_FOLLOW_TARGET_VISIBLE)
DECLARE_CONDITION(COND_FOLLOW_TARGET_NOT_VISIBLE)
DECLARE_CONDITION(COND_FOLLOW_WAIT_POINT_INVALID)
DECLARE_CONDITION(COND_FOLLOW_PLAYER_IS_LIT)
DECLARE_CONDITION(COND_FOLLOW_PLAYER_IS_NOT_LIT)
//=========================================================
// > SCHED_FOLLOWER_MOVE_AWAY_END
//=========================================================
DEFINE_SCHEDULE
(
SCHED_FOLLOWER_MOVE_AWAY_END,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FOLLOWER_MOVE_AWAY_FAIL "
" TASK_STOP_MOVING 0"
" TASK_FACE_FOLLOW_TARGET 0"
" TASK_SET_FOLLOW_DELAY 2"
""
" Interrupts"
" COND_PLAYER_PUSHING"
)
//=========================================================
// > SCHED_FOLLOWER_MOVE_AWAY_FAIL
//=========================================================
DEFINE_SCHEDULE
(
SCHED_FOLLOWER_MOVE_AWAY_FAIL,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_FOLLOW_TARGET 0"
" TASK_SET_FOLLOW_DELAY 2"
""
" Interrupts"
" COND_PLAYER_PUSHING"
)
//=========================================================
// > SCHED_FOLLOW
//=========================================================
DEFINE_SCHEDULE
(
SCHED_FOLLOW,
" Tasks"
" TASK_GET_PATH_TO_FOLLOW_POSITION 0"
" TASK_MOVE_TO_FOLLOW_POSITION 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_TARGET_FACE "
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_HEAR_DANGER"
" COND_PROVOKED"
" COND_PLAYER_PUSHING"
" COND_BETTER_WEAPON_AVAILABLE"
);
//=========================================================
// > SCHED_MOVE_TO_FACE_FOLLOW_TARGET
//=========================================================
DEFINE_SCHEDULE
(
SCHED_MOVE_TO_FACE_FOLLOW_TARGET,
" Tasks"
// " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
// " TASK_FACE_FOLLOW_TARGET 0"
// " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_FOLLOW"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_HEAR_DANGER"
" COND_PROVOKED"
" COND_PLAYER_PUSHING"
)
//=========================================================
// > SCHED_FACE_FOLLOW_TARGET
//=========================================================
DEFINE_SCHEDULE
(
SCHED_FACE_FOLLOW_TARGET,
" Tasks"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
" TASK_FACE_FOLLOW_TARGET 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_FOLLOWER_IDLE_STAND "
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_HEAR_DANGER"
" COND_PROVOKED"
" COND_PLAYER_PUSHING"
" COND_GIVE_WAY"
)
//=========================================================
// > SCHED_FOLLOWER_GO_TO_WAIT_POINT
//=========================================================
DEFINE_SCHEDULE
(
SCHED_FOLLOWER_GO_TO_WAIT_POINT,
" Tasks"
" TASK_LOCK_HINTNODE 0 " // this will fail the schedule if no hint node or not already lockable
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL"
" TASK_SET_TOLERANCE_DISTANCE 4"
" TASK_GET_PATH_TO_FOLLOW_POINT 0"
" TASK_SET_FOLLOW_TARGET_MARK 0"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_ARRIVE_AT_FOLLOW_POINT 0"
" TASK_SET_FOLLOW_POINT_STAND_SCHEDULE 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_HEAR_DANGER"
" COND_PROVOKED"
" COND_PLAYER_PUSHING"
" COND_TARGET_MOVED_FROM_MARK"
)
//=========================================================
// > SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL
//=========================================================
DEFINE_SCHEDULE
(
SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL,
" Tasks"
" TASK_CLEAR_HINTNODE .5"
" TASK_SET_FOLLOW_DELAY 1"
""
" Interrupts"
)
//=========================================================
// > SCHED_FOLLOWER_STAND_AT_WAIT_POINT
//=========================================================
DEFINE_SCHEDULE
(
SCHED_FOLLOWER_STAND_AT_WAIT_POINT,
" Tasks"
" TASK_BEGIN_STAND_AT_WAIT_POINT 0"
" TASK_PLAY_HINT_ACTIVITY 0"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_FOLLOWER_STAND_AT_WAIT_POINT "
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_HEAR_DANGER"
" COND_PROVOKED"
" COND_PLAYER_PUSHING"
" COND_TARGET_MOVED_FROM_MARK"
" COND_GIVE_WAY"
" COND_FOLLOW_WAIT_POINT_INVALID"
// " COND_IDLE_INTERRUPT"
)
DEFINE_SCHEDULE
(
SCHED_FOLLOWER_IDLE_STAND,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
// " TASK_SET_FOLLOW_TARGET_MARK 0"
" TASK_WAIT 2.5"
" TASK_FACE_FOLLOW_TARGET 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
" TASK_WAIT 3"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_SEE_FEAR"
" COND_CAN_RANGE_ATTACK1"
" COND_NO_PRIMARY_AMMO"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_SMELL"
" COND_PROVOKED"
" COND_GIVE_WAY"
" COND_HEAR_DANGER"
" COND_HEAR_COMBAT"
" COND_HEAR_BULLET_IMPACT"
" COND_PLAYER_PUSHING"
" COND_TARGET_MOVED_FROM_MARK"
" COND_FOLLOW_DELAY_EXPIRED"
" COND_FOUND_WAIT_POINT"
" COND_IDLE_INTERRUPT"
" COND_BETTER_WEAPON_AVAILABLE"
)
AI_END_CUSTOM_SCHEDULE_PROVIDER()
//=============================================================================