diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/ai_behavior_functank.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/ai_behavior_functank.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/ai_behavior_functank.cpp | 776 |
1 files changed, 776 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/ai_behavior_functank.cpp b/mp/src/game/server/hl2/ai_behavior_functank.cpp new file mode 100644 index 00000000..51cf6ff9 --- /dev/null +++ b/mp/src/game/server/hl2/ai_behavior_functank.cpp @@ -0,0 +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()
|