From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/server/ai_behavior_standoff.cpp | 1336 +++++++++++++++++++++++++++ 1 file changed, 1336 insertions(+) create mode 100644 mp/src/game/server/ai_behavior_standoff.cpp (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 new file mode 100644 index 00000000..8254f4e8 --- /dev/null +++ b/mp/src/game/server/ai_behavior_standoff.cpp @@ -0,0 +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() + +///----------------------------------------------------------------------------- -- cgit v1.2.3