diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_behavior_follow.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_behavior_follow.cpp')
| -rw-r--r-- | mp/src/game/server/ai_behavior_follow.cpp | 6246 |
1 files changed, 3123 insertions, 3123 deletions
diff --git a/mp/src/game/server/ai_behavior_follow.cpp b/mp/src/game/server/ai_behavior_follow.cpp index 44e67028..4c77972c 100644 --- a/mp/src/game/server/ai_behavior_follow.cpp +++ b/mp/src/game/server/ai_behavior_follow.cpp @@ -1,3123 +1,3123 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "tier1/utllinkedlist.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;
- CVarBitVec 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 CountFollowers( CBaseEntity *pFollowTarget, string_t iszClassname )
- {
- AI_FollowGroup_t *pGroup = FindGroup( pFollowTarget );
-
- if( !pGroup )
- {
- return 0;
- }
-
- if ( iszClassname == NULL_STRING )
- {
- return pGroup->followers.Count();
- }
- else
- {
- int result = 0;
- for ( int i = pGroup->followers.Head(); i != pGroup->followers.InvalidIndex(); i = pGroup->followers.Next( i ) )
- {
- if ( pGroup->followers[i].hFollower && pGroup->followers[i].hFollower->ClassMatches( iszClassname ) )
- {
- result++;
- }
- }
- return result;
- }
- }
-
- 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;
-
-//-----------------------------------------------------------------------------
-
-int AIGetNumFollowers( CBaseEntity *pEntity, string_t iszClassname )
-{
- return g_AIFollowManager.CountFollowers( pEntity, iszClassname );
-}
-
-//-----------------------------------------------------------------------------
-//
-// CAI_FollowBehavior
-//
-//-----------------------------------------------------------------------------
-
-BEGIN_SIMPLE_DATADESC( AI_FollowNavInfo_t )
- DEFINE_FIELD( flags, FIELD_INTEGER ),
- DEFINE_FIELD( position, FIELD_POSITION_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 ),
- DEFINE_FIELD( bNormalMemoryDiscard, FIELD_BOOLEAN ),
-
-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_bFollowNavFailed, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bMovingToCover, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flOriginalEnemyDiscardTime, FIELD_FLOAT ),
- DEFINE_FIELD( m_SavedDistTooFar, FIELD_FLOAT ),
- DEFINE_EMBEDDED( m_FollowDelay ),
- DEFINE_EMBEDDED( m_RepathOnFollowTimer ),
- 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 ¶ms )
-{
- 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::DrawDebugGeometryOverlays()
-{
- if ( GetFollowTarget() )
- {
- Vector vecFollowPos = GetGoalPosition();
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 );
- }
-}
-
-
-//-------------------------------------
-
-void CAI_FollowBehavior::SetParameters( const AI_FollowParams_t ¶ms )
-{
- 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;
-
- if ( !GetOuter()->IsAlive() )
- {
- 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 || pGoal->m_iFormation == AIF_VORTIGAUNT );
- 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;
- }
-
- CBaseEntity *pFollowTarget = GetFollowTarget();
-
- if ( pFollowTarget->GetParent() )
- {
- if ( pFollowTarget->GetParent()->GetServerVehicle() )
- {
- m_FollowNavGoal.targetMoveTolerance *= 1.5;
- m_FollowNavGoal.range += pFollowTarget->GetParent()->BoundingRadius() * 0.333;
- }
- }
-
-#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;
-
- if ( !ShouldFollow() )
- {
- return false;
- }
-
- return true;
-}
-
-//-------------------------------------
-
-bool CAI_FollowBehavior::PlayerIsPushing()
-{
- return (m_hFollowTarget && m_hFollowTarget->IsPlayer() && HasCondition( COND_PLAYER_PUSHING ) );
-}
-
-//-------------------------------------
-
-bool CAI_FollowBehavior::IsFollowTargetInRange( float rangeMultiplier )
-{
- if ( !GetFollowTarget()->IsPlayer() && HasCondition( COND_RECEIVED_ORDERS ) )
- return false;
-
- if( GetNpcState() == NPC_STATE_COMBAT )
- {
- if( IsFollowGoalInRange( MAX( m_FollowNavGoal.coverTolerance, m_FollowNavGoal.enemyLOSTolerance ) * rangeMultiplier, GetGoalZRange(), GetGoalFlags() ) )
- {
- return true;
- }
- }
- else
- {
- if( IsFollowGoalInRange( MAX( m_FollowNavGoal.tolerance, GetGoalRange() ) * rangeMultiplier, 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.f, 1.f );
- 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();
-
- if ( !m_params.bNormalMemoryDiscard )
- {
- // Forget about enemies that I haven't seen for >5 seconds
- m_flOriginalEnemyDiscardTime = GetOuter()->GetEnemies()->GetEnemyDiscardTime();
- GetOuter()->GetEnemies()->SetEnemyDiscardTime( 5.0f );
- }
-
- m_SavedDistTooFar = GetOuter()->m_flDistTooFar;
- if ( GetFollowTarget() && GetFollowTarget()->IsPlayer() )
- {
- GetOuter()->m_flDistTooFar = FLT_MAX;
- }
-
- BaseClass::BeginScheduleSelection();
-}
-
-//-------------------------------------
-
-void CAI_FollowBehavior::EndScheduleSelection()
-{
- if ( !m_params.bNormalMemoryDiscard )
- {
- // Restore our original enemy discard time
- GetOuter()->GetEnemies()->SetEnemyDiscardTime( m_flOriginalEnemyDiscardTime );
- }
-
- if ( m_SavedDistTooFar > 0.1 ) // backward savefile compatability
- {
- GetOuter()->m_flDistTooFar = m_SavedDistTooFar;
- }
-
- 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;
-
- if ( IsRunning() )
- {
- GetOuter()->ClearSchedule( "Follow target gone" );
- }
- 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;
-
- if ( GetFollowTarget()->GetFlags() & FL_NOTARGET )
- return false;
-
- // If we recently failed to build a follow path, wait a while to
- // give other schedules a chance to run.
- if ( m_bFollowNavFailed && m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() )
- {
- return false;
- }
-
- m_bFollowNavFailed = false;
-
- return true;
-}
-
-//-------------------------------------
-
-bool CAI_FollowBehavior::ShouldMoveToFollowTarget()
-{
- if ( GetFollowTarget() == NULL )
- return false;
-
- 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 FollowCallBaseSelectSchedule();
-}
-
-//-------------------------------------
-
-int CAI_FollowBehavior::TranslateSchedule( int scheduleType )
-{
- switch( scheduleType )
- {
- case SCHED_FOLLOWER_IDLE_STAND:
- // If we have an enemy, at least face them!
- if ( GetEnemy() )
- return SCHED_FOLLOWER_COMBAT_FACE;
-
- break;
-
- 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;
-
- // If we have an enemy, at least face them!
- if ( GetEnemy() )
- return SCHED_FOLLOWER_COMBAT_FACE;
-
- 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() || m_bFirstFacing ) && !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;
- }
-
- // SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK just tells the NPC to chase their enemy, so
- // forbid this unless the destination is acceptable within the parameters of the follow behavior.
- case SCHED_CHASE_ENEMY:
- case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
- {
- if ( IsChaseGoalInRange() == false )
- return SCHED_FOLLOWER_IDLE_STAND;
- break;
- }
-
- case SCHED_RANGE_ATTACK1:
- {
- if ( GetOuter()->GetShotRegulator()->IsInRestInterval() )
- {
- if ( GetEnemy() )
- return SCHED_FOLLOWER_COMBAT_FACE;
-
- 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_BLOCKLOS, 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;
- m_RepathOnFollowTimer.Force();
- }
- 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( vGoalPosition, AIN_DEF_ACTIVITY, GetGoalTolerance() );
- if ( !m_hFollowTarget->GetParent() || !m_hFollowTarget->GetParent()->GetServerVehicle() )
- {
- goal.pTarget = m_hFollowTarget;
- }
- else
- {
- goal.pTarget = m_hFollowTarget->GetParent();
- }
-
- bool bSuccess = true;
- if ( !GetNavigator()->SetGoal( goal, AIN_NO_PATH_TASK_FAIL ) )
- {
- const Vector &vTarget = GetFollowTarget()->WorldSpaceCenter();
- Vector vToGoal = vGoalPosition - vTarget;
- if ( vToGoal.Length2DSqr() > 6*12 )
- {
- goal.dest = vTarget + vToGoal * 0.5;
- if ( !GetNavigator()->SetGoal( goal, AIN_NO_PATH_TASK_FAIL ) )
- {
- bSuccess = false;
- m_FollowDelay.Start( 2.0, 5.0 );
- }
- }
- else
- {
- bSuccess = false;
- m_FollowDelay.Start( 2.0, 5.0 );
- }
- }
-
- if ( !bSuccess )
- {
- m_bFollowNavFailed = true;
- TaskFail( FAIL_NO_ROUTE );
- }
- else
- {
- TaskComplete();
- }
- }
- }
-
- 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 ( m_RepathOnFollowTimer.Expired() )
- {
- if ( (GetNavigator()->GetGoalPos() - GetGoalPosition()).LengthSqr() > Square( m_FollowNavGoal.repathOnRouteTolerance ) )
- {
- if ( GetNavigator()->GetNavType() != NAV_JUMP )
- {
- m_RepathOnFollowTimer.Set( .5 );
- if ( !GetNavigator()->UpdateGoalPos( GetGoalPosition() ) )
- {
- bool bSuccess = false;
- const Vector &vTarget = GetFollowTarget()->WorldSpaceCenter();
- Vector vToGoal = GetGoalPosition() - vTarget;
- if ( vToGoal.Length2DSqr() > 6*12 )
- {
- if ( GetNavigator()->UpdateGoalPos( vTarget + vToGoal * 0.5 ) )
- {
- bSuccess = true;
- }
- }
-
- if ( !bSuccess )
- {
- 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" ),
-
-#ifdef HL2_EPISODIC
- DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
-#endif
-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 );
-}
-
-//-------------------------------------
-
-#ifdef HL2_EPISODIC
-void CAI_FollowGoal::InputOutsideTransition( inputdata_t &inputdata )
-{
- EnterDormant();
-}
-#endif
-
-//-----------------------------------------------------------------------------
-//
-// 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,
-};
-
-
-//-------------------------------------
-// Used for hunters following striders
-//-------------------------------------
-static AI_FollowSlot_t g_HunterFollowFormationSlots[] =
-{
- { 3, { 480, -240, -400 }, 0, 48, 64, 1000, 60 },
- { 3, { 480, 240, -400 }, 0, 48, 64, 1000, 60 },
- { 2, { 480, 0, -400 }, 0, 48, 64, 1000, 60 },
- { 1, { -240, 0, -400 }, 0, 48, 64, 1000, 60 },
-};
-
-static AI_FollowFormation_t g_HunterFollowFormation =
-{
- "Hunter",
- AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
- ARRAYSIZE(g_HunterFollowFormationSlots),
- 48, // followPointTolerance
- 48, // targetMoveTolerance
- 60,//180, // repathOnRouteTolerance
- 0, // walkTolerance
- 960, // coverTolerance
- 960, // enemyLOSTolerance
- 1920, // chaseEnemyTolerance
- g_HunterFollowFormationSlots,
-};
-
-
-//-------------------------------------
-
-static AI_FollowSlot_t g_VortigauntFollowFormationSlots[] =
-{
- { 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_VortigauntFollowFormation =
-{
- "Vortigaunt",
- AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS | AIFF_REQUIRE_LOS_OUTSIDE_COMBAT,
- ARRAYSIZE(g_VortigauntFollowFormationSlots),
- 168, // followPointTolerance
- 36, // targetMoveTolerance
- 60, // repathOnRouteTolerance
- 190, // walkTolerance
- 300, // coverTolerance
- (50*12), // enemyLOSTolerance
- (50*12), // chaseEnemyTolerance
- g_VortigauntFollowFormationSlots,
-};
-
-
-//-----------------------------------------------------------------------------
-// NOTE: these must correspond with the AI_Formations_t enumeration in AI_Behavior_Follow.h!!
-//-----------------------------------------------------------------------------
-AI_FollowFormation_t *g_AI_Formations[] =
-{
- &g_SimpleFollowFormation,
- &g_WideFollowFormation,
- &g_AntlionFollowFormation,
- &g_CommanderFollowFormation,
- &g_TightFollowFormation,
- &g_MediumFollowFormation,
- &g_SidekickFollowFormation,
- &g_HunterFollowFormation,
- &g_VortigauntFollowFormation,
-};
-
-AI_FollowFormation_t *AIGetFormation( AI_Formations_t formation )
-{
- if ( formation < 0 )
- formation = (AI_Formations_t)0;
- else if ( 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.Set( 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.Clear( pBest->slot );
- }
- pBest->slot = bestSlot;
- CalculateFieldsFromSlot( pSlot, &pBest->navInfo );
- pGroup->slotUsage.Set( 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.ClearAll();
- 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.Clear( 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.IsBitSet( 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"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_FOLLOWER_COMBAT_FACE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_SEE_FEAR"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " 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_BETTER_WEAPON_AVAILABLE"
- )
-
- AI_END_CUSTOM_SCHEDULE_PROVIDER()
-
-//=============================================================================
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tier1/utllinkedlist.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; + CVarBitVec 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 CountFollowers( CBaseEntity *pFollowTarget, string_t iszClassname ) + { + AI_FollowGroup_t *pGroup = FindGroup( pFollowTarget ); + + if( !pGroup ) + { + return 0; + } + + if ( iszClassname == NULL_STRING ) + { + return pGroup->followers.Count(); + } + else + { + int result = 0; + for ( int i = pGroup->followers.Head(); i != pGroup->followers.InvalidIndex(); i = pGroup->followers.Next( i ) ) + { + if ( pGroup->followers[i].hFollower && pGroup->followers[i].hFollower->ClassMatches( iszClassname ) ) + { + result++; + } + } + return result; + } + } + + 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; + +//----------------------------------------------------------------------------- + +int AIGetNumFollowers( CBaseEntity *pEntity, string_t iszClassname ) +{ + return g_AIFollowManager.CountFollowers( pEntity, iszClassname ); +} + +//----------------------------------------------------------------------------- +// +// CAI_FollowBehavior +// +//----------------------------------------------------------------------------- + +BEGIN_SIMPLE_DATADESC( AI_FollowNavInfo_t ) + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( position, FIELD_POSITION_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 ), + DEFINE_FIELD( bNormalMemoryDiscard, FIELD_BOOLEAN ), + +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_bFollowNavFailed, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bMovingToCover, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flOriginalEnemyDiscardTime, FIELD_FLOAT ), + DEFINE_FIELD( m_SavedDistTooFar, FIELD_FLOAT ), + DEFINE_EMBEDDED( m_FollowDelay ), + DEFINE_EMBEDDED( m_RepathOnFollowTimer ), + 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 ¶ms ) +{ + 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::DrawDebugGeometryOverlays() +{ + if ( GetFollowTarget() ) + { + Vector vecFollowPos = GetGoalPosition(); + NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 ); + } +} + + +//------------------------------------- + +void CAI_FollowBehavior::SetParameters( const AI_FollowParams_t ¶ms ) +{ + 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; + + if ( !GetOuter()->IsAlive() ) + { + 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 || pGoal->m_iFormation == AIF_VORTIGAUNT ); + 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; + } + + CBaseEntity *pFollowTarget = GetFollowTarget(); + + if ( pFollowTarget->GetParent() ) + { + if ( pFollowTarget->GetParent()->GetServerVehicle() ) + { + m_FollowNavGoal.targetMoveTolerance *= 1.5; + m_FollowNavGoal.range += pFollowTarget->GetParent()->BoundingRadius() * 0.333; + } + } + +#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; + + if ( !ShouldFollow() ) + { + return false; + } + + return true; +} + +//------------------------------------- + +bool CAI_FollowBehavior::PlayerIsPushing() +{ + return (m_hFollowTarget && m_hFollowTarget->IsPlayer() && HasCondition( COND_PLAYER_PUSHING ) ); +} + +//------------------------------------- + +bool CAI_FollowBehavior::IsFollowTargetInRange( float rangeMultiplier ) +{ + if ( !GetFollowTarget()->IsPlayer() && HasCondition( COND_RECEIVED_ORDERS ) ) + return false; + + if( GetNpcState() == NPC_STATE_COMBAT ) + { + if( IsFollowGoalInRange( MAX( m_FollowNavGoal.coverTolerance, m_FollowNavGoal.enemyLOSTolerance ) * rangeMultiplier, GetGoalZRange(), GetGoalFlags() ) ) + { + return true; + } + } + else + { + if( IsFollowGoalInRange( MAX( m_FollowNavGoal.tolerance, GetGoalRange() ) * rangeMultiplier, 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.f, 1.f ); + 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(); + + if ( !m_params.bNormalMemoryDiscard ) + { + // Forget about enemies that I haven't seen for >5 seconds + m_flOriginalEnemyDiscardTime = GetOuter()->GetEnemies()->GetEnemyDiscardTime(); + GetOuter()->GetEnemies()->SetEnemyDiscardTime( 5.0f ); + } + + m_SavedDistTooFar = GetOuter()->m_flDistTooFar; + if ( GetFollowTarget() && GetFollowTarget()->IsPlayer() ) + { + GetOuter()->m_flDistTooFar = FLT_MAX; + } + + BaseClass::BeginScheduleSelection(); +} + +//------------------------------------- + +void CAI_FollowBehavior::EndScheduleSelection() +{ + if ( !m_params.bNormalMemoryDiscard ) + { + // Restore our original enemy discard time + GetOuter()->GetEnemies()->SetEnemyDiscardTime( m_flOriginalEnemyDiscardTime ); + } + + if ( m_SavedDistTooFar > 0.1 ) // backward savefile compatability + { + GetOuter()->m_flDistTooFar = m_SavedDistTooFar; + } + + 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; + + if ( IsRunning() ) + { + GetOuter()->ClearSchedule( "Follow target gone" ); + } + 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; + + if ( GetFollowTarget()->GetFlags() & FL_NOTARGET ) + return false; + + // If we recently failed to build a follow path, wait a while to + // give other schedules a chance to run. + if ( m_bFollowNavFailed && m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() ) + { + return false; + } + + m_bFollowNavFailed = false; + + return true; +} + +//------------------------------------- + +bool CAI_FollowBehavior::ShouldMoveToFollowTarget() +{ + if ( GetFollowTarget() == NULL ) + return false; + + 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 FollowCallBaseSelectSchedule(); +} + +//------------------------------------- + +int CAI_FollowBehavior::TranslateSchedule( int scheduleType ) +{ + switch( scheduleType ) + { + case SCHED_FOLLOWER_IDLE_STAND: + // If we have an enemy, at least face them! + if ( GetEnemy() ) + return SCHED_FOLLOWER_COMBAT_FACE; + + break; + + 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; + + // If we have an enemy, at least face them! + if ( GetEnemy() ) + return SCHED_FOLLOWER_COMBAT_FACE; + + 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() || m_bFirstFacing ) && !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; + } + + // SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK just tells the NPC to chase their enemy, so + // forbid this unless the destination is acceptable within the parameters of the follow behavior. + case SCHED_CHASE_ENEMY: + case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK: + { + if ( IsChaseGoalInRange() == false ) + return SCHED_FOLLOWER_IDLE_STAND; + break; + } + + case SCHED_RANGE_ATTACK1: + { + if ( GetOuter()->GetShotRegulator()->IsInRestInterval() ) + { + if ( GetEnemy() ) + return SCHED_FOLLOWER_COMBAT_FACE; + + 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_BLOCKLOS, 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; + m_RepathOnFollowTimer.Force(); + } + 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( vGoalPosition, AIN_DEF_ACTIVITY, GetGoalTolerance() ); + if ( !m_hFollowTarget->GetParent() || !m_hFollowTarget->GetParent()->GetServerVehicle() ) + { + goal.pTarget = m_hFollowTarget; + } + else + { + goal.pTarget = m_hFollowTarget->GetParent(); + } + + bool bSuccess = true; + if ( !GetNavigator()->SetGoal( goal, AIN_NO_PATH_TASK_FAIL ) ) + { + const Vector &vTarget = GetFollowTarget()->WorldSpaceCenter(); + Vector vToGoal = vGoalPosition - vTarget; + if ( vToGoal.Length2DSqr() > 6*12 ) + { + goal.dest = vTarget + vToGoal * 0.5; + if ( !GetNavigator()->SetGoal( goal, AIN_NO_PATH_TASK_FAIL ) ) + { + bSuccess = false; + m_FollowDelay.Start( 2.0, 5.0 ); + } + } + else + { + bSuccess = false; + m_FollowDelay.Start( 2.0, 5.0 ); + } + } + + if ( !bSuccess ) + { + m_bFollowNavFailed = true; + TaskFail( FAIL_NO_ROUTE ); + } + else + { + TaskComplete(); + } + } + } + + 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 ( m_RepathOnFollowTimer.Expired() ) + { + if ( (GetNavigator()->GetGoalPos() - GetGoalPosition()).LengthSqr() > Square( m_FollowNavGoal.repathOnRouteTolerance ) ) + { + if ( GetNavigator()->GetNavType() != NAV_JUMP ) + { + m_RepathOnFollowTimer.Set( .5 ); + if ( !GetNavigator()->UpdateGoalPos( GetGoalPosition() ) ) + { + bool bSuccess = false; + const Vector &vTarget = GetFollowTarget()->WorldSpaceCenter(); + Vector vToGoal = GetGoalPosition() - vTarget; + if ( vToGoal.Length2DSqr() > 6*12 ) + { + if ( GetNavigator()->UpdateGoalPos( vTarget + vToGoal * 0.5 ) ) + { + bSuccess = true; + } + } + + if ( !bSuccess ) + { + 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" ), + +#ifdef HL2_EPISODIC + DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ), +#endif +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 ); +} + +//------------------------------------- + +#ifdef HL2_EPISODIC +void CAI_FollowGoal::InputOutsideTransition( inputdata_t &inputdata ) +{ + EnterDormant(); +} +#endif + +//----------------------------------------------------------------------------- +// +// 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, +}; + + +//------------------------------------- +// Used for hunters following striders +//------------------------------------- +static AI_FollowSlot_t g_HunterFollowFormationSlots[] = +{ + { 3, { 480, -240, -400 }, 0, 48, 64, 1000, 60 }, + { 3, { 480, 240, -400 }, 0, 48, 64, 1000, 60 }, + { 2, { 480, 0, -400 }, 0, 48, 64, 1000, 60 }, + { 1, { -240, 0, -400 }, 0, 48, 64, 1000, 60 }, +}; + +static AI_FollowFormation_t g_HunterFollowFormation = +{ + "Hunter", + AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS, + ARRAYSIZE(g_HunterFollowFormationSlots), + 48, // followPointTolerance + 48, // targetMoveTolerance + 60,//180, // repathOnRouteTolerance + 0, // walkTolerance + 960, // coverTolerance + 960, // enemyLOSTolerance + 1920, // chaseEnemyTolerance + g_HunterFollowFormationSlots, +}; + + +//------------------------------------- + +static AI_FollowSlot_t g_VortigauntFollowFormationSlots[] = +{ + { 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_VortigauntFollowFormation = +{ + "Vortigaunt", + AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS | AIFF_REQUIRE_LOS_OUTSIDE_COMBAT, + ARRAYSIZE(g_VortigauntFollowFormationSlots), + 168, // followPointTolerance + 36, // targetMoveTolerance + 60, // repathOnRouteTolerance + 190, // walkTolerance + 300, // coverTolerance + (50*12), // enemyLOSTolerance + (50*12), // chaseEnemyTolerance + g_VortigauntFollowFormationSlots, +}; + + +//----------------------------------------------------------------------------- +// NOTE: these must correspond with the AI_Formations_t enumeration in AI_Behavior_Follow.h!! +//----------------------------------------------------------------------------- +AI_FollowFormation_t *g_AI_Formations[] = +{ + &g_SimpleFollowFormation, + &g_WideFollowFormation, + &g_AntlionFollowFormation, + &g_CommanderFollowFormation, + &g_TightFollowFormation, + &g_MediumFollowFormation, + &g_SidekickFollowFormation, + &g_HunterFollowFormation, + &g_VortigauntFollowFormation, +}; + +AI_FollowFormation_t *AIGetFormation( AI_Formations_t formation ) +{ + if ( formation < 0 ) + formation = (AI_Formations_t)0; + else if ( 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.Set( 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.Clear( pBest->slot ); + } + pBest->slot = bestSlot; + CalculateFieldsFromSlot( pSlot, &pBest->navInfo ); + pGroup->slotUsage.Set( 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.ClearAll(); + 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.Clear( 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.IsBitSet( 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" + ) + + DEFINE_SCHEDULE + ( + SCHED_FOLLOWER_COMBAT_FACE, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_FACE_ENEMY 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_SEE_FEAR" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " 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_BETTER_WEAPON_AVAILABLE" + ) + + AI_END_CUSTOM_SCHEDULE_PROVIDER() + +//============================================================================= |