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/hl2/ai_behavior_functank.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/hl2/ai_behavior_functank.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/ai_behavior_functank.cpp | 1552 |
1 files changed, 776 insertions, 776 deletions
diff --git a/mp/src/game/server/hl2/ai_behavior_functank.cpp b/mp/src/game/server/hl2/ai_behavior_functank.cpp index 51cf6ff9..0c1ae3bf 100644 --- a/mp/src/game/server/hl2/ai_behavior_functank.cpp +++ b/mp/src/game/server/hl2/ai_behavior_functank.cpp @@ -1,776 +1,776 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "ai_behavior_functank.h"
-#include "ai_navigator.h"
-#include "ai_memory.h"
-#include "ai_senses.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-// How long to fire a func tank before running schedule selection again.
-#define FUNCTANK_FIRE_TIME 5.0f
-
-BEGIN_DATADESC( CAI_FuncTankBehavior )
- DEFINE_FIELD( m_hFuncTank, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bMounted, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flBusyTime, FIELD_TIME ),
- DEFINE_FIELD( m_bSpottedPlayerOutOfCover, FIELD_BOOLEAN ),
-END_DATADESC();
-
-//-----------------------------------------------------------------------------
-// Purpose: Constructor
-//--k---------------------------------------------------------------------------
-CAI_FuncTankBehavior::CAI_FuncTankBehavior()
-{
- m_hFuncTank = NULL;
- m_bMounted = false;
- m_flBusyTime = 0.0f;
- m_bSpottedPlayerOutOfCover = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Deconstructor
-//-----------------------------------------------------------------------------
-CAI_FuncTankBehavior::~CAI_FuncTankBehavior()
-{
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_FuncTankBehavior::CanSelectSchedule()
-{
- // If we don't have a func_tank do not bother with conditions, schedules, etc.
- if ( !m_hFuncTank )
- return false;
-
- // Are you alive, in a script?
- if ( !GetOuter()->IsInterruptable() )
- return false;
-
- // Commander is giving you orders?
- if ( GetOuter()->HasCondition( COND_RECEIVED_ORDERS ) )
- return false;
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::BeginScheduleSelection()
-{
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::EndScheduleSelection()
-{
- if ( m_bMounted )
- {
- Dismount();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::PrescheduleThink()
-{
- BaseClass::PrescheduleThink();
-
- if ( !HasCondition(COND_SEE_PLAYER) )
- {
- m_bSpottedPlayerOutOfCover = false;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CAI_FuncTankBehavior::SelectSchedule()
-{
- // This shouldn't get called with an m_hFuncTank, see CanSelectSchedule.
- Assert( m_hFuncTank );
-
- // If we've been told to dismount, or we are out of ammo - dismount.
- if ( HasCondition( COND_FUNCTANK_DISMOUNT ) || m_hFuncTank->GetAmmoCount() == 0 )
- {
- if ( m_bMounted )
- {
- Dismount();
- }
-
- return BaseClass::SelectSchedule();
- }
-
- // If we are not mounted to a func_tank look for one.
- if ( !IsMounted() )
- {
- return SCHED_MOVE_TO_FUNCTANK;
- }
-
- // If we have an enemy, it's in the viewcone & we have LOS to it
- if ( GetEnemy() )
- {
- // Tell the func tank whenever we see the player for the first time since not seeing him for a while
- if ( HasCondition( COND_NEW_ENEMY ) && GetEnemy()->IsPlayer() && !m_bSpottedPlayerOutOfCover )
- {
- m_bSpottedPlayerOutOfCover = true;
- m_hFuncTank->NPC_JustSawPlayer( GetEnemy() );
- }
-
- // Fire at the enemy.
- return SCHED_FIRE_FUNCTANK;
- }
- else
- {
- // Scan for enemies.
- return SCHED_SCAN_WITH_FUNCTANK;
- }
-
- return SCHED_IDLE_STAND;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : activity -
-// Output : Activity
-//-----------------------------------------------------------------------------
-Activity CAI_FuncTankBehavior::NPC_TranslateActivity( Activity activity )
-{
- // If I'm on the gun, I play the idle manned gun animation
- if ( m_bMounted )
- return ACT_IDLE_MANNEDGUN;
-
- return BaseClass::NPC_TranslateActivity( activity );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::Dismount( void )
-{
- SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
-
- Assert( m_hFuncTank );
-
- if ( m_hFuncTank )
- {
- GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_DISMOUNTING );
-
- Assert( m_hFuncTank->IsMarkedForDeletion() || m_hFuncTank->GetController() == GetOuter() );
-
- m_hFuncTank->NPC_SetInRoute( false );
- if ( m_hFuncTank->GetController() == GetOuter() )
- m_hFuncTank->StopControl();
- SetFuncTank( NULL );
- }
-
- GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED );
-
- m_bMounted = false;
-
- // Set this condition to force breakout of any func_tank behavior schedules
- SetCondition( COND_FUNCTANK_DISMOUNT );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-int CAI_FuncTankBehavior::OnTakeDamage_Alive( const CTakeDamageInfo &info )
-{
- int iResult = BaseClass::OnTakeDamage_Alive( info );
- if ( !iResult )
- return 0;
-
- // If we've been hit by the player, and the player's not targetable
- // by our func_tank, get off the tank.
- CBaseEntity *pAttacker = info.GetAttacker();
- bool bValidDismountAttacker = (pAttacker && pAttacker->IsPlayer());
-
-#ifdef HL2_EPISODIC
- bValidDismountAttacker = true;
-#endif
-
- if ( m_hFuncTank && bValidDismountAttacker == true )
- {
- if ( !m_hFuncTank->IsEntityInViewCone( pAttacker ) )
- {
- SetCondition( COND_FUNCTANK_DISMOUNT );
- }
- }
-
- return iResult;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_FUNCTANK_ANNOUNCE_SCAN:
- {
- if ( random->RandomInt( 0, 3 ) == 0 )
- {
- GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_SCAN_FOR_ENEMIES );
- }
- TaskComplete();
- }
- break;
-
- case TASK_GET_PATH_TO_FUNCTANK:
- {
- if ( !m_hFuncTank )
- {
- TaskFail( FAIL_NO_TARGET );
- return;
- }
-
- Vector vecManPos;
- m_hFuncTank->NPC_FindManPoint( vecManPos );
- AI_NavGoal_t goal( vecManPos );
- goal.pTarget = m_hFuncTank;
- if ( GetNavigator()->SetGoal( goal ) )
- {
- GetNavigator()->SetArrivalDirection( m_hFuncTank->GetAbsAngles() );
- TaskComplete();
- }
- else
- {
- TaskFail("NO PATH");
-
- // Don't try and use me again for a while
- SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
- }
- break;
- }
- case TASK_FACE_FUNCTANK:
- {
- if ( !m_hFuncTank )
- {
- TaskFail( FAIL_NO_TARGET );
- return;
- }
-
- // Ensure we've reached the func_tank
- Vector vecManPos;
- m_hFuncTank->NPC_FindManPoint( vecManPos );
-
- // More leniency in Z.
- Vector vecDelta = (vecManPos - GetAbsOrigin());
- if ( fabs(vecDelta.x) > 16 || fabs(vecDelta.y) > 16 || fabs(vecDelta.z) > 48 )
- {
- TaskFail( "Not correctly on func_tank man point" );
- m_hFuncTank->NPC_InterruptRoute();
- return;
- }
-
- GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );
- GetOuter()->SetTurnActivity();
- break;
- }
-
- case TASK_HOLSTER_WEAPON:
- {
- if ( !m_hFuncTank )
- {
- TaskFail( FAIL_NO_TARGET );
- return;
- }
-
- if ( GetOuter()->IsWeaponHolstered() || !GetOuter()->CanHolsterWeapon() )
- {
- GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_JUST_MOUNTED );
-
- // We are at the correct position and facing for the func_tank, mount it.
- m_hFuncTank->StartControl( GetOuter() );
- GetOuter()->ClearEnemyMemory();
- m_bMounted = true;
- TaskComplete();
-
- GetOuter()->SetIdealActivity( ACT_IDLE_MANNEDGUN );
- }
- else
- {
- GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED );
- }
- break;
- }
-
- case TASK_FIRE_FUNCTANK:
- {
- if ( !m_hFuncTank )
- {
- TaskFail( FAIL_NO_TARGET );
- return;
- }
- GetOuter()->m_flWaitFinished = gpGlobals->curtime + FUNCTANK_FIRE_TIME;
- break;
- }
- case TASK_SCAN_LEFT_FUNCTANK:
- {
- if ( !m_hFuncTank )
- {
- TaskFail( FAIL_NO_TARGET );
- return;
- }
-
- GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );
-
- float flCenterYaw = m_hFuncTank->YawCenterWorld();
- float flYawRange = m_hFuncTank->YawRange();
- float flScanAmount = random->RandomFloat( 0, flYawRange );
- QAngle vecTargetAngles( 0, UTIL_AngleMod( flCenterYaw + flScanAmount ), 0 );
-
- /*
- float flCenterPitch = m_hFuncTank->YawCenterWorld();
- float flPitchRange = m_hFuncTank->PitchRange();
- float flPitch = random->RandomFloat( -flPitchRange, flPitchRange );
- QAngle vecTargetAngles( flCenterPitch + flPitch, UTIL_AngleMod( flCenterYaw + flScanAmount ), 0 );
- */
-
- Vector vecTargetForward;
- AngleVectors( vecTargetAngles, &vecTargetForward );
- Vector vecTarget = GetOuter()->EyePosition() + (vecTargetForward * 256);
- GetOuter()->AddLookTarget( vecTarget, 1.0, 2.0, 0.2 );
-
- m_hFuncTank->NPC_SetIdleAngle( vecTarget );
-
- break;
- }
- case TASK_SCAN_RIGHT_FUNCTANK:
- {
- if ( !m_hFuncTank )
- {
- TaskFail( FAIL_NO_TARGET );
- return;
- }
-
- GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );
-
- float flCenterYaw = m_hFuncTank->YawCenterWorld();
- float flYawRange = m_hFuncTank->YawRange();
- float flScanAmount = random->RandomFloat( 0, flYawRange );
- QAngle vecTargetAngles( 0, UTIL_AngleMod( flCenterYaw - flScanAmount ), 0 );
-
- /*
- float flCenterPitch = m_hFuncTank->YawCenterWorld();
- float flPitchRange = m_hFuncTank->PitchRange();
- float flPitch = random->RandomFloat( -flPitchRange, flPitchRange );
- QAngle vecTargetAngles( flCenterPitch + flPitch, UTIL_AngleMod( flCenterYaw - flScanAmount ), 0 );
- */
-
- Vector vecTargetForward;
- AngleVectors( vecTargetAngles, &vecTargetForward );
- Vector vecTarget = GetOuter()->EyePosition() + (vecTargetForward * 256);
- GetOuter()->AddLookTarget( vecTarget, 1.0, 2.0, 0.2 );
-
- m_hFuncTank->NPC_SetIdleAngle( vecTarget );
-
- break;
- }
- case TASK_FORGET_ABOUT_FUNCTANK:
- {
- if ( !m_hFuncTank )
- {
- TaskFail( FAIL_NO_TARGET );
- return;
- }
- break;
- }
- default:
- {
- BaseClass::StartTask( pTask );
- break;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_FACE_FUNCTANK:
- {
- Assert( m_hFuncTank );
-
- GetMotor()->UpdateYaw();
-
- if ( GetOuter()->FacingIdeal() )
- {
- TaskComplete();
- }
- break;
- }
- case TASK_HOLSTER_WEAPON:
- {
- Assert( m_hFuncTank );
-
- if ( GetOuter()->IsWeaponHolstered() )
- {
- GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_JUST_MOUNTED );
-
- // We are at the correct position and facing for the func_tank, mount it.
- m_hFuncTank->StartControl( GetOuter() );
- GetOuter()->ClearEnemyMemory();
- m_bMounted = true;
- TaskComplete();
-
- GetOuter()->SetIdealActivity( ACT_IDLE_MANNEDGUN );
- }
-
- break;
- }
- case TASK_FIRE_FUNCTANK:
- {
- Assert( m_hFuncTank );
-
- if( GetOuter()->m_flWaitFinished < gpGlobals->curtime )
- {
- TaskComplete();
- }
-
- if ( m_hFuncTank->NPC_HasEnemy() )
- {
- GetOuter()->SetLastAttackTime( gpGlobals->curtime );
- m_hFuncTank->NPC_Fire();
-
- // The NPC may have decided to stop using the func_tank, because it's out of ammo.
- if ( !m_hFuncTank )
- {
- TaskComplete();
- break;
- }
- }
- else
- {
- TaskComplete();
- }
-
- Assert( m_hFuncTank );
-
- if ( m_hFuncTank->GetAmmoCount() == 0 )
- {
- TaskComplete();
- }
- break;
- }
- case TASK_SCAN_LEFT_FUNCTANK:
- case TASK_SCAN_RIGHT_FUNCTANK:
- {
- GetMotor()->UpdateYaw();
- if ( GetOuter()->FacingIdeal() )
- {
- TaskComplete();
- }
- break;
- }
- case TASK_FORGET_ABOUT_FUNCTANK:
- {
- m_hFuncTank->NPC_InterruptRoute();
- SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
- TaskComplete();
- break;
- }
- default:
- {
- BaseClass::RunTask( pTask );
- break;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::Event_Killed( const CTakeDamageInfo &info )
-{
- if ( m_hFuncTank )
- {
- Dismount();
- }
- Assert( !m_hFuncTank );
-
- BaseClass::Event_Killed( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::UpdateOnRemove( void )
-{
- if ( m_hFuncTank )
- {
- Dismount();
- }
-
- BaseClass::UpdateOnRemove();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::SetFuncTank( CHandle<CFuncTank> hFuncTank )
-{
- if ( m_hFuncTank && !hFuncTank )
- {
- SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
- SetCondition( COND_FUNCTANK_DISMOUNT );
- }
-
- m_hFuncTank = hFuncTank;
- GetOuter()->ClearSchedule( "Setting a new func_tank" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::AimGun( void )
-{
- if ( m_bMounted && m_hFuncTank)
- {
- Vector vecForward;
- AngleVectors( m_hFuncTank->GetAbsAngles(), &vecForward );
- GetOuter()->SetAim( vecForward );
- return;
- }
-
- BaseClass::AimGun();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_FuncTankBehavior::GatherConditions()
-{
- BaseClass::GatherConditions();
-
- // Since we can't pathfind, if we can't see the enemy, he's eluded us
- // So we deliberately ignore unreachability
- if ( GetEnemy() && !HasCondition(COND_SEE_ENEMY) )
- {
- if ( gpGlobals->curtime - GetOuter()->GetEnemyLastTimeSeen() >= 3.0f )
- {
- GetOuter()->MarkEnemyAsEluded();
- }
- }
-
- if ( !m_hFuncTank )
- {
- m_bMounted = false;
- GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-CBaseEntity *CAI_FuncTankBehavior::BestEnemy( void )
-{
- // Only use this BestEnemy call when we are on the manned gun.
- if ( !m_hFuncTank ||!IsMounted() )
- return BaseClass::BestEnemy();
-
- CBaseEntity *pBestEnemy = NULL;
- int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE; // so first visible entity will become the closest.
- int iBestPriority = -1000;
- bool bBestUnreachable = false; // Forces initial check
- bool bBestSeen = false;
- bool bUnreachable = false;
- int iDistSq;
-
- AIEnemiesIter_t iter;
-
- // Get the current npc for checking from.
- CAI_BaseNPC *pNPC = GetOuter();
- if ( !pNPC )
- return NULL;
-
- for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) )
- {
- CBaseEntity *pEnemy = pEMemory->hEnemy;
- if ( !pEnemy || !pEnemy->IsAlive() )
- continue;
-
- // UNDONE: Move relationship checks into IsValidEnemy?
- if ( ( pEnemy->GetFlags() & FL_NOTARGET ) ||
- ( pNPC->IRelationType( pEnemy ) != D_HT && pNPC->IRelationType( pEnemy ) != D_FR ) ||
- !IsValidEnemy( pEnemy ) )
- continue;
-
- if ( pEMemory->timeLastSeen < pNPC->GetAcceptableTimeSeenEnemy() )
- continue;
-
- if ( pEMemory->timeValidEnemy > gpGlobals->curtime )
- continue;
-
- // Skip enemies that have eluded me to prevent infinite loops
- if ( GetEnemies()->HasEludedMe( pEnemy ) )
- continue;
-
- // Establish the reachability of this enemy
- bUnreachable = pNPC->IsUnreachable( pEnemy );
-
- // Check view cone of the view tank here.
- bUnreachable = !m_hFuncTank->IsEntityInViewCone( pEnemy );
- if ( !bUnreachable )
- {
- // It's in the viewcone. Now make sure we have LOS to it.
- bUnreachable = !m_hFuncTank->HasLOSTo( pEnemy );
- }
-
- // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority
- if ( !bBestUnreachable && bUnreachable )
- continue;
-
- // If best is unreachable and current is reachable, always pick the current regardless of priority
- if ( bBestUnreachable && !bUnreachable )
- {
- bBestSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) ); // @TODO (toml 04-02-03): Need to optimize CanSeeEntity() so multiple calls in frame do not recalculate, rather cache
- iBestPriority = pNPC->IRelationPriority( pEnemy );
- iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
- pBestEnemy = pEnemy;
- bBestUnreachable = bUnreachable;
- }
- // If both are unreachable or both are reachable, chose enemy based on priority and distance
- else if ( pNPC->IRelationPriority( pEnemy ) > iBestPriority )
- {
- // this entity is disliked MORE than the entity that we
- // currently think is the best visible enemy. No need to do
- // a distance check, just get mad at this one for now.
- iBestPriority = pNPC->IRelationPriority ( pEnemy );
- iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
- pBestEnemy = pEnemy;
- bBestUnreachable = bUnreachable;
- }
- else if ( pNPC->IRelationPriority( pEnemy ) == iBestPriority )
- {
- // this entity is disliked just as much as the entity that
- // we currently think is the best visible enemy, so we only
- // get mad at it if it is closer.
- iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
-
- bool bCloser = ( iDistSq < iBestDistSq ) ;
-
- if ( bCloser || !bBestSeen )
- {
- // @TODO (toml 04-02-03): Need to optimize FVisible() so multiple calls in frame do not recalculate, rather cache
- bool fSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) );
- if ( ( bCloser && ( fSeen || !bBestSeen ) ) || ( !bCloser && !bBestSeen && fSeen ) )
- {
- bBestSeen = fSeen;
- iBestDistSq = iDistSq;
- iBestPriority = pNPC->IRelationPriority( pEnemy );
- pBestEnemy = pEnemy;
- bBestUnreachable = bUnreachable;
- }
- }
- }
- }
- return pBestEnemy;
-}
-
-//=============================================================================
-//
-// Custom AI schedule data
-//
-
-AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FuncTankBehavior )
-
- DECLARE_TASK( TASK_GET_PATH_TO_FUNCTANK )
- DECLARE_TASK( TASK_FACE_FUNCTANK )
- DECLARE_TASK( TASK_HOLSTER_WEAPON )
- DECLARE_TASK( TASK_FIRE_FUNCTANK )
- DECLARE_TASK( TASK_SCAN_LEFT_FUNCTANK )
- DECLARE_TASK( TASK_SCAN_RIGHT_FUNCTANK )
- DECLARE_TASK( TASK_FORGET_ABOUT_FUNCTANK )
- DECLARE_TASK( TASK_FUNCTANK_ANNOUNCE_SCAN )
-
- DECLARE_CONDITION( COND_FUNCTANK_DISMOUNT )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_MOVE_TO_FUNCTANK,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE: SCHED_FAIL_MOVE_TO_FUNCTANK"
- " TASK_GET_PATH_TO_FUNCTANK 0"
- " TASK_SPEAK_SENTENCE 1000" // FUNCTANK_SENTENCE_MOVE_TO_MOUNT
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_FUNCTANK 0"
- " TASK_HOLSTER_WEAPON 0"
- " "
- " Interrupts"
- " COND_FUNCTANK_DISMOUNT"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_FIRE_FUNCTANK,
-
- " Tasks"
- " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
- " TASK_FIRE_FUNCTANK 0"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_LOST_ENEMY"
- " COND_ENEMY_OCCLUDED"
- " COND_WEAPON_BLOCKED_BY_FRIEND"
- " COND_WEAPON_SIGHT_OCCLUDED"
- " COND_FUNCTANK_DISMOUNT"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_SCAN_WITH_FUNCTANK,
-
- " Tasks"
- " TASK_FUNCTANK_ANNOUNCE_SCAN 0"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT 4"
- " TASK_SCAN_LEFT_FUNCTANK 0"
- " TASK_WAIT 4"
- " TASK_SCAN_RIGHT_FUNCTANK 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_PROVOKED"
- " COND_FUNCTANK_DISMOUNT"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_FAIL_MOVE_TO_FUNCTANK,
-
- " Tasks"
- " TASK_FORGET_ABOUT_FUNCTANK 0"
- ""
- " Interrupts"
- )
-
-AI_END_CUSTOM_SCHEDULE_PROVIDER()
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "ai_behavior_functank.h" +#include "ai_navigator.h" +#include "ai_memory.h" +#include "ai_senses.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// How long to fire a func tank before running schedule selection again. +#define FUNCTANK_FIRE_TIME 5.0f + +BEGIN_DATADESC( CAI_FuncTankBehavior ) + DEFINE_FIELD( m_hFuncTank, FIELD_EHANDLE ), + DEFINE_FIELD( m_bMounted, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flBusyTime, FIELD_TIME ), + DEFINE_FIELD( m_bSpottedPlayerOutOfCover, FIELD_BOOLEAN ), +END_DATADESC(); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//--k--------------------------------------------------------------------------- +CAI_FuncTankBehavior::CAI_FuncTankBehavior() +{ + m_hFuncTank = NULL; + m_bMounted = false; + m_flBusyTime = 0.0f; + m_bSpottedPlayerOutOfCover = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Deconstructor +//----------------------------------------------------------------------------- +CAI_FuncTankBehavior::~CAI_FuncTankBehavior() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_FuncTankBehavior::CanSelectSchedule() +{ + // If we don't have a func_tank do not bother with conditions, schedules, etc. + if ( !m_hFuncTank ) + return false; + + // Are you alive, in a script? + if ( !GetOuter()->IsInterruptable() ) + return false; + + // Commander is giving you orders? + if ( GetOuter()->HasCondition( COND_RECEIVED_ORDERS ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::BeginScheduleSelection() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::EndScheduleSelection() +{ + if ( m_bMounted ) + { + Dismount(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::PrescheduleThink() +{ + BaseClass::PrescheduleThink(); + + if ( !HasCondition(COND_SEE_PLAYER) ) + { + m_bSpottedPlayerOutOfCover = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CAI_FuncTankBehavior::SelectSchedule() +{ + // This shouldn't get called with an m_hFuncTank, see CanSelectSchedule. + Assert( m_hFuncTank ); + + // If we've been told to dismount, or we are out of ammo - dismount. + if ( HasCondition( COND_FUNCTANK_DISMOUNT ) || m_hFuncTank->GetAmmoCount() == 0 ) + { + if ( m_bMounted ) + { + Dismount(); + } + + return BaseClass::SelectSchedule(); + } + + // If we are not mounted to a func_tank look for one. + if ( !IsMounted() ) + { + return SCHED_MOVE_TO_FUNCTANK; + } + + // If we have an enemy, it's in the viewcone & we have LOS to it + if ( GetEnemy() ) + { + // Tell the func tank whenever we see the player for the first time since not seeing him for a while + if ( HasCondition( COND_NEW_ENEMY ) && GetEnemy()->IsPlayer() && !m_bSpottedPlayerOutOfCover ) + { + m_bSpottedPlayerOutOfCover = true; + m_hFuncTank->NPC_JustSawPlayer( GetEnemy() ); + } + + // Fire at the enemy. + return SCHED_FIRE_FUNCTANK; + } + else + { + // Scan for enemies. + return SCHED_SCAN_WITH_FUNCTANK; + } + + return SCHED_IDLE_STAND; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : activity - +// Output : Activity +//----------------------------------------------------------------------------- +Activity CAI_FuncTankBehavior::NPC_TranslateActivity( Activity activity ) +{ + // If I'm on the gun, I play the idle manned gun animation + if ( m_bMounted ) + return ACT_IDLE_MANNEDGUN; + + return BaseClass::NPC_TranslateActivity( activity ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::Dismount( void ) +{ + SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME ); + + Assert( m_hFuncTank ); + + if ( m_hFuncTank ) + { + GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_DISMOUNTING ); + + Assert( m_hFuncTank->IsMarkedForDeletion() || m_hFuncTank->GetController() == GetOuter() ); + + m_hFuncTank->NPC_SetInRoute( false ); + if ( m_hFuncTank->GetController() == GetOuter() ) + m_hFuncTank->StopControl(); + SetFuncTank( NULL ); + } + + GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED ); + + m_bMounted = false; + + // Set this condition to force breakout of any func_tank behavior schedules + SetCondition( COND_FUNCTANK_DISMOUNT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +int CAI_FuncTankBehavior::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + int iResult = BaseClass::OnTakeDamage_Alive( info ); + if ( !iResult ) + return 0; + + // If we've been hit by the player, and the player's not targetable + // by our func_tank, get off the tank. + CBaseEntity *pAttacker = info.GetAttacker(); + bool bValidDismountAttacker = (pAttacker && pAttacker->IsPlayer()); + +#ifdef HL2_EPISODIC + bValidDismountAttacker = true; +#endif + + if ( m_hFuncTank && bValidDismountAttacker == true ) + { + if ( !m_hFuncTank->IsEntityInViewCone( pAttacker ) ) + { + SetCondition( COND_FUNCTANK_DISMOUNT ); + } + } + + return iResult; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::StartTask( const Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FUNCTANK_ANNOUNCE_SCAN: + { + if ( random->RandomInt( 0, 3 ) == 0 ) + { + GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_SCAN_FOR_ENEMIES ); + } + TaskComplete(); + } + break; + + case TASK_GET_PATH_TO_FUNCTANK: + { + if ( !m_hFuncTank ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + + Vector vecManPos; + m_hFuncTank->NPC_FindManPoint( vecManPos ); + AI_NavGoal_t goal( vecManPos ); + goal.pTarget = m_hFuncTank; + if ( GetNavigator()->SetGoal( goal ) ) + { + GetNavigator()->SetArrivalDirection( m_hFuncTank->GetAbsAngles() ); + TaskComplete(); + } + else + { + TaskFail("NO PATH"); + + // Don't try and use me again for a while + SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME ); + } + break; + } + case TASK_FACE_FUNCTANK: + { + if ( !m_hFuncTank ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + + // Ensure we've reached the func_tank + Vector vecManPos; + m_hFuncTank->NPC_FindManPoint( vecManPos ); + + // More leniency in Z. + Vector vecDelta = (vecManPos - GetAbsOrigin()); + if ( fabs(vecDelta.x) > 16 || fabs(vecDelta.y) > 16 || fabs(vecDelta.z) > 48 ) + { + TaskFail( "Not correctly on func_tank man point" ); + m_hFuncTank->NPC_InterruptRoute(); + return; + } + + GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() ); + GetOuter()->SetTurnActivity(); + break; + } + + case TASK_HOLSTER_WEAPON: + { + if ( !m_hFuncTank ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + + if ( GetOuter()->IsWeaponHolstered() || !GetOuter()->CanHolsterWeapon() ) + { + GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_JUST_MOUNTED ); + + // We are at the correct position and facing for the func_tank, mount it. + m_hFuncTank->StartControl( GetOuter() ); + GetOuter()->ClearEnemyMemory(); + m_bMounted = true; + TaskComplete(); + + GetOuter()->SetIdealActivity( ACT_IDLE_MANNEDGUN ); + } + else + { + GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED ); + } + break; + } + + case TASK_FIRE_FUNCTANK: + { + if ( !m_hFuncTank ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + GetOuter()->m_flWaitFinished = gpGlobals->curtime + FUNCTANK_FIRE_TIME; + break; + } + case TASK_SCAN_LEFT_FUNCTANK: + { + if ( !m_hFuncTank ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + + GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() ); + + float flCenterYaw = m_hFuncTank->YawCenterWorld(); + float flYawRange = m_hFuncTank->YawRange(); + float flScanAmount = random->RandomFloat( 0, flYawRange ); + QAngle vecTargetAngles( 0, UTIL_AngleMod( flCenterYaw + flScanAmount ), 0 ); + + /* + float flCenterPitch = m_hFuncTank->YawCenterWorld(); + float flPitchRange = m_hFuncTank->PitchRange(); + float flPitch = random->RandomFloat( -flPitchRange, flPitchRange ); + QAngle vecTargetAngles( flCenterPitch + flPitch, UTIL_AngleMod( flCenterYaw + flScanAmount ), 0 ); + */ + + Vector vecTargetForward; + AngleVectors( vecTargetAngles, &vecTargetForward ); + Vector vecTarget = GetOuter()->EyePosition() + (vecTargetForward * 256); + GetOuter()->AddLookTarget( vecTarget, 1.0, 2.0, 0.2 ); + + m_hFuncTank->NPC_SetIdleAngle( vecTarget ); + + break; + } + case TASK_SCAN_RIGHT_FUNCTANK: + { + if ( !m_hFuncTank ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + + GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() ); + + float flCenterYaw = m_hFuncTank->YawCenterWorld(); + float flYawRange = m_hFuncTank->YawRange(); + float flScanAmount = random->RandomFloat( 0, flYawRange ); + QAngle vecTargetAngles( 0, UTIL_AngleMod( flCenterYaw - flScanAmount ), 0 ); + + /* + float flCenterPitch = m_hFuncTank->YawCenterWorld(); + float flPitchRange = m_hFuncTank->PitchRange(); + float flPitch = random->RandomFloat( -flPitchRange, flPitchRange ); + QAngle vecTargetAngles( flCenterPitch + flPitch, UTIL_AngleMod( flCenterYaw - flScanAmount ), 0 ); + */ + + Vector vecTargetForward; + AngleVectors( vecTargetAngles, &vecTargetForward ); + Vector vecTarget = GetOuter()->EyePosition() + (vecTargetForward * 256); + GetOuter()->AddLookTarget( vecTarget, 1.0, 2.0, 0.2 ); + + m_hFuncTank->NPC_SetIdleAngle( vecTarget ); + + break; + } + case TASK_FORGET_ABOUT_FUNCTANK: + { + if ( !m_hFuncTank ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + break; + } + default: + { + BaseClass::StartTask( pTask ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::RunTask( const Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FACE_FUNCTANK: + { + Assert( m_hFuncTank ); + + GetMotor()->UpdateYaw(); + + if ( GetOuter()->FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_HOLSTER_WEAPON: + { + Assert( m_hFuncTank ); + + if ( GetOuter()->IsWeaponHolstered() ) + { + GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_JUST_MOUNTED ); + + // We are at the correct position and facing for the func_tank, mount it. + m_hFuncTank->StartControl( GetOuter() ); + GetOuter()->ClearEnemyMemory(); + m_bMounted = true; + TaskComplete(); + + GetOuter()->SetIdealActivity( ACT_IDLE_MANNEDGUN ); + } + + break; + } + case TASK_FIRE_FUNCTANK: + { + Assert( m_hFuncTank ); + + if( GetOuter()->m_flWaitFinished < gpGlobals->curtime ) + { + TaskComplete(); + } + + if ( m_hFuncTank->NPC_HasEnemy() ) + { + GetOuter()->SetLastAttackTime( gpGlobals->curtime ); + m_hFuncTank->NPC_Fire(); + + // The NPC may have decided to stop using the func_tank, because it's out of ammo. + if ( !m_hFuncTank ) + { + TaskComplete(); + break; + } + } + else + { + TaskComplete(); + } + + Assert( m_hFuncTank ); + + if ( m_hFuncTank->GetAmmoCount() == 0 ) + { + TaskComplete(); + } + break; + } + case TASK_SCAN_LEFT_FUNCTANK: + case TASK_SCAN_RIGHT_FUNCTANK: + { + GetMotor()->UpdateYaw(); + if ( GetOuter()->FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_FORGET_ABOUT_FUNCTANK: + { + m_hFuncTank->NPC_InterruptRoute(); + SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME ); + TaskComplete(); + break; + } + default: + { + BaseClass::RunTask( pTask ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::Event_Killed( const CTakeDamageInfo &info ) +{ + if ( m_hFuncTank ) + { + Dismount(); + } + Assert( !m_hFuncTank ); + + BaseClass::Event_Killed( info ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::UpdateOnRemove( void ) +{ + if ( m_hFuncTank ) + { + Dismount(); + } + + BaseClass::UpdateOnRemove(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::SetFuncTank( CHandle<CFuncTank> hFuncTank ) +{ + if ( m_hFuncTank && !hFuncTank ) + { + SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME ); + SetCondition( COND_FUNCTANK_DISMOUNT ); + } + + m_hFuncTank = hFuncTank; + GetOuter()->ClearSchedule( "Setting a new func_tank" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::AimGun( void ) +{ + if ( m_bMounted && m_hFuncTank) + { + Vector vecForward; + AngleVectors( m_hFuncTank->GetAbsAngles(), &vecForward ); + GetOuter()->SetAim( vecForward ); + return; + } + + BaseClass::AimGun(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::GatherConditions() +{ + BaseClass::GatherConditions(); + + // Since we can't pathfind, if we can't see the enemy, he's eluded us + // So we deliberately ignore unreachability + if ( GetEnemy() && !HasCondition(COND_SEE_ENEMY) ) + { + if ( gpGlobals->curtime - GetOuter()->GetEnemyLastTimeSeen() >= 3.0f ) + { + GetOuter()->MarkEnemyAsEluded(); + } + } + + if ( !m_hFuncTank ) + { + m_bMounted = false; + GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *CAI_FuncTankBehavior::BestEnemy( void ) +{ + // Only use this BestEnemy call when we are on the manned gun. + if ( !m_hFuncTank ||!IsMounted() ) + return BaseClass::BestEnemy(); + + CBaseEntity *pBestEnemy = NULL; + int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE; // so first visible entity will become the closest. + int iBestPriority = -1000; + bool bBestUnreachable = false; // Forces initial check + bool bBestSeen = false; + bool bUnreachable = false; + int iDistSq; + + AIEnemiesIter_t iter; + + // Get the current npc for checking from. + CAI_BaseNPC *pNPC = GetOuter(); + if ( !pNPC ) + return NULL; + + for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) ) + { + CBaseEntity *pEnemy = pEMemory->hEnemy; + if ( !pEnemy || !pEnemy->IsAlive() ) + continue; + + // UNDONE: Move relationship checks into IsValidEnemy? + if ( ( pEnemy->GetFlags() & FL_NOTARGET ) || + ( pNPC->IRelationType( pEnemy ) != D_HT && pNPC->IRelationType( pEnemy ) != D_FR ) || + !IsValidEnemy( pEnemy ) ) + continue; + + if ( pEMemory->timeLastSeen < pNPC->GetAcceptableTimeSeenEnemy() ) + continue; + + if ( pEMemory->timeValidEnemy > gpGlobals->curtime ) + continue; + + // Skip enemies that have eluded me to prevent infinite loops + if ( GetEnemies()->HasEludedMe( pEnemy ) ) + continue; + + // Establish the reachability of this enemy + bUnreachable = pNPC->IsUnreachable( pEnemy ); + + // Check view cone of the view tank here. + bUnreachable = !m_hFuncTank->IsEntityInViewCone( pEnemy ); + if ( !bUnreachable ) + { + // It's in the viewcone. Now make sure we have LOS to it. + bUnreachable = !m_hFuncTank->HasLOSTo( pEnemy ); + } + + // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority + if ( !bBestUnreachable && bUnreachable ) + continue; + + // If best is unreachable and current is reachable, always pick the current regardless of priority + if ( bBestUnreachable && !bUnreachable ) + { + bBestSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) ); // @TODO (toml 04-02-03): Need to optimize CanSeeEntity() so multiple calls in frame do not recalculate, rather cache + iBestPriority = pNPC->IRelationPriority( pEnemy ); + iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); + pBestEnemy = pEnemy; + bBestUnreachable = bUnreachable; + } + // If both are unreachable or both are reachable, chose enemy based on priority and distance + else if ( pNPC->IRelationPriority( pEnemy ) > iBestPriority ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestPriority = pNPC->IRelationPriority ( pEnemy ); + iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); + pBestEnemy = pEnemy; + bBestUnreachable = bUnreachable; + } + else if ( pNPC->IRelationPriority( pEnemy ) == iBestPriority ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); + + bool bCloser = ( iDistSq < iBestDistSq ) ; + + if ( bCloser || !bBestSeen ) + { + // @TODO (toml 04-02-03): Need to optimize FVisible() so multiple calls in frame do not recalculate, rather cache + bool fSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) ); + if ( ( bCloser && ( fSeen || !bBestSeen ) ) || ( !bCloser && !bBestSeen && fSeen ) ) + { + bBestSeen = fSeen; + iBestDistSq = iDistSq; + iBestPriority = pNPC->IRelationPriority( pEnemy ); + pBestEnemy = pEnemy; + bBestUnreachable = bUnreachable; + } + } + } + } + return pBestEnemy; +} + +//============================================================================= +// +// Custom AI schedule data +// + +AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FuncTankBehavior ) + + DECLARE_TASK( TASK_GET_PATH_TO_FUNCTANK ) + DECLARE_TASK( TASK_FACE_FUNCTANK ) + DECLARE_TASK( TASK_HOLSTER_WEAPON ) + DECLARE_TASK( TASK_FIRE_FUNCTANK ) + DECLARE_TASK( TASK_SCAN_LEFT_FUNCTANK ) + DECLARE_TASK( TASK_SCAN_RIGHT_FUNCTANK ) + DECLARE_TASK( TASK_FORGET_ABOUT_FUNCTANK ) + DECLARE_TASK( TASK_FUNCTANK_ANNOUNCE_SCAN ) + + DECLARE_CONDITION( COND_FUNCTANK_DISMOUNT ) + + //========================================================= + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_MOVE_TO_FUNCTANK, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE: SCHED_FAIL_MOVE_TO_FUNCTANK" + " TASK_GET_PATH_TO_FUNCTANK 0" + " TASK_SPEAK_SENTENCE 1000" // FUNCTANK_SENTENCE_MOVE_TO_MOUNT + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_STOP_MOVING 0" + " TASK_FACE_FUNCTANK 0" + " TASK_HOLSTER_WEAPON 0" + " " + " Interrupts" + " COND_FUNCTANK_DISMOUNT" + ) + + //========================================================= + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_FIRE_FUNCTANK, + + " Tasks" + " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack + " TASK_FIRE_FUNCTANK 0" + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_LOST_ENEMY" + " COND_ENEMY_OCCLUDED" + " COND_WEAPON_BLOCKED_BY_FRIEND" + " COND_WEAPON_SIGHT_OCCLUDED" + " COND_FUNCTANK_DISMOUNT" + ) + + DEFINE_SCHEDULE + ( + SCHED_SCAN_WITH_FUNCTANK, + + " Tasks" + " TASK_FUNCTANK_ANNOUNCE_SCAN 0" + " TASK_STOP_MOVING 0" + " TASK_WAIT 4" + " TASK_SCAN_LEFT_FUNCTANK 0" + " TASK_WAIT 4" + " TASK_SCAN_RIGHT_FUNCTANK 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_PROVOKED" + " COND_FUNCTANK_DISMOUNT" + ) + + DEFINE_SCHEDULE + ( + SCHED_FAIL_MOVE_TO_FUNCTANK, + + " Tasks" + " TASK_FORGET_ABOUT_FUNCTANK 0" + "" + " Interrupts" + ) + +AI_END_CUSTOM_SCHEDULE_PROVIDER() |