1337 lines
37 KiB
C++
1337 lines
37 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Combat behaviors for AIs in a relatively self-preservationist mode.
|
|
// Lots of cover taking and attempted shots out of cover.
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "ai_hint.h"
|
|
#include "ai_node.h"
|
|
#include "ai_navigator.h"
|
|
#include "ai_tacticalservices.h"
|
|
#include "ai_behavior_standoff.h"
|
|
#include "ai_senses.h"
|
|
#include "ai_squad.h"
|
|
#include "ai_goalentity.h"
|
|
#include "ndebugoverlay.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define GOAL_POSITION_INVALID Vector( FLT_MAX, FLT_MAX, FLT_MAX )
|
|
|
|
ConVar DrawBattleLines( "ai_drawbattlelines", "0", FCVAR_CHEAT );
|
|
|
|
|
|
static AI_StandoffParams_t AI_DEFAULT_STANDOFF_PARAMS = { AIHCR_MOVE_ON_COVER, true, true, 2.5, 1., 3, 25, 0, false, 0.f };
|
|
|
|
#define MAKE_ACTMAP_KEY( posture, activity ) ( (((unsigned)(posture)) << 16) | ((unsigned)(activity)) )
|
|
|
|
// #define DEBUG_STANDOFF 1
|
|
|
|
|
|
#ifdef DEBUG_STANDOFF
|
|
#define StandoffMsg( msg ) DevMsg( GetOuter(), msg )
|
|
#define StandoffMsg1( msg, a ) DevMsg( GetOuter(), msg, a )
|
|
#define StandoffMsg2( msg, a, b ) DevMsg( GetOuter(), msg, a, b )
|
|
#define StandoffMsg3( msg, a, b, c ) DevMsg( GetOuter(), msg, a, b, c )
|
|
#define StandoffMsg4( msg, a, b, c, d ) DevMsg( GetOuter(), msg, a, b, c, d )
|
|
#define StandoffMsg5( msg, a, b, c, d, e ) DevMsg( GetOuter(), msg, a, b, c, d, e )
|
|
#else
|
|
#define StandoffMsg( msg ) ((void)0)
|
|
#define StandoffMsg1( msg, a ) ((void)0)
|
|
#define StandoffMsg2( msg, a, b ) ((void)0)
|
|
#define StandoffMsg3( msg, a, b, c ) ((void)0)
|
|
#define StandoffMsg4( msg, a, b, c, d ) ((void)0)
|
|
#define StandoffMsg5( msg, a, b, c, d, e ) ((void)0)
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// CAI_BattleLine
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const float AIBL_THINK_INTERVAL = 0.3;
|
|
|
|
class CAI_BattleLine : public CBaseEntity
|
|
{
|
|
DECLARE_CLASS( CAI_BattleLine, CBaseEntity );
|
|
|
|
public:
|
|
string_t m_iszActor;
|
|
bool m_fActive;
|
|
bool m_fStrict;
|
|
|
|
void Spawn()
|
|
{
|
|
if ( m_fActive )
|
|
{
|
|
SetThink(&CAI_BattleLine::MovementThink);
|
|
SetNextThink( gpGlobals->curtime + AIBL_THINK_INTERVAL );
|
|
m_SelfMoveMonitor.SetMark( this, 60 );
|
|
}
|
|
}
|
|
|
|
virtual void InputActivate( inputdata_t &inputdata )
|
|
{
|
|
if ( !m_fActive )
|
|
{
|
|
m_fActive = true;
|
|
NotifyChangeTacticalConstraints();
|
|
|
|
SetThink(&CAI_BattleLine::MovementThink);
|
|
SetNextThink( gpGlobals->curtime + AIBL_THINK_INTERVAL );
|
|
m_SelfMoveMonitor.SetMark( this, 60 );
|
|
}
|
|
}
|
|
|
|
virtual void InputDeactivate( inputdata_t &inputdata )
|
|
{
|
|
if ( m_fActive )
|
|
{
|
|
m_fActive = false;
|
|
NotifyChangeTacticalConstraints();
|
|
|
|
SetThink(NULL);
|
|
}
|
|
}
|
|
|
|
void UpdateOnRemove()
|
|
{
|
|
if ( m_fActive )
|
|
{
|
|
m_fActive = false;
|
|
NotifyChangeTacticalConstraints();
|
|
}
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
bool Affects( CAI_BaseNPC *pNpc )
|
|
{
|
|
const char *pszNamedActor = STRING( m_iszActor );
|
|
|
|
if ( pNpc->NameMatches( pszNamedActor ) ||
|
|
pNpc->ClassMatches( pszNamedActor ) ||
|
|
( pNpc->GetSquad() && stricmp( pNpc->GetSquad()->GetName(), pszNamedActor ) == 0 ) )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MovementThink()
|
|
{
|
|
if ( m_SelfMoveMonitor.TargetMoved( this ) )
|
|
{
|
|
NotifyChangeTacticalConstraints();
|
|
m_SelfMoveMonitor.SetMark( this, 60 );
|
|
}
|
|
SetNextThink( gpGlobals->curtime + AIBL_THINK_INTERVAL );
|
|
}
|
|
|
|
private:
|
|
void NotifyChangeTacticalConstraints()
|
|
{
|
|
for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
|
|
{
|
|
CAI_BaseNPC *pNpc = (g_AI_Manager.AccessAIs())[i];
|
|
if ( Affects( pNpc ) )
|
|
{
|
|
CAI_StandoffBehavior *pBehavior;
|
|
if ( pNpc->GetBehavior( &pBehavior ) )
|
|
{
|
|
pBehavior->OnChangeTacticalConstraints();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CAI_MoveMonitor m_SelfMoveMonitor;
|
|
|
|
DECLARE_DATADESC();
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
LINK_ENTITY_TO_CLASS( ai_battle_line, CAI_BattleLine );
|
|
|
|
BEGIN_DATADESC( CAI_BattleLine )
|
|
DEFINE_KEYFIELD( m_iszActor, FIELD_STRING, "Actor" ),
|
|
DEFINE_KEYFIELD( m_fActive, FIELD_BOOLEAN, "Active" ),
|
|
DEFINE_KEYFIELD( m_fStrict, FIELD_BOOLEAN, "Strict" ),
|
|
DEFINE_EMBEDDED( m_SelfMoveMonitor ),
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
|
|
|
|
DEFINE_THINKFUNC( MovementThink ),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// CAI_StandoffBehavior
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BEGIN_SIMPLE_DATADESC( AI_StandoffParams_t )
|
|
DEFINE_FIELD( hintChangeReaction, FIELD_INTEGER ),
|
|
DEFINE_FIELD( fPlayerIsBattleline, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( fCoverOnReload, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( minTimeShots, FIELD_FLOAT ),
|
|
DEFINE_FIELD( maxTimeShots, FIELD_FLOAT ),
|
|
DEFINE_FIELD( minShots, FIELD_INTEGER ),
|
|
DEFINE_FIELD( maxShots, FIELD_INTEGER ),
|
|
DEFINE_FIELD( oddsCover, FIELD_INTEGER ),
|
|
DEFINE_FIELD( fStayAtCover, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( flAbandonTimeLimit, FIELD_FLOAT ),
|
|
END_DATADESC();
|
|
|
|
BEGIN_DATADESC( CAI_StandoffBehavior )
|
|
DEFINE_FIELD( m_fActive, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fTestNoDamage, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_vecStandoffGoalPosition, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_posture, FIELD_INTEGER ),
|
|
DEFINE_EMBEDDED( m_params ),
|
|
DEFINE_FIELD( m_hStandoffGoal, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_fTakeCover, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_SavedDistTooFar, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_fForceNewEnemy, FIELD_BOOLEAN ),
|
|
DEFINE_EMBEDDED( m_PlayerMoveMonitor ),
|
|
DEFINE_EMBEDDED( m_TimeForceCoverHint ),
|
|
DEFINE_EMBEDDED( m_TimePreventForceNewEnemy ),
|
|
DEFINE_EMBEDDED( m_RandomCoverChangeTimer ),
|
|
// m_UpdateBattleLinesSemaphore (not saved, only an in-think item)
|
|
// m_BattleLines (not saved, rebuilt)
|
|
DEFINE_FIELD( m_fIgnoreFronts, FIELD_BOOLEAN ),
|
|
// m_ActivityMap (not saved, rebuilt)
|
|
// m_bHasLowCoverActivity (not saved, rebuilt)
|
|
|
|
DEFINE_FIELD( m_nSavedMinShots, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_nSavedMaxShots, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flSavedMinRest, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flSavedMaxRest, FIELD_FLOAT ),
|
|
|
|
END_DATADESC();
|
|
|
|
//-------------------------------------
|
|
|
|
CAI_StandoffBehavior::CAI_StandoffBehavior( CAI_BaseNPC *pOuter )
|
|
: CAI_MappedActivityBehavior_Temporary( pOuter )
|
|
{
|
|
m_fActive = false;
|
|
SetParameters( AI_DEFAULT_STANDOFF_PARAMS );
|
|
SetPosture( AIP_STANDING );
|
|
m_SavedDistTooFar = FLT_MAX;
|
|
m_fForceNewEnemy = false;
|
|
m_TimePreventForceNewEnemy.Set( 3.0, 6.0 );
|
|
m_fIgnoreFronts = false;
|
|
m_bHasLowCoverActivity = false;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::SetActive( bool fActive )
|
|
{
|
|
if ( fActive != m_fActive )
|
|
{
|
|
if ( fActive )
|
|
{
|
|
GetOuter()->SpeakSentence( STANDOFF_SENTENCE_BEGIN_STANDOFF );
|
|
}
|
|
else
|
|
{
|
|
GetOuter()->SpeakSentence( STANDOFF_SENTENCE_END_STANDOFF );
|
|
}
|
|
|
|
m_fActive = fActive;
|
|
NotifyChangeBehaviorStatus();
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::SetParameters( const AI_StandoffParams_t ¶ms, CAI_GoalEntity *pGoalEntity )
|
|
{
|
|
m_params = params;
|
|
m_hStandoffGoal = pGoalEntity;
|
|
m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
|
|
if ( GetOuter() && GetOuter()->GetShotRegulator() )
|
|
{
|
|
GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
|
|
GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CAI_StandoffBehavior::CanSelectSchedule()
|
|
{
|
|
if ( !m_bHasLowCoverActivity )
|
|
m_fActive = false;
|
|
|
|
if ( !m_fActive )
|
|
return false;
|
|
|
|
return ( GetNpcState() == NPC_STATE_COMBAT && GetOuter()->GetActiveWeapon() != NULL );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::Spawn()
|
|
{
|
|
BaseClass::Spawn();
|
|
UpdateTranslateActivityMap();
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::BeginScheduleSelection()
|
|
{
|
|
m_fTakeCover = true;
|
|
|
|
// FIXME: Improve!!!
|
|
GetOuter()->GetShotRegulator()->GetBurstShotCountRange( &m_nSavedMinShots, &m_nSavedMaxShots );
|
|
GetOuter()->GetShotRegulator()->GetRestInterval( &m_flSavedMinRest, &m_flSavedMaxRest );
|
|
|
|
GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
|
|
GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
|
|
GetOuter()->GetShotRegulator()->Reset();
|
|
|
|
m_SavedDistTooFar = GetOuter()->m_flDistTooFar;
|
|
GetOuter()->m_flDistTooFar = FLT_MAX;
|
|
|
|
m_TimeForceCoverHint.Set( 8, false );
|
|
m_RandomCoverChangeTimer.Set( 8, 16, false );
|
|
UpdateTranslateActivityMap();
|
|
}
|
|
|
|
|
|
void CAI_StandoffBehavior::OnUpdateShotRegulator()
|
|
{
|
|
GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
|
|
GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
|
|
}
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::EndScheduleSelection()
|
|
{
|
|
UnlockHintNode();
|
|
|
|
m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
|
|
GetOuter()->m_flDistTooFar = m_SavedDistTooFar;
|
|
|
|
// FIXME: Improve!!!
|
|
GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_nSavedMinShots, m_nSavedMaxShots );
|
|
GetOuter()->GetShotRegulator()->SetRestInterval( m_flSavedMinRest, m_flSavedMaxRest );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::PrescheduleThink()
|
|
{
|
|
VPROF_BUDGET( "CAI_StandoffBehavior::PrescheduleThink", VPROF_BUDGETGROUP_NPCS );
|
|
|
|
BaseClass::PrescheduleThink();
|
|
|
|
if( DrawBattleLines.GetInt() != 0 )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
while ((pEntity = gEntList.FindEntityByClassname( pEntity, "ai_battle_line" )) != NULL)
|
|
{
|
|
// Visualize the battle line and its normal.
|
|
CAI_BattleLine *pLine = dynamic_cast<CAI_BattleLine *>(pEntity);
|
|
|
|
if( pLine->m_fActive )
|
|
{
|
|
Vector normal;
|
|
|
|
pLine->GetVectors( &normal, NULL, NULL );
|
|
|
|
NDebugOverlay::Line( pLine->GetAbsOrigin() - Vector( 0, 0, 64 ), pLine->GetAbsOrigin() + Vector(0,0,64), 0,255,0, false, 0.1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::GatherConditions()
|
|
{
|
|
CBaseEntity *pLeader = GetPlayerLeader();
|
|
if ( pLeader && m_TimeForceCoverHint.Expired() )
|
|
{
|
|
if ( m_PlayerMoveMonitor.IsMarkSet() )
|
|
{
|
|
if (m_PlayerMoveMonitor.TargetMoved( pLeader ) )
|
|
{
|
|
OnChangeTacticalConstraints();
|
|
m_PlayerMoveMonitor.ClearMark();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_PlayerMoveMonitor.SetMark( pLeader, 60 );
|
|
}
|
|
}
|
|
|
|
if ( m_fForceNewEnemy )
|
|
{
|
|
m_TimePreventForceNewEnemy.Reset();
|
|
GetOuter()->SetEnemy( NULL );
|
|
}
|
|
BaseClass::GatherConditions();
|
|
m_fForceNewEnemy = false;
|
|
|
|
ClearCondition( COND_ABANDON_TIME_EXPIRED );
|
|
|
|
bool bAbandonStandoff = false;
|
|
CAI_Squad *pSquad = GetOuter()->GetSquad();
|
|
AISquadIter_t iter;
|
|
if ( GetEnemy() )
|
|
{
|
|
AI_EnemyInfo_t *pEnemyInfo = GetOuter()->GetEnemies()->Find( GetEnemy() );
|
|
if ( pEnemyInfo &&
|
|
m_params.flAbandonTimeLimit > 0 &&
|
|
( ( pEnemyInfo->timeAtFirstHand != AI_INVALID_TIME &&
|
|
gpGlobals->curtime - pEnemyInfo->timeLastSeen > m_params.flAbandonTimeLimit ) ||
|
|
( pEnemyInfo->timeAtFirstHand == AI_INVALID_TIME &&
|
|
gpGlobals->curtime - pEnemyInfo->timeFirstSeen > m_params.flAbandonTimeLimit * 2 ) ) )
|
|
{
|
|
SetCondition( COND_ABANDON_TIME_EXPIRED );
|
|
|
|
bAbandonStandoff = true;
|
|
|
|
if ( pSquad )
|
|
{
|
|
for ( CAI_BaseNPC *pSquadMate = pSquad->GetFirstMember( &iter ); pSquadMate; pSquadMate = pSquad->GetNextMember( &iter ) )
|
|
{
|
|
if ( pSquadMate->IsAlive() && pSquadMate != GetOuter() )
|
|
{
|
|
CAI_StandoffBehavior *pSquadmateStandoff;
|
|
pSquadMate->GetBehavior( &pSquadmateStandoff );
|
|
if ( pSquadmateStandoff && pSquadmateStandoff->IsActive() &&
|
|
pSquadmateStandoff->m_hStandoffGoal == m_hStandoffGoal &&
|
|
!pSquadmateStandoff->HasCondition( COND_ABANDON_TIME_EXPIRED ) )
|
|
{
|
|
bAbandonStandoff = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bAbandonStandoff )
|
|
{
|
|
if ( pSquad )
|
|
{
|
|
for ( CAI_BaseNPC *pSquadMate = pSquad->GetFirstMember( &iter ); pSquadMate; pSquadMate = pSquad->GetNextMember( &iter ) )
|
|
{
|
|
CAI_StandoffBehavior *pSquadmateStandoff;
|
|
pSquadMate->GetBehavior( &pSquadmateStandoff );
|
|
if ( pSquadmateStandoff && pSquadmateStandoff->IsActive() && pSquadmateStandoff->m_hStandoffGoal == m_hStandoffGoal )
|
|
pSquadmateStandoff->SetActive( false );
|
|
}
|
|
}
|
|
else
|
|
SetActive( false );
|
|
}
|
|
else if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
|
|
{
|
|
if( DrawBattleLines.GetInt() != 0 )
|
|
{
|
|
if ( IsBehindBattleLines( GetAbsOrigin() ) )
|
|
{
|
|
NDebugOverlay::Box( GetOuter()->GetAbsOrigin(), -Vector(48,48,4), Vector(48,48,4), 255,0,0,8, 0.1 );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Box( GetOuter()->GetAbsOrigin(), -Vector(48,48,4), Vector(48,48,4), 0,255,0,8, 0.1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
int CAI_StandoffBehavior::SelectScheduleUpdateWeapon( void )
|
|
{
|
|
// Check if need to reload
|
|
if ( HasCondition ( COND_NO_PRIMARY_AMMO ) || HasCondition ( COND_LOW_PRIMARY_AMMO ))
|
|
{
|
|
StandoffMsg( "Out of ammo, reloading\n" );
|
|
if ( m_params.fCoverOnReload )
|
|
{
|
|
GetOuter()->SpeakSentence( STANDOFF_SENTENCE_OUT_OF_AMMO );
|
|
return SCHED_HIDE_AND_RELOAD;
|
|
}
|
|
|
|
return SCHED_RELOAD;
|
|
}
|
|
|
|
// Otherwise, update planned shots to fire before taking cover again
|
|
if ( HasCondition( COND_LIGHT_DAMAGE ) )
|
|
{
|
|
// if hurt:
|
|
int iPercent = random->RandomInt(0,99);
|
|
|
|
if ( iPercent <= m_params.oddsCover && GetEnemy() != NULL )
|
|
{
|
|
SetReuseCurrentCover();
|
|
StandoffMsg( "Hurt, firing one more shot before cover\n" );
|
|
if ( !GetOuter()->GetShotRegulator()->IsInRestInterval() )
|
|
{
|
|
GetOuter()->GetShotRegulator()->SetBurstShotsRemaining( 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
return SCHED_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
int CAI_StandoffBehavior::SelectScheduleCheckCover( void )
|
|
{
|
|
if ( m_fTakeCover )
|
|
{
|
|
m_fTakeCover = false;
|
|
if ( GetEnemy() )
|
|
{
|
|
GetOuter()->SpeakSentence( STANDOFF_SENTENCE_FORCED_TAKE_COVER );
|
|
StandoffMsg( "Taking forced cover\n" );
|
|
return SCHED_TAKE_COVER_FROM_ENEMY;
|
|
}
|
|
}
|
|
|
|
if ( GetOuter()->GetShotRegulator()->IsInRestInterval() )
|
|
{
|
|
StandoffMsg( "Regulated to not shoot\n" );
|
|
if ( GetHintType() == HINT_TACTICAL_COVER_LOW )
|
|
SetPosture( AIP_CROUCHING );
|
|
else
|
|
SetPosture( AIP_STANDING );
|
|
|
|
if ( random->RandomInt(0,99) < 80 )
|
|
SetReuseCurrentCover();
|
|
return SCHED_TAKE_COVER_FROM_ENEMY;
|
|
}
|
|
|
|
return SCHED_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
int CAI_StandoffBehavior::SelectScheduleEstablishAim( void )
|
|
{
|
|
if ( HasCondition( COND_ENEMY_OCCLUDED ) )
|
|
{
|
|
if ( GetPosture() == AIP_CROUCHING )
|
|
{
|
|
// force a stand up, just in case
|
|
GetOuter()->SpeakSentence( STANDOFF_SENTENCE_STAND_CHECK_TARGET );
|
|
StandoffMsg( "Crouching, standing up to gain LOS\n" );
|
|
SetPosture( AIP_PEEKING );
|
|
return SCHED_STANDOFF;
|
|
}
|
|
else if ( GetPosture() == AIP_PEEKING )
|
|
{
|
|
if ( m_TimePreventForceNewEnemy.Expired() )
|
|
{
|
|
// Look for a new enemy
|
|
m_fForceNewEnemy = true;
|
|
StandoffMsg( "Looking for enemy\n" );
|
|
}
|
|
}
|
|
#if 0
|
|
else
|
|
{
|
|
return SCHED_ESTABLISH_LINE_OF_FIRE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return SCHED_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
int CAI_StandoffBehavior::SelectScheduleAttack( void )
|
|
{
|
|
if ( GetPosture() == AIP_PEEKING || GetPosture() == AIP_STANDING )
|
|
{
|
|
if ( !HasCondition( COND_CAN_RANGE_ATTACK1 ) &&
|
|
!HasCondition( COND_CAN_MELEE_ATTACK1 ) &&
|
|
HasCondition( COND_TOO_FAR_TO_ATTACK ) )
|
|
{
|
|
if ( GetOuter()->GetActiveWeapon() && ( GetOuter()->GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 ) )
|
|
{
|
|
if ( !HasCondition( COND_ENEMY_OCCLUDED ) || random->RandomInt(0,99) < 50 )
|
|
// Don't advance, just fire anyway
|
|
return SCHED_RANGE_ATTACK1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SCHED_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
int CAI_StandoffBehavior::SelectSchedule( void )
|
|
{
|
|
switch ( GetNpcState() )
|
|
{
|
|
case NPC_STATE_COMBAT:
|
|
{
|
|
int schedule = SCHED_NONE;
|
|
|
|
schedule = SelectScheduleUpdateWeapon();
|
|
if ( schedule != SCHED_NONE )
|
|
return schedule;
|
|
|
|
schedule = SelectScheduleCheckCover();
|
|
if ( schedule != SCHED_NONE )
|
|
return schedule;
|
|
|
|
schedule = SelectScheduleEstablishAim();
|
|
if ( schedule != SCHED_NONE )
|
|
return schedule;
|
|
|
|
schedule = SelectScheduleAttack();
|
|
if ( schedule != SCHED_NONE )
|
|
return schedule;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
int CAI_StandoffBehavior::TranslateSchedule( int schedule )
|
|
{
|
|
if ( schedule == SCHED_CHASE_ENEMY )
|
|
{
|
|
StandoffMsg( "trying SCHED_ESTABLISH_LINE_OF_FIRE\n" );
|
|
return SCHED_ESTABLISH_LINE_OF_FIRE;
|
|
}
|
|
return BaseClass::TranslateSchedule( schedule );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::BuildScheduleTestBits()
|
|
{
|
|
BaseClass::BuildScheduleTestBits();
|
|
|
|
if ( IsCurSchedule( SCHED_TAKE_COVER_FROM_ENEMY ) )
|
|
GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
Activity CAI_MappedActivityBehavior_Temporary::GetMappedActivity( AI_Posture_t posture, Activity activity )
|
|
{
|
|
if ( posture != AIP_STANDING )
|
|
{
|
|
unsigned short iActivityTranslation = m_ActivityMap.Find( MAKE_ACTMAP_KEY( posture, activity ) );
|
|
if ( iActivityTranslation != m_ActivityMap.InvalidIndex() )
|
|
{
|
|
Activity result = m_ActivityMap[iActivityTranslation];
|
|
return result;
|
|
}
|
|
}
|
|
return ACT_INVALID;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
Activity CAI_StandoffBehavior::NPC_TranslateActivity( Activity activity )
|
|
{
|
|
Activity coverActivity = GetCoverActivity();
|
|
if ( coverActivity != ACT_INVALID )
|
|
{
|
|
if ( activity == ACT_IDLE )
|
|
activity = coverActivity;
|
|
if ( GetPosture() == AIP_STANDING && coverActivity == ACT_COVER_LOW )
|
|
SetPosture( AIP_CROUCHING );
|
|
}
|
|
|
|
Activity result = GetMappedActivity( GetPosture(), activity );
|
|
if ( result != ACT_INVALID)
|
|
return result;
|
|
|
|
return BaseClass::NPC_TranslateActivity( activity );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &vecPos -
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_StandoffBehavior::SetStandoffGoalPosition( const Vector &vecPos )
|
|
{
|
|
m_vecStandoffGoalPosition = vecPos;
|
|
UpdateBattleLines();
|
|
OnChangeTacticalConstraints();
|
|
GetOuter()->ClearSchedule( "Standoff goal position changed" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &vecPos -
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_StandoffBehavior::ClearStandoffGoalPosition()
|
|
{
|
|
if ( m_vecStandoffGoalPosition != GOAL_POSITION_INVALID )
|
|
{
|
|
m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
|
|
UpdateBattleLines();
|
|
OnChangeTacticalConstraints();
|
|
GetOuter()->ClearSchedule( "Standoff goal position cleared" );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Vector
|
|
//-----------------------------------------------------------------------------
|
|
Vector CAI_StandoffBehavior::GetStandoffGoalPosition()
|
|
{
|
|
if( m_vecStandoffGoalPosition != GOAL_POSITION_INVALID )
|
|
{
|
|
return m_vecStandoffGoalPosition;
|
|
}
|
|
else if( PlayerIsLeading() )
|
|
{
|
|
return UTIL_GetLocalPlayer()->GetAbsOrigin();
|
|
}
|
|
else
|
|
{
|
|
CAI_BattleLine *pBattleLine = NULL;
|
|
for (;;)
|
|
{
|
|
pBattleLine = (CAI_BattleLine *)gEntList.FindEntityByClassname( pBattleLine, "ai_battle_line" );
|
|
|
|
if ( !pBattleLine )
|
|
break;
|
|
|
|
if ( pBattleLine->m_fActive && pBattleLine->Affects( GetOuter() ) )
|
|
{
|
|
StandoffMsg1( "Using battleline %s as goal\n", STRING( pBattleLine->GetEntityName() ) );
|
|
return pBattleLine->GetAbsOrigin();
|
|
}
|
|
}
|
|
}
|
|
|
|
return GetAbsOrigin();
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::UpdateBattleLines()
|
|
{
|
|
if ( m_UpdateBattleLinesSemaphore.EnterThink() )
|
|
{
|
|
// @TODO (toml 06-19-03): This is the quick to code thing. Could use some optimization/caching to not recalc everything (up to) each think
|
|
m_BattleLines.RemoveAll();
|
|
|
|
bool bHaveGoalPosition = ( m_vecStandoffGoalPosition != GOAL_POSITION_INVALID );
|
|
|
|
if ( bHaveGoalPosition )
|
|
{
|
|
// If we have a valid standoff goal position, it takes precendence.
|
|
const float DIST_GOAL_PLANE = 180;
|
|
|
|
BattleLine_t goalLine;
|
|
|
|
if ( GetDirectionOfStandoff( &goalLine.normal ) )
|
|
{
|
|
goalLine.point = GetStandoffGoalPosition() + goalLine.normal * DIST_GOAL_PLANE;
|
|
m_BattleLines.AddToTail( goalLine );
|
|
}
|
|
}
|
|
else if ( PlayerIsLeading() && GetEnemy() )
|
|
{
|
|
if ( m_params.fPlayerIsBattleline )
|
|
{
|
|
const float DIST_PLAYER_PLANE = 180;
|
|
CBaseEntity *pPlayer = UTIL_GetLocalPlayer();
|
|
|
|
BattleLine_t playerLine;
|
|
|
|
if ( GetDirectionOfStandoff( &playerLine.normal ) )
|
|
{
|
|
playerLine.point = pPlayer->GetAbsOrigin() + playerLine.normal * DIST_PLAYER_PLANE;
|
|
m_BattleLines.AddToTail( playerLine );
|
|
}
|
|
}
|
|
}
|
|
|
|
CAI_BattleLine *pBattleLine = NULL;
|
|
for (;;)
|
|
{
|
|
pBattleLine = (CAI_BattleLine *)gEntList.FindEntityByClassname( pBattleLine, "ai_battle_line" );
|
|
|
|
if ( !pBattleLine )
|
|
break;
|
|
|
|
if ( pBattleLine->m_fActive && (pBattleLine->m_fStrict || !bHaveGoalPosition ) && pBattleLine->Affects( GetOuter() ) )
|
|
{
|
|
BattleLine_t battleLine;
|
|
|
|
battleLine.point = pBattleLine->GetAbsOrigin();
|
|
battleLine.normal = UTIL_YawToVector( pBattleLine->GetAbsAngles().y );
|
|
|
|
m_BattleLines.AddToTail( battleLine );
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CAI_StandoffBehavior::IsBehindBattleLines( const Vector &point )
|
|
{
|
|
UpdateBattleLines();
|
|
|
|
Vector vecToPoint;
|
|
|
|
for ( int i = 0; i < m_BattleLines.Count(); i++ )
|
|
{
|
|
vecToPoint = point - m_BattleLines[i].point;
|
|
VectorNormalize( vecToPoint );
|
|
vecToPoint.z = 0;
|
|
|
|
if ( DotProduct( m_BattleLines[i].normal, vecToPoint ) > 0 )
|
|
{
|
|
if( DrawBattleLines.GetInt() != 0 )
|
|
{
|
|
NDebugOverlay::Box( point, -Vector(48,48,4), Vector(48,48,4), 0,255,0,8, 1 );
|
|
NDebugOverlay::Line( point, GetOuter()->GetAbsOrigin(), 0,255,0,true, 1 );
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( DrawBattleLines.GetInt() != 0 )
|
|
{
|
|
NDebugOverlay::Box( point, -Vector(48,48,4), Vector(48,48,4), 255,0,0,8, 1 );
|
|
NDebugOverlay::Line( point, GetOuter()->GetAbsOrigin(), 255,0,0,true, 1 );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CAI_StandoffBehavior::IsValidCover( const Vector &vecCoverLocation, const CAI_Hint *pHint )
|
|
{
|
|
if ( !BaseClass::IsValidCover( vecCoverLocation, pHint ) )
|
|
return false;
|
|
|
|
if ( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) )
|
|
return true;
|
|
|
|
return ( m_fIgnoreFronts || IsBehindBattleLines( vecCoverLocation ) );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CAI_StandoffBehavior::IsValidShootPosition( const Vector &vLocation, CAI_Node *pNode, const CAI_Hint *pHint )
|
|
{
|
|
if ( !BaseClass::IsValidShootPosition( vLocation, pNode, pHint ) )
|
|
return false;
|
|
|
|
return ( m_fIgnoreFronts || IsBehindBattleLines( vLocation ) );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::StartTask( const Task_t *pTask )
|
|
{
|
|
bool fCallBase = false;
|
|
|
|
switch ( pTask->iTask )
|
|
{
|
|
case TASK_RANGE_ATTACK1:
|
|
{
|
|
fCallBase = true;
|
|
break;
|
|
}
|
|
|
|
case TASK_FIND_COVER_FROM_ENEMY:
|
|
{
|
|
StandoffMsg( "TASK_FIND_COVER_FROM_ENEMY\n" );
|
|
|
|
// If within time window to force change
|
|
if ( !m_params.fStayAtCover && (!m_TimeForceCoverHint.Expired() || m_RandomCoverChangeTimer.Expired()) )
|
|
{
|
|
m_TimeForceCoverHint.Force();
|
|
m_RandomCoverChangeTimer.Set( 8, 16, false );
|
|
|
|
// @TODO (toml 03-24-03): clean this up be tool-izing base tasks. Right now, this is here to force to not use lateral cover search
|
|
CBaseEntity *pEntity = GetEnemy();
|
|
|
|
if ( pEntity == NULL )
|
|
{
|
|
// Find cover from self if no enemy available
|
|
pEntity = GetOuter();
|
|
}
|
|
|
|
CBaseEntity *pLeader = GetPlayerLeader();
|
|
if ( pLeader )
|
|
{
|
|
m_PlayerMoveMonitor.SetMark( pLeader, 60 );
|
|
}
|
|
|
|
Vector coverPos = vec3_origin;
|
|
CAI_TacticalServices * pTacticalServices = GetTacticalServices();
|
|
const Vector & enemyPos = pEntity->GetAbsOrigin();
|
|
Vector enemyEyePos = pEntity->EyePosition();
|
|
float coverRadius = GetOuter()->CoverRadius();
|
|
const Vector & goalPos = GetStandoffGoalPosition();
|
|
bool bTryGoalPosFirst = true;
|
|
|
|
if( pLeader && m_vecStandoffGoalPosition == GOAL_POSITION_INVALID )
|
|
{
|
|
if( random->RandomInt(1, 100) <= 50 )
|
|
{
|
|
// Half the time, if the player is leading, try to find a spot near them
|
|
bTryGoalPosFirst = false;
|
|
StandoffMsg( "Not trying goal pos\n" );
|
|
}
|
|
}
|
|
|
|
if( bTryGoalPosFirst )
|
|
{
|
|
// Firstly, try to find cover near the goal position.
|
|
pTacticalServices->FindCoverPos( goalPos, enemyPos, enemyEyePos, 0, 15*12, &coverPos );
|
|
|
|
if ( coverPos == vec3_origin )
|
|
pTacticalServices->FindCoverPos( goalPos, enemyPos, enemyEyePos, 15*12-0.1, 40*12, &coverPos );
|
|
|
|
StandoffMsg1( "Trying goal pos, %s\n", ( coverPos == vec3_origin ) ? "failed" : "succeeded" );
|
|
}
|
|
|
|
if ( coverPos == vec3_origin )
|
|
{
|
|
// Otherwise, find a node near to self
|
|
StandoffMsg( "Looking for near cover\n" );
|
|
if ( !GetTacticalServices()->FindCoverPos( enemyPos, enemyEyePos, 0, coverRadius, &coverPos ) )
|
|
{
|
|
// Try local lateral cover
|
|
if ( !GetTacticalServices()->FindLateralCover( enemyEyePos, 0, &coverPos ) )
|
|
{
|
|
// At this point, try again ignoring front lines. Any cover probably better than hanging out in the open
|
|
m_fIgnoreFronts = true;
|
|
if ( !GetTacticalServices()->FindCoverPos( enemyPos, enemyEyePos, 0, coverRadius, &coverPos ) )
|
|
{
|
|
if ( !GetTacticalServices()->FindLateralCover( enemyEyePos, 0, &coverPos ) )
|
|
{
|
|
Assert( coverPos == vec3_origin );
|
|
}
|
|
}
|
|
m_fIgnoreFronts = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( coverPos != vec3_origin )
|
|
{
|
|
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
|
|
{
|
|
fCallBase = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
fCallBase = true;
|
|
}
|
|
}
|
|
|
|
if ( fCallBase )
|
|
BaseClass::StartTask( pTask );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::OnChangeHintGroup( string_t oldGroup, string_t newGroup )
|
|
{
|
|
OnChangeTacticalConstraints();
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::OnChangeTacticalConstraints()
|
|
{
|
|
if ( m_params.hintChangeReaction > AIHCR_DEFAULT_AI )
|
|
m_TimeForceCoverHint.Set( 8.0, false );
|
|
if ( m_params.hintChangeReaction == AIHCR_MOVE_IMMEDIATE )
|
|
m_fTakeCover = true;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CAI_StandoffBehavior::PlayerIsLeading()
|
|
{
|
|
CBaseEntity *pPlayer = AI_GetSinglePlayer();
|
|
return ( pPlayer && GetOuter()->IRelationType( pPlayer ) == D_LI );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
CBaseEntity *CAI_StandoffBehavior::GetPlayerLeader()
|
|
{
|
|
CBaseEntity *pPlayer = AI_GetSinglePlayer();
|
|
if ( pPlayer && GetOuter()->IRelationType( pPlayer ) == D_LI )
|
|
return pPlayer;
|
|
return NULL;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CAI_StandoffBehavior::GetDirectionOfStandoff( Vector *pDir )
|
|
{
|
|
if ( GetEnemy() )
|
|
{
|
|
*pDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
|
|
VectorNormalize( *pDir );
|
|
pDir->z = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
Hint_e CAI_StandoffBehavior::GetHintType()
|
|
{
|
|
CAI_Hint *pHintNode = GetHintNode();
|
|
if ( pHintNode )
|
|
return pHintNode->HintType();
|
|
return HINT_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::SetReuseCurrentCover()
|
|
{
|
|
CAI_Hint *pHintNode = GetHintNode();
|
|
if ( pHintNode && pHintNode->GetNode() && pHintNode->GetNode()->IsLocked() )
|
|
pHintNode->GetNode()->Unlock();
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::UnlockHintNode()
|
|
{
|
|
CAI_Hint *pHintNode = GetHintNode();
|
|
if ( pHintNode )
|
|
{
|
|
if ( pHintNode->IsLocked() && pHintNode->IsLockedBy( GetOuter() ) )
|
|
pHintNode->Unlock();
|
|
CAI_Node *pNode = pHintNode->GetNode();
|
|
if ( pNode && pNode->IsLocked() )
|
|
pNode->Unlock();
|
|
ClearHintNode();
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
Activity CAI_StandoffBehavior::GetCoverActivity()
|
|
{
|
|
CAI_Hint *pHintNode = GetHintNode();
|
|
if ( pHintNode && pHintNode->HintType() == HINT_TACTICAL_COVER_LOW )
|
|
return GetOuter()->GetCoverActivity( pHintNode );
|
|
return ACT_INVALID;
|
|
}
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
struct AI_ActivityMapping_t
|
|
{
|
|
AI_Posture_t posture;
|
|
Activity activity;
|
|
const char * pszWeapon;
|
|
Activity translation;
|
|
};
|
|
|
|
void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap()
|
|
{
|
|
AI_ActivityMapping_t mappings[] = // This array cannot be static, as some activity values are set on a per-map-load basis
|
|
{
|
|
{ AIP_CROUCHING, ACT_IDLE, NULL, ACT_COVER_LOW, },
|
|
{ AIP_CROUCHING, ACT_IDLE_ANGRY, NULL, ACT_COVER_LOW, },
|
|
{ AIP_CROUCHING, ACT_WALK, NULL, ACT_WALK_CROUCH, },
|
|
{ AIP_CROUCHING, ACT_RUN, NULL, ACT_RUN_CROUCH, },
|
|
{ AIP_CROUCHING, ACT_WALK_AIM, NULL, ACT_WALK_CROUCH_AIM, },
|
|
{ AIP_CROUCHING, ACT_RUN_AIM, NULL, ACT_RUN_CROUCH_AIM, },
|
|
{ AIP_CROUCHING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, },
|
|
{ AIP_CROUCHING, ACT_RANGE_ATTACK_SMG1, NULL, ACT_RANGE_ATTACK_SMG1_LOW, },
|
|
{ AIP_CROUCHING, ACT_RANGE_ATTACK_AR2, NULL, ACT_RANGE_ATTACK_AR2_LOW, },
|
|
|
|
//----
|
|
{ AIP_PEEKING, ACT_IDLE, NULL, ACT_RANGE_AIM_LOW, },
|
|
{ AIP_PEEKING, ACT_IDLE_ANGRY, NULL, ACT_RANGE_AIM_LOW, },
|
|
{ AIP_PEEKING, ACT_COVER_LOW, NULL, ACT_RANGE_AIM_LOW, },
|
|
{ AIP_PEEKING, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_LOW, },
|
|
{ AIP_PEEKING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, },
|
|
};
|
|
|
|
m_ActivityMap.RemoveAll();
|
|
|
|
CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon();
|
|
const char *pszWeaponClass = ( pWeapon ) ? pWeapon->GetClassname() : "";
|
|
for ( int i = 0; i < ARRAYSIZE(mappings); i++ )
|
|
{
|
|
if ( !mappings[i].pszWeapon || stricmp( mappings[i].pszWeapon, pszWeaponClass ) == 0 )
|
|
{
|
|
if ( HaveSequenceForActivity( mappings[i].translation ) || HaveSequenceForActivity( GetOuter()->Weapon_TranslateActivity( mappings[i].translation ) ) )
|
|
{
|
|
Assert( m_ActivityMap.Find( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ) ) == m_ActivityMap.InvalidIndex() );
|
|
m_ActivityMap.Insert( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ), mappings[i].translation );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CAI_StandoffBehavior::UpdateTranslateActivityMap()
|
|
{
|
|
BaseClass::UpdateTranslateActivityMap();
|
|
|
|
Activity lowCoverActivity = GetMappedActivity( AIP_CROUCHING, ACT_COVER_LOW );
|
|
if ( lowCoverActivity == ACT_INVALID )
|
|
lowCoverActivity = ACT_COVER_LOW;
|
|
|
|
m_bHasLowCoverActivity = ( ( CapabilitiesGet() & bits_CAP_DUCK ) && (GetOuter()->TranslateActivity( lowCoverActivity ) != ACT_INVALID));
|
|
|
|
CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon();
|
|
if ( pWeapon && (GetOuter()->TranslateActivity( lowCoverActivity ) == ACT_INVALID ))
|
|
DevMsg( "Note: NPC class %s lacks ACT_COVER_LOW, therefore cannot participate in standoff\n", GetOuter()->GetClassname() );
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_MappedActivityBehavior_Temporary::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
|
|
{
|
|
UpdateTranslateActivityMap();
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_StandoffBehavior::OnRestore()
|
|
{
|
|
UpdateTranslateActivityMap();
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER(CAI_StandoffBehavior)
|
|
|
|
DECLARE_CONDITION( COND_ABANDON_TIME_EXPIRED )
|
|
|
|
AI_END_CUSTOM_SCHEDULE_PROVIDER()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// CAI_StandoffGoal
|
|
//
|
|
// Purpose: A level tool to control the standoff behavior. Use is not required
|
|
// in order to use behavior.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
AI_StandoffParams_t g_StandoffParamsByAgression[] =
|
|
{
|
|
// hintChangeReaction, fCoverOnReload, PlayerBtlLn, minTimeShots, maxTimeShots, minShots, maxShots, oddsCover flAbandonTimeLimit
|
|
{ AIHCR_MOVE_ON_COVER, true, true, 4.0, 8.0, 2, 4, 50, false, 30 }, // AGGR_VERY_LOW
|
|
{ AIHCR_MOVE_ON_COVER, true, true, 2.0, 5.0, 3, 5, 25, false, 20 }, // AGGR_LOW
|
|
{ AIHCR_MOVE_ON_COVER, true, true, 0.6, 2.5, 3, 6, 25, false, 10 }, // AGGR_MEDIUM
|
|
{ AIHCR_MOVE_ON_COVER, true, true, 0.2, 1.5, 5, 8, 10, false, 10 }, // AGGR_HIGH
|
|
{ AIHCR_MOVE_ON_COVER, false, true, 0, 0, 100, 100, 0, false, 5 }, // AGGR_VERY_HIGH
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
class CAI_StandoffGoal : public CAI_GoalEntity
|
|
{
|
|
DECLARE_CLASS( CAI_StandoffGoal, CAI_GoalEntity );
|
|
|
|
public:
|
|
CAI_StandoffGoal()
|
|
{
|
|
m_aggressiveness = AGGR_MEDIUM;
|
|
m_fPlayerIsBattleline = true;
|
|
m_HintChangeReaction = AIHCR_DEFAULT_AI;
|
|
m_fStayAtCover = false;
|
|
m_bAbandonIfEnemyHides = false;
|
|
m_customParams = AI_DEFAULT_STANDOFF_PARAMS;
|
|
}
|
|
|
|
//---------------------------------
|
|
|
|
void EnableGoal( CAI_BaseNPC *pAI )
|
|
{
|
|
CAI_StandoffBehavior *pBehavior;
|
|
if ( !pAI->GetBehavior( &pBehavior ) )
|
|
return;
|
|
|
|
pBehavior->SetActive( true );
|
|
SetBehaviorParams( pBehavior);
|
|
}
|
|
|
|
void DisableGoal( CAI_BaseNPC *pAI )
|
|
{
|
|
// @TODO (toml 04-07-03): remove the no damage spawn flag once stable. The implementation isn't very good.
|
|
CAI_StandoffBehavior *pBehavior;
|
|
if ( !pAI->GetBehavior( &pBehavior ) )
|
|
return;
|
|
pBehavior->SetActive( false );
|
|
SetBehaviorParams( pBehavior);
|
|
}
|
|
|
|
void InputActivate( inputdata_t &inputdata )
|
|
{
|
|
ValidateAggression();
|
|
BaseClass::InputActivate( inputdata );
|
|
}
|
|
|
|
void InputDeactivate( inputdata_t &inputdata )
|
|
{
|
|
ValidateAggression();
|
|
BaseClass::InputDeactivate( inputdata );
|
|
}
|
|
|
|
void InputSetAggressiveness( inputdata_t &inputdata )
|
|
{
|
|
int newVal = inputdata.value.Int();
|
|
|
|
m_aggressiveness = (Aggressiveness_t)newVal;
|
|
ValidateAggression();
|
|
|
|
UpdateActors();
|
|
|
|
const CUtlVector<AIHANDLE> &actors = AccessActors();
|
|
for ( int i = 0; i < actors.Count(); i++ )
|
|
{
|
|
CAI_BaseNPC *pAI = actors[i];
|
|
CAI_StandoffBehavior *pBehavior;
|
|
if ( !pAI->GetBehavior( &pBehavior ) )
|
|
continue;
|
|
SetBehaviorParams( pBehavior);
|
|
}
|
|
}
|
|
|
|
void SetBehaviorParams( CAI_StandoffBehavior *pBehavior )
|
|
{
|
|
AI_StandoffParams_t params;
|
|
|
|
if ( m_aggressiveness != AGGR_CUSTOM )
|
|
params = g_StandoffParamsByAgression[m_aggressiveness];
|
|
else
|
|
params = m_customParams;
|
|
|
|
params.hintChangeReaction = m_HintChangeReaction;
|
|
params.fPlayerIsBattleline = m_fPlayerIsBattleline;
|
|
params.fStayAtCover = m_fStayAtCover;
|
|
if ( !m_bAbandonIfEnemyHides )
|
|
params.flAbandonTimeLimit = 0;
|
|
|
|
pBehavior->SetParameters( params, this );
|
|
pBehavior->OnChangeTacticalConstraints();
|
|
if ( pBehavior->IsRunning() )
|
|
pBehavior->GetOuter()->ClearSchedule( "Standoff behavior parms changed" );
|
|
}
|
|
|
|
void ValidateAggression()
|
|
{
|
|
if ( m_aggressiveness < AGGR_VERY_LOW || m_aggressiveness > AGGR_VERY_HIGH )
|
|
{
|
|
if ( m_aggressiveness != AGGR_CUSTOM )
|
|
{
|
|
DevMsg( "Invalid aggressiveness value %d\n", m_aggressiveness );
|
|
|
|
if ( m_aggressiveness < AGGR_VERY_LOW )
|
|
m_aggressiveness = AGGR_VERY_LOW;
|
|
else if ( m_aggressiveness > AGGR_VERY_HIGH )
|
|
m_aggressiveness = AGGR_VERY_HIGH;
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
//---------------------------------
|
|
|
|
DECLARE_DATADESC();
|
|
|
|
enum Aggressiveness_t
|
|
{
|
|
AGGR_VERY_LOW,
|
|
AGGR_LOW,
|
|
AGGR_MEDIUM,
|
|
AGGR_HIGH,
|
|
AGGR_VERY_HIGH,
|
|
|
|
AGGR_CUSTOM,
|
|
};
|
|
|
|
Aggressiveness_t m_aggressiveness;
|
|
AI_HintChangeReaction_t m_HintChangeReaction;
|
|
bool m_fPlayerIsBattleline;
|
|
bool m_fStayAtCover;
|
|
bool m_bAbandonIfEnemyHides;
|
|
AI_StandoffParams_t m_customParams;
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
LINK_ENTITY_TO_CLASS( ai_goal_standoff, CAI_StandoffGoal );
|
|
|
|
BEGIN_DATADESC( CAI_StandoffGoal )
|
|
DEFINE_KEYFIELD( m_aggressiveness, FIELD_INTEGER, "Aggressiveness" ),
|
|
// m_customParams (individually)
|
|
DEFINE_KEYFIELD( m_HintChangeReaction, FIELD_INTEGER, "HintGroupChangeReaction" ),
|
|
DEFINE_KEYFIELD( m_fPlayerIsBattleline, FIELD_BOOLEAN, "PlayerBattleline" ),
|
|
DEFINE_KEYFIELD( m_fStayAtCover, FIELD_BOOLEAN, "StayAtCover" ),
|
|
DEFINE_KEYFIELD( m_bAbandonIfEnemyHides, FIELD_BOOLEAN, "AbandonIfEnemyHides" ),
|
|
DEFINE_KEYFIELD( m_customParams.fCoverOnReload, FIELD_BOOLEAN, "CustomCoverOnReload" ),
|
|
DEFINE_KEYFIELD( m_customParams.minTimeShots, FIELD_FLOAT, "CustomMinTimeShots" ),
|
|
DEFINE_KEYFIELD( m_customParams.maxTimeShots, FIELD_FLOAT, "CustomMaxTimeShots" ),
|
|
DEFINE_KEYFIELD( m_customParams.minShots, FIELD_INTEGER, "CustomMinShots" ),
|
|
DEFINE_KEYFIELD( m_customParams.maxShots, FIELD_INTEGER, "CustomMaxShots" ),
|
|
DEFINE_KEYFIELD( m_customParams.oddsCover, FIELD_INTEGER, "CustomOddsCover" ),
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetAggressiveness", InputSetAggressiveness ),
|
|
END_DATADESC()
|
|
|
|
///-----------------------------------------------------------------------------
|