Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

776 lines
20 KiB

//========= 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()