text: //========= 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 &params, CAI_GoalEntity *pGoalEntity )
  {
  	m_params = params;
  	m_hStandoffGoal = pGoalEntity;
  	m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
  	if ( GetOuter() && GetOuter()->GetShotRegulator() )
  	{
  		GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
  		GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
  	}
  }
  
  //-------------------------------------
  
  bool CAI_StandoffBehavior::CanSelectSchedule()
  {
  	if ( !m_bHasLowCoverActivity )
  		m_fActive = false;
  
  	if ( !m_fActive )
  		return false;
  		
  	return ( GetNpcState() == NPC_STATE_COMBAT && GetOuter()->GetActiveWeapon() != NULL );
  }
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::Spawn()
  {
  	BaseClass::Spawn();
  	UpdateTranslateActivityMap();
  }
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::BeginScheduleSelection()
  {
  	m_fTakeCover = true;
  
  	// FIXME: Improve!!!
  	GetOuter()->GetShotRegulator()->GetBurstShotCountRange( &m_nSavedMinShots, &m_nSavedMaxShots );
  	GetOuter()->GetShotRegulator()->GetRestInterval( &m_flSavedMinRest, &m_flSavedMaxRest );
  
  	GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
  	GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
  	GetOuter()->GetShotRegulator()->Reset();
  
  	m_SavedDistTooFar = GetOuter()->m_flDistTooFar;
  	GetOuter()->m_flDistTooFar = FLT_MAX;
  	
  	m_TimeForceCoverHint.Set( 8, false );
  	m_RandomCoverChangeTimer.Set( 8, 16, false );
  	UpdateTranslateActivityMap();
  }
  
  
  void CAI_StandoffBehavior::OnUpdateShotRegulator()
  {
  	GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
  	GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
  }
  
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::EndScheduleSelection()
  {
  	UnlockHintNode();
  
  	m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
  	GetOuter()->m_flDistTooFar = m_SavedDistTooFar;
  
  	// FIXME: Improve!!!
  	GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_nSavedMinShots, m_nSavedMaxShots );
  	GetOuter()->GetShotRegulator()->SetRestInterval( m_flSavedMinRest, m_flSavedMaxRest );
  }
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::PrescheduleThink()
  {
  	VPROF_BUDGET( "CAI_StandoffBehavior::PrescheduleThink", VPROF_BUDGETGROUP_NPCS );
  
  	BaseClass::PrescheduleThink();
  	
  	if( DrawBattleLines.GetInt() != 0 )
  	{
  		CBaseEntity *pEntity = NULL;
  		while ((pEntity = gEntList.FindEntityByClassname( pEntity, "ai_battle_line" )) != NULL)
  		{
  			// Visualize the battle line and its normal.
  			CAI_BattleLine *pLine = dynamic_cast<CAI_BattleLine *>(pEntity);
  
  			if( pLine->m_fActive )
  			{
  				Vector normal;
  
  				pLine->GetVectors( &normal, NULL, NULL );
  
  				NDebugOverlay::Line( pLine->GetAbsOrigin() - Vector( 0, 0, 64 ), pLine->GetAbsOrigin() + Vector(0,0,64), 0,255,0, false, 0.1 );
  			}
  		}
  	}
  }
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::GatherConditions()
  {
  	CBaseEntity *pLeader = GetPlayerLeader();
  	if ( pLeader && m_TimeForceCoverHint.Expired() )
  	{
  		if ( m_PlayerMoveMonitor.IsMarkSet() )
  		{
  			if (m_PlayerMoveMonitor.TargetMoved( pLeader ) )
  			{
  				OnChangeTacticalConstraints();
  				m_PlayerMoveMonitor.ClearMark();
  			}
  		}
  		else
  		{
  			m_PlayerMoveMonitor.SetMark( pLeader, 60 );
  		}
  	}
  
  	if ( m_fForceNewEnemy )
  	{
  		m_TimePreventForceNewEnemy.Reset();
  		GetOuter()->SetEnemy( NULL );
  	}
  	BaseClass::GatherConditions();
  	m_fForceNewEnemy = false;
  
  	ClearCondition( COND_ABANDON_TIME_EXPIRED );
  
  	bool bAbandonStandoff = false;
  	CAI_Squad *pSquad = GetOuter()->GetSquad();
  	AISquadIter_t iter;
  	if ( GetEnemy() )
  	{
  		AI_EnemyInfo_t *pEnemyInfo = GetOuter()->GetEnemies()->Find( GetEnemy() );
  		if ( pEnemyInfo &&
  			 m_params.flAbandonTimeLimit > 0 && 
  			 ( ( pEnemyInfo->timeAtFirstHand != AI_INVALID_TIME && 
  			     gpGlobals->curtime  - pEnemyInfo->timeLastSeen > m_params.flAbandonTimeLimit ) ||
  			   ( pEnemyInfo->timeAtFirstHand == AI_INVALID_TIME && 
  			     gpGlobals->curtime  - pEnemyInfo->timeFirstSeen > m_params.flAbandonTimeLimit * 2 ) ) )
  		{
  			SetCondition( COND_ABANDON_TIME_EXPIRED );
  
  			bAbandonStandoff = true;
  
  			if ( pSquad )
  			{
  				for ( CAI_BaseNPC *pSquadMate = pSquad->GetFirstMember( &iter ); pSquadMate; pSquadMate = pSquad->GetNextMember( &iter ) )
  				{
  					if ( pSquadMate->IsAlive() && pSquadMate != GetOuter() )
  					{
  						CAI_StandoffBehavior *pSquadmateStandoff;
  						pSquadMate->GetBehavior( &pSquadmateStandoff );
  						if ( pSquadmateStandoff && pSquadmateStandoff->IsActive() && 
  							 pSquadmateStandoff->m_hStandoffGoal == m_hStandoffGoal &&
  							 !pSquadmateStandoff->HasCondition( COND_ABANDON_TIME_EXPIRED ) )
  						{
  							bAbandonStandoff = false;
  							break;
  						}
  					}
  				}
  			}
  		}
  	}
  
  	if ( bAbandonStandoff )
  	{
  		if ( pSquad )
  		{
  			for ( CAI_BaseNPC *pSquadMate = pSquad->GetFirstMember( &iter ); pSquadMate; pSquadMate = pSquad->GetNextMember( &iter ) )
  			{
  				CAI_StandoffBehavior *pSquadmateStandoff;
  				pSquadMate->GetBehavior( &pSquadmateStandoff );
  				if ( pSquadmateStandoff && pSquadmateStandoff->IsActive() && pSquadmateStandoff->m_hStandoffGoal == m_hStandoffGoal )
  					pSquadmateStandoff->SetActive( false );
  			}
  		}
  		else
  			SetActive( false );
  	}
  	else if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  	{
  		if( DrawBattleLines.GetInt() != 0 )
  		{
  			if ( IsBehindBattleLines( GetAbsOrigin() ) )
  			{
  				NDebugOverlay::Box( GetOuter()->GetAbsOrigin(), -Vector(48,48,4), Vector(48,48,4), 255,0,0,8, 0.1 );
  			}
  			else
  			{
  				NDebugOverlay::Box( GetOuter()->GetAbsOrigin(), -Vector(48,48,4), Vector(48,48,4), 0,255,0,8, 0.1 );
  			}
  		}
  	}
  }
  
  //-------------------------------------
  
  int CAI_StandoffBehavior::SelectScheduleUpdateWeapon( void )
  {
  	// Check if need to reload
  	if ( HasCondition ( COND_NO_PRIMARY_AMMO ) || HasCondition ( COND_LOW_PRIMARY_AMMO ))
  	{
  		StandoffMsg( "Out of ammo, reloading\n" );
  		if ( m_params.fCoverOnReload )
  		{
  			GetOuter()->SpeakSentence( STANDOFF_SENTENCE_OUT_OF_AMMO );
  			return SCHED_HIDE_AND_RELOAD;
  		}
  		
  		return SCHED_RELOAD;
  	}
  	
  	// Otherwise, update planned shots to fire before taking cover again
  	if ( HasCondition( COND_LIGHT_DAMAGE ) )
  	{
  		// if hurt:
  		int iPercent = random->RandomInt(0,99);
  
  		if ( iPercent <= m_params.oddsCover && GetEnemy() != NULL )
  		{
  			SetReuseCurrentCover();
  			StandoffMsg( "Hurt, firing one more shot before cover\n" );
  			if ( !GetOuter()->GetShotRegulator()->IsInRestInterval() )
  			{
  				GetOuter()->GetShotRegulator()->SetBurstShotsRemaining( 1 );
  			}
  		}
  	}
  
  	return SCHED_NONE;
  }
  
  //-------------------------------------
  
  int CAI_StandoffBehavior::SelectScheduleCheckCover( void )
  {
  	if ( m_fTakeCover )
  	{
  		m_fTakeCover = false;
  		if ( GetEnemy() )
  		{
  			GetOuter()->SpeakSentence( STANDOFF_SENTENCE_FORCED_TAKE_COVER );
  			StandoffMsg( "Taking forced cover\n" );
  			return SCHED_TAKE_COVER_FROM_ENEMY;
  		}
  	}
  		
  	if ( GetOuter()->GetShotRegulator()->IsInRestInterval() )
  	{
  		StandoffMsg( "Regulated to not shoot\n" );
  		if ( GetHintType() == HINT_TACTICAL_COVER_LOW )
  			SetPosture( AIP_CROUCHING );
  		else
  			SetPosture( AIP_STANDING );
  
  		if ( random->RandomInt(0,99) < 80 )
  			SetReuseCurrentCover();
  		return SCHED_TAKE_COVER_FROM_ENEMY;
  	}
  	
  	return SCHED_NONE;
  }
  
  //-------------------------------------
  
  int CAI_StandoffBehavior::SelectScheduleEstablishAim( void )
  {
  	if ( HasCondition( COND_ENEMY_OCCLUDED ) )
  	{
  		if ( GetPosture() == AIP_CROUCHING )
  		{
  			// force a stand up, just in case
  			GetOuter()->SpeakSentence( STANDOFF_SENTENCE_STAND_CHECK_TARGET );
  			StandoffMsg( "Crouching, standing up to gain LOS\n" );
  			SetPosture( AIP_PEEKING );
  			return SCHED_STANDOFF;
  		}
  		else if ( GetPosture() == AIP_PEEKING )
  		{
  			if ( m_TimePreventForceNewEnemy.Expired() )
  			{
  				// Look for a new enemy
  				m_fForceNewEnemy = true;
  				StandoffMsg( "Looking for enemy\n" );
  			}
  		}
  #if 0
  		else
  		{
  			return SCHED_ESTABLISH_LINE_OF_FIRE;
  		}
  #endif
  	}
  
  	return SCHED_NONE;
  }
  
  //-------------------------------------
  
  int CAI_StandoffBehavior::SelectScheduleAttack( void )
  {
  	if ( GetPosture() == AIP_PEEKING || GetPosture() == AIP_STANDING )
  	{
  		if ( !HasCondition( COND_CAN_RANGE_ATTACK1 ) && 
  			 !HasCondition( COND_CAN_MELEE_ATTACK1 ) &&
  			  HasCondition( COND_TOO_FAR_TO_ATTACK ) )
  		{
  			if ( GetOuter()->GetActiveWeapon() && ( GetOuter()->GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 ) )
  			{
  				if ( !HasCondition( COND_ENEMY_OCCLUDED ) || random->RandomInt(0,99) < 50 )
  					// Don't advance, just fire anyway
  					return SCHED_RANGE_ATTACK1;
  			}
  		}
  	}
  
  	return SCHED_NONE;
  }
  
  //-------------------------------------
  
  int CAI_StandoffBehavior::SelectSchedule( void )
  {
  	switch ( GetNpcState() )
  	{
  		case NPC_STATE_COMBAT:
  		{
  			int schedule = SCHED_NONE;
  			
  			schedule = SelectScheduleUpdateWeapon();
  			if ( schedule != SCHED_NONE )
  				return schedule;
  				
  			schedule = SelectScheduleCheckCover();
  			if ( schedule != SCHED_NONE )
  				return schedule;
  				
  			schedule = SelectScheduleEstablishAim();
  			if ( schedule != SCHED_NONE )
  				return schedule;
  				
  			schedule = SelectScheduleAttack();
  			if ( schedule != SCHED_NONE )
  				return schedule;
  				
  			break;
  		}
  	}
  
  	return BaseClass::SelectSchedule();
  }
  
  //-------------------------------------
  
  int CAI_StandoffBehavior::TranslateSchedule( int schedule )
  {
  	if ( schedule == SCHED_CHASE_ENEMY )
  	{
  		StandoffMsg( "trying SCHED_ESTABLISH_LINE_OF_FIRE\n" );
  		return SCHED_ESTABLISH_LINE_OF_FIRE;
  	}
  	return BaseClass::TranslateSchedule( schedule );
  }
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::BuildScheduleTestBits()
  {
  	BaseClass::BuildScheduleTestBits();
  
  	if ( IsCurSchedule( SCHED_TAKE_COVER_FROM_ENEMY ) )
  		GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
  }
  
  //-------------------------------------
  
  Activity CAI_MappedActivityBehavior_Temporary::GetMappedActivity( AI_Posture_t posture, Activity activity )
  {
  	if ( posture != AIP_STANDING )
  	{
  		unsigned short iActivityTranslation = m_ActivityMap.Find( MAKE_ACTMAP_KEY( posture, activity ) );
  		if ( iActivityTranslation != m_ActivityMap.InvalidIndex() )
  		{
  			Activity result = m_ActivityMap[iActivityTranslation];
  			return result;
  		}
  	}
  	return ACT_INVALID;
  }
  
  //-------------------------------------
  
  Activity CAI_StandoffBehavior::NPC_TranslateActivity( Activity activity )
  {
  	Activity coverActivity = GetCoverActivity();
  	if ( coverActivity != ACT_INVALID )
  	{
  		if ( activity == ACT_IDLE )
  			activity = coverActivity;
  		if ( GetPosture() == AIP_STANDING && coverActivity == ACT_COVER_LOW )
  			SetPosture( AIP_CROUCHING );
  	}
  	
  	Activity result = GetMappedActivity( GetPosture(), activity );
  	if ( result != ACT_INVALID)
  		return result;
  
  	return BaseClass::NPC_TranslateActivity( activity );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : &vecPos - 
  //-----------------------------------------------------------------------------
  void CAI_StandoffBehavior::SetStandoffGoalPosition( const Vector &vecPos )
  {
  	m_vecStandoffGoalPosition = vecPos;
  	UpdateBattleLines();
  	OnChangeTacticalConstraints();
  	GetOuter()->ClearSchedule( "Standoff goal position changed" );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : &vecPos - 
  //-----------------------------------------------------------------------------
  void CAI_StandoffBehavior::ClearStandoffGoalPosition()
  {
  	if ( m_vecStandoffGoalPosition != GOAL_POSITION_INVALID )
  	{
  		m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
  		UpdateBattleLines();
  		OnChangeTacticalConstraints();
  		GetOuter()->ClearSchedule( "Standoff goal position cleared" );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Output : Vector
  //-----------------------------------------------------------------------------
  Vector CAI_StandoffBehavior::GetStandoffGoalPosition() 
  {
  	if( m_vecStandoffGoalPosition != GOAL_POSITION_INVALID )
  	{
  		return m_vecStandoffGoalPosition;
  	}
  	else if( PlayerIsLeading() )
  	{
  		#ifdef SecobMod__Enable_Fixed_Multiplayer_AI
  			return UTIL_GetNearestPlayer(GetAbsOrigin())->GetAbsOrigin(); 
  		#else
  			return UTIL_GetLocalPlayer()->GetAbsOrigin();
  		#endif //SecobMod__Enable_Fixed_Multiplayer_AI
  	}
  	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;
  				#ifdef SecobMod__Enable_Fixed_Multiplayer_AI
  					CBaseEntity *pPlayer = UTIL_GetNearestPlayer(GetAbsOrigin()); 
  				#else
  					CBaseEntity *pPlayer = UTIL_GetLocalPlayer();
  				#endif //SecobMod__Enable_Fixed_Multiplayer_AI
  				
  				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()
  {
  	#ifdef SecobMod__Enable_Fixed_Multiplayer_AI
  		CBaseEntity *pPlayer = UTIL_GetNearestPlayer(GetAbsOrigin()); 
  	#else
  		CBaseEntity *pPlayer = AI_GetSinglePlayer();
  	#endif //SecobMod__Enable_Fixed_Multiplayer_AI
  
  	return ( pPlayer && GetOuter()->IRelationType( pPlayer ) == D_LI );
  }
  
  //-------------------------------------
  
  CBaseEntity *CAI_StandoffBehavior::GetPlayerLeader()
  {
  	#ifdef SecobMod__Enable_Fixed_Multiplayer_AI
  		CBaseEntity *pPlayer = UTIL_GetNearestPlayer(GetAbsOrigin()); 
  	#else
  		CBaseEntity *pPlayer = AI_GetSinglePlayer();
  	#endif //SecobMod__Enable_Fixed_Multiplayer_AI
  
  	if ( pPlayer && GetOuter()->IRelationType( pPlayer ) == D_LI )
  		return pPlayer;
  	return NULL;
  }
  
  //-------------------------------------
  
  bool CAI_StandoffBehavior::GetDirectionOfStandoff( Vector *pDir )
  {
  	if ( GetEnemy() )
  	{
  		*pDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
  		VectorNormalize( *pDir );
  		pDir->z = 0;
  		return true;
  	}
  	return false;
  }
  
  //-------------------------------------
  
  Hint_e CAI_StandoffBehavior::GetHintType()
  {
  	CAI_Hint *pHintNode = GetHintNode();
  	if ( pHintNode )
  		return pHintNode->HintType();
  	return HINT_NONE;
  }
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::SetReuseCurrentCover()
  {
  	CAI_Hint *pHintNode = GetHintNode();
  	if ( pHintNode && pHintNode->GetNode() && pHintNode->GetNode()->IsLocked() )
  		pHintNode->GetNode()->Unlock();
  }
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::UnlockHintNode()
  {
  	CAI_Hint *pHintNode = GetHintNode();
  	if ( pHintNode )
  	{
  		if ( pHintNode->IsLocked() && pHintNode->IsLockedBy( GetOuter() ) )
  			pHintNode->Unlock();
  		CAI_Node *pNode = pHintNode->GetNode();
  		if ( pNode && pNode->IsLocked() )
  			pNode->Unlock();
  		ClearHintNode();
  	}
  }
  
  
  //-------------------------------------
  
  Activity CAI_StandoffBehavior::GetCoverActivity()
  {
  	CAI_Hint *pHintNode = GetHintNode();
  	if ( pHintNode && pHintNode->HintType() == HINT_TACTICAL_COVER_LOW )
  		return GetOuter()->GetCoverActivity( pHintNode );
  	return ACT_INVALID;
  }
  
  
  //-------------------------------------
  
  struct AI_ActivityMapping_t
  {
  	AI_Posture_t	posture;
  	Activity		activity;
  	const char *	pszWeapon;
  	Activity		translation;
  };
  
  void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap()
  {
  	AI_ActivityMapping_t mappings[] =		// This array cannot be static, as some activity values are set on a per-map-load basis
  	{
  		{	AIP_CROUCHING, 	ACT_IDLE, 				NULL, 				ACT_COVER_LOW, 				},
  		{	AIP_CROUCHING, 	ACT_IDLE_ANGRY,			NULL, 				ACT_COVER_LOW, 				},
  		{	AIP_CROUCHING, 	ACT_WALK, 				NULL, 				ACT_WALK_CROUCH, 			},
  		{	AIP_CROUCHING, 	ACT_RUN, 				NULL, 				ACT_RUN_CROUCH, 			},
  		{	AIP_CROUCHING, 	ACT_WALK_AIM, 			NULL, 				ACT_WALK_CROUCH_AIM, 		},
  		{	AIP_CROUCHING, 	ACT_RUN_AIM, 			NULL, 				ACT_RUN_CROUCH_AIM, 		},
  		{	AIP_CROUCHING,	ACT_RELOAD,				NULL, 				ACT_RELOAD_LOW,				},
  		{	AIP_CROUCHING,	ACT_RANGE_ATTACK_SMG1,	NULL,				ACT_RANGE_ATTACK_SMG1_LOW,	},
  		{	AIP_CROUCHING,	ACT_RANGE_ATTACK_AR2,	NULL,				ACT_RANGE_ATTACK_AR2_LOW,	},
  		
  		//----
  		{	AIP_PEEKING, 	ACT_IDLE,				NULL,				ACT_RANGE_AIM_LOW,			},
  		{	AIP_PEEKING, 	ACT_IDLE_ANGRY,			NULL,				ACT_RANGE_AIM_LOW,			},
  		{	AIP_PEEKING, 	ACT_COVER_LOW,			NULL,				ACT_RANGE_AIM_LOW,			},
  		{	AIP_PEEKING, 	ACT_RANGE_ATTACK1,		NULL, 				ACT_RANGE_ATTACK1_LOW,		},
  		{	AIP_PEEKING,	ACT_RELOAD, 			NULL, 				ACT_RELOAD_LOW,				},
  	};
  
  	m_ActivityMap.RemoveAll();
  	
  	CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon();
  	const char *pszWeaponClass = ( pWeapon ) ? pWeapon->GetClassname() : "";
  	for ( int i = 0; i < ARRAYSIZE(mappings); i++ )
  	{
  		if ( !mappings[i].pszWeapon || stricmp( mappings[i].pszWeapon, pszWeaponClass ) == 0 )
  		{
  			if ( HaveSequenceForActivity( mappings[i].translation ) || HaveSequenceForActivity( GetOuter()->Weapon_TranslateActivity( mappings[i].translation ) ) )
  			{
  				Assert( m_ActivityMap.Find( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ) ) == m_ActivityMap.InvalidIndex() );
  				m_ActivityMap.Insert( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ), mappings[i].translation );
  			}
  		}
  	}
  }
  
  void CAI_StandoffBehavior::UpdateTranslateActivityMap()
  {
  	BaseClass::UpdateTranslateActivityMap();
  	
  	Activity lowCoverActivity = GetMappedActivity( AIP_CROUCHING, ACT_COVER_LOW );
  	if ( lowCoverActivity == ACT_INVALID )
  		lowCoverActivity = ACT_COVER_LOW;
  		
  	m_bHasLowCoverActivity = ( ( CapabilitiesGet() & bits_CAP_DUCK ) && (GetOuter()->TranslateActivity( lowCoverActivity ) != ACT_INVALID));
  
  	CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon();
  	if ( pWeapon && (GetOuter()->TranslateActivity( lowCoverActivity ) == ACT_INVALID ))
  		DevMsg( "Note: NPC class %s lacks ACT_COVER_LOW, therefore cannot participate in standoff\n", GetOuter()->GetClassname() );
  }
  
  //-------------------------------------
  
  void CAI_MappedActivityBehavior_Temporary::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
  {
  	UpdateTranslateActivityMap();
  }
  
  //-------------------------------------
  
  void CAI_StandoffBehavior::OnRestore()
  {
  	UpdateTranslateActivityMap();
  }
  
  //-------------------------------------
  
  AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER(CAI_StandoffBehavior)
  
  	DECLARE_CONDITION( COND_ABANDON_TIME_EXPIRED )
  
  AI_END_CUSTOM_SCHEDULE_PROVIDER()
  
  //-----------------------------------------------------------------------------
  //
  // CAI_StandoffGoal
  //
  // Purpose: A level tool to control the standoff behavior. Use is not required
  //			in order to use behavior.
  //
  //-----------------------------------------------------------------------------
  
  AI_StandoffParams_t g_StandoffParamsByAgression[] =
  {
  	//	hintChangeReaction,		fCoverOnReload, PlayerBtlLn,	minTimeShots,	maxTimeShots, 	minShots, 	maxShots, 	oddsCover			flAbandonTimeLimit
  	{ 	AIHCR_MOVE_ON_COVER,	true,			true, 			4.0, 			8.0, 			2, 			4, 			50,			false,	30 		},	// AGGR_VERY_LOW
  	{ 	AIHCR_MOVE_ON_COVER,	true,			true, 			2.0, 			5.0, 			3, 			5, 			25,			false, 	20		},	// AGGR_LOW
  	{ 	AIHCR_MOVE_ON_COVER,	true,			true, 			0.6, 			2.5, 			3, 			6, 			25,			false, 	10		},	// AGGR_MEDIUM
  	{ 	AIHCR_MOVE_ON_COVER,	true,			true, 			0.2, 			1.5, 			5, 			8, 			10,			false, 	10		},	// AGGR_HIGH
  	{ 	AIHCR_MOVE_ON_COVER,	false,			true, 			0, 				0, 				100,		100, 		0,			false, 	5		},	// AGGR_VERY_HIGH
  };
  
  //-------------------------------------
  
  class CAI_StandoffGoal : public CAI_GoalEntity
  {
  	DECLARE_CLASS( CAI_StandoffGoal, CAI_GoalEntity );
  
  public:
  	CAI_StandoffGoal()
  	{
  		m_aggressiveness = AGGR_MEDIUM;	
  		m_fPlayerIsBattleline = true;
  		m_HintChangeReaction = AIHCR_DEFAULT_AI;
  		m_fStayAtCover = false;
  		m_bAbandonIfEnemyHides = false;
  		m_customParams = AI_DEFAULT_STANDOFF_PARAMS;
  	}
  
  	//---------------------------------
  
  	void EnableGoal( CAI_BaseNPC *pAI )	
  	{
  		CAI_StandoffBehavior *pBehavior;
  		if ( !pAI->GetBehavior( &pBehavior ) )
  			return;
  		
  		pBehavior->SetActive( true );
  		SetBehaviorParams( pBehavior);
  	}
  
  	void DisableGoal( CAI_BaseNPC *pAI  ) 
  	{
  		// @TODO (toml 04-07-03): remove the no damage spawn flag once stable. The implementation isn't very good.
  		CAI_StandoffBehavior *pBehavior;
  		if ( !pAI->GetBehavior( &pBehavior ) )
  			return;
  		pBehavior->SetActive( false );
  		SetBehaviorParams( pBehavior);
  	}
  
  	void InputActivate( inputdata_t &inputdata )
  	{
  		ValidateAggression();
  		BaseClass::InputActivate( inputdata );
  	}
  	
  	void InputDeactivate( inputdata_t &inputdata ) 	
  	{
  		ValidateAggression();
  		BaseClass::InputDeactivate( inputdata );
  	}
  	
  	void InputSetAggressiveness( inputdata_t &inputdata )
  	{
  		int newVal = inputdata.value.Int();
  		
  		m_aggressiveness = (Aggressiveness_t)newVal;
  		ValidateAggression();
  		
  		UpdateActors();
  
  		const CUtlVector<AIHANDLE> &actors = AccessActors();
  		for ( int i = 0; i < actors.Count(); i++ )
  		{
  			CAI_BaseNPC *pAI = actors[i];
  			CAI_StandoffBehavior *pBehavior;
  			if ( !pAI->GetBehavior( &pBehavior ) )
  				continue;
  			SetBehaviorParams( pBehavior);
  		}
  	}
  
  	void SetBehaviorParams( CAI_StandoffBehavior *pBehavior )
  	{
  		AI_StandoffParams_t params;
  
  		if ( m_aggressiveness != AGGR_CUSTOM )
  			params = g_StandoffParamsByAgression[m_aggressiveness];
  		else
  			params = m_customParams;
  
  		params.hintChangeReaction = m_HintChangeReaction;
  		params.fPlayerIsBattleline = m_fPlayerIsBattleline;
  		params.fStayAtCover = m_fStayAtCover;
  		if ( !m_bAbandonIfEnemyHides )
  			params.flAbandonTimeLimit = 0;
  
  		pBehavior->SetParameters( params, this );
  		pBehavior->OnChangeTacticalConstraints();
  		if ( pBehavior->IsRunning() )
  			pBehavior->GetOuter()->ClearSchedule( "Standoff behavior parms changed" );
  	}
  	
  	void ValidateAggression()
  	{
  		if ( m_aggressiveness < AGGR_VERY_LOW || m_aggressiveness > AGGR_VERY_HIGH )
  		{
  			if ( m_aggressiveness != AGGR_CUSTOM )
  			{
  				DevMsg( "Invalid aggressiveness value %d\n", m_aggressiveness );
  				
  				if ( m_aggressiveness < AGGR_VERY_LOW )
  					m_aggressiveness = AGGR_VERY_LOW;
  				else if ( m_aggressiveness > AGGR_VERY_HIGH )
  					m_aggressiveness = AGGR_VERY_HIGH;
  			}
  		}
  	}
  
  private:
  	//---------------------------------
  
  	DECLARE_DATADESC();
  
  	enum Aggressiveness_t
  	{
  		AGGR_VERY_LOW,
  		AGGR_LOW,
  		AGGR_MEDIUM,
  		AGGR_HIGH,
  		AGGR_VERY_HIGH,
  		
  		AGGR_CUSTOM,
  	};
  
  	Aggressiveness_t 		m_aggressiveness;	
  	AI_HintChangeReaction_t m_HintChangeReaction;
  	bool					m_fPlayerIsBattleline;
  	bool					m_fStayAtCover;
  	bool					m_bAbandonIfEnemyHides;
  	AI_StandoffParams_t		m_customParams;
  };
  
  //-------------------------------------
  
  LINK_ENTITY_TO_CLASS( ai_goal_standoff, CAI_StandoffGoal );
  
  BEGIN_DATADESC( CAI_StandoffGoal )
  	DEFINE_KEYFIELD( m_aggressiveness,				FIELD_INTEGER, 	"Aggressiveness" ),
  	//								   m_customParams  (individually)
  	DEFINE_KEYFIELD( m_HintChangeReaction,			FIELD_INTEGER, 	"HintGroupChangeReaction" ),
  	DEFINE_KEYFIELD( m_fPlayerIsBattleline,			FIELD_BOOLEAN,	"PlayerBattleline" ),
  	DEFINE_KEYFIELD( m_fStayAtCover,				FIELD_BOOLEAN,	"StayAtCover" ),
  	DEFINE_KEYFIELD( m_bAbandonIfEnemyHides,		FIELD_BOOLEAN, 	"AbandonIfEnemyHides" ),
  	DEFINE_KEYFIELD( m_customParams.fCoverOnReload,	FIELD_BOOLEAN, 	"CustomCoverOnReload" ),
  	DEFINE_KEYFIELD( m_customParams.minTimeShots,	FIELD_FLOAT, 	"CustomMinTimeShots" ),
  	DEFINE_KEYFIELD( m_customParams.maxTimeShots,	FIELD_FLOAT, 	"CustomMaxTimeShots" ),
  	DEFINE_KEYFIELD( m_customParams.minShots,		FIELD_INTEGER, 	"CustomMinShots" ),
  	DEFINE_KEYFIELD( m_customParams.maxShots,		FIELD_INTEGER, 	"CustomMaxShots" ),
  	DEFINE_KEYFIELD( m_customParams.oddsCover,		FIELD_INTEGER, 	"CustomOddsCover" ),
  
  	// Inputs
  	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetAggressiveness", InputSetAggressiveness ),
  END_DATADESC()
  
  ///-----------------------------------------------------------------------------
  