You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3123 lines
82 KiB
3123 lines
82 KiB
//========= 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() |
|
|
|
//=============================================================================
|
|
|