From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/ai_behavior_standoff.cpp | 2672 +++++++++++++-------------- 1 file changed, 1336 insertions(+), 1336 deletions(-) (limited to 'mp/src/game/server/ai_behavior_standoff.cpp') diff --git a/mp/src/game/server/ai_behavior_standoff.cpp b/mp/src/game/server/ai_behavior_standoff.cpp index 8254f4e8..4f5f7469 100644 --- a/mp/src/game/server/ai_behavior_standoff.cpp +++ b/mp/src/game/server/ai_behavior_standoff.cpp @@ -1,1336 +1,1336 @@ -//========= 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, 1.5, 2.5, 1, 3, 25, 0 }; - -#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(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 &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() - -///----------------------------------------------------------------------------- +//========= 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, 1.5, 2.5, 1, 3, 25, 0 }; + +#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(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 &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() + +///----------------------------------------------------------------------------- -- cgit v1.2.3