importonly class HACK_UseSensePresetEvent extends Event
{
	import var sensePreset : TweakDBID;
}

import enum AdditionalTraceType : Uint32
{
	Knee,
	Hip,
	Chest,
}

import class SensorObjectComponent extends IPlacedComponent
{
	public import function SetSensorObjectType( objectType : gamedataSenseObjectType ) : Bool;
	public import function GetTimeSinceLastEntityVisible( entityID : EntityID ) : Float;
	public import function SetMainTrackedObject( target : weak< GameObject > ) : Bool;
	public import function SetMainTrackedObjectTraceZOffset( traceType : AdditionalTraceType, zOffset : Float ) : Bool;
	public import function GetDistToTraceEndFromPosToMainTrackedObject( traceType : AdditionalTraceType ) : Float;
	public import function SetForcedSensesTracing( targetObjectType : gamedataSenseObjectType, attitudeToTarget : EAIAttitude ) : Bool;
	public import function RemoveForcedSensesTracing( targetObjectType : gamedataSenseObjectType, attitudeToTarget : EAIAttitude ) : Bool;
	public import function SetTickDistanceOverride( overrideDistance : Float ) : Bool;
	public import function SetHasPierceableWapon( hasTechWeapon : Bool ) : Bool;
}

enum ESenseLogSource
{
	Undefined = 0,
	AddToBlacklistEvent = 1,
	RemoveFromBlacklistEvent = 2,
	SecurityAreaCrossingPerimeter = 3,
}

import class SenseComponent extends IPlacedComponent
{
	public import function SetHearingEnabled( enabled : Bool );
	public import function IsHearingEnabled() : Bool;
	public import function IsAgentVisible( object : GameObject ) : Bool;
	public import function SetCrowdsAggressiveState( isAggresive : Bool );
	public import function GetVisibilityTraceEndToAgentDist( object : GameObject ) : Float;
	public import function GetDetection( entityID : EntityID ) : Float;
	public import function ResetDetection( entityID : EntityID ) : Bool;
	public import function SetDetectionFactor( detection : Float, optional shapeName : CName ) : Bool;
	public import function SetDetectionCoolDown( coolDown : Float );
	public import function SetDetectionDropFactor( detectionDrop : Float );
	public import function SetDetectionMinRange( range : Float ) : Bool;
	private import function UsePreset( presetID : TweakDBID ) : Bool;
	public import function GetCurrentPreset() : TweakDBID;
	public import function AddDetection( target : SenseComponent, detection : Float ) : Bool;
	public import function GetSenseShapes() : array< ISenseShape >;
	public import function HasDetectionOverwrite( entityID : EntityID ) : Bool;
	public import function SetDetectionOverwrite( entityID : EntityID );
	public import function RemoveDetectionOverwrite( entityID : EntityID ) : Bool;
	public import function HasDetectionAttitudeOverwrite( attitudeGroup : CName ) : Bool;
	public import function SetDetectionAttitudeOverwrite( attitudeGroup : CName );
	public import function RemoveDetectionAttitudeOverwrite( attitudeGroup : CName ) : Bool;
	public import function GetDetectionMultiplier( entityID : EntityID ) : Float;
	public import function SetDetectionMultiplier( entityID : EntityID, multiplier : Float );
	public import function CreateSenseMappin();
	public import function CreateHearingMappin();
	public import function RemoveSenseMappin();
	public import function RequestRemovingSenseMappin();
	public import function RemoveHearingMappin();
	public import function UpdateCrowdMappin();
	public import function HasSenseMappin() : Bool;
	public import function HasHearingMappin() : Bool;
	public import function SetSensorObjectType( objectType : gamedataSenseObjectType ) : Bool;
	public import function SetVisibleObjectType( objectType : gamedataSenseObjectType ) : Bool;
	public import function GetTimeSinceLastEntityVisible( entityID : EntityID ) : Float;
	public import function SetMainTrackedObject( target : weak< GameObject > ) : Bool;
	public import function SetMainTrackedObjectTraceZOffset( traceType : AdditionalTraceType, zOffset : Float ) : Bool;
	public import function GetDistToTraceEndFromPosToMainTrackedObject( traceType : AdditionalTraceType ) : Float;
	public import function SetForcedSensesTracing( targetObjectType : gamedataSenseObjectType, attitudeToTarget : EAIAttitude ) : Bool;
	public import function RemoveForcedSensesTracing( targetObjectType : gamedataSenseObjectType, attitudeToTarget : EAIAttitude ) : Bool;
	public import function SetTickDistanceOverride( overrideDistance : Float ) : Bool;
	public import function SetHasPierceableWapon( hasTechWeapon : Bool ) : Bool;
	public import function SetVisionBlockersIgnoredBySensor( ignoredVisionBlockers : VisionBlockerTypeFlags ) : Bool;
	private import function SetIsPlayerInterestingFromSecuritySystemPOV( isPlayerInteresting : Bool );
	public import function IgnoreLODChange( ignoreLOD : Bool );

	import var visibleObject : VisibleObject;
	import var forceDisableUI : Bool;
	private var m_highLevelCb : CallbackHandle;
	private var m_reactionCb : CallbackHandle;
	private var m_wantedLevelChangedCb : CallbackHandle;
	private var m_highLevelState : gamedataNPCHighLevelState;
	private var m_mainPreset : TweakDBID;
	private var m_secondaryPreset : TweakDBID;
	private var m_puppetBlackboard : weak< IBlackboard >;
	private var m_uiWantedBarBB : weak< IBlackboard >;
	private var m_hideUIElements : Bool;
	private var m_enabledSenses : Bool;
	private var m_shouldStartDetectingPlayerCached : Bool;
	private var m_wasPlayerLastReactionTarget : Bool;
	private var m_logSource : ESenseLogSource;
	default m_logSource = ESenseLogSource.Undefined;

	private const function LogEnabled() : Bool
	{
		return AIComponent.DebugLogEnabled( GetConstOwnerPuppet() );
	}

	private const function Log( category : CName, message : String )
	{
		var aiComponent : AIHumanComponent;
		aiComponent = GetConstOwnerPuppet().GetAIControllerComponent();
		if( aiComponent )
		{
			aiComponent.DebugLog( category, message );
		}
	}

	private const function LogBySource( category : CName, message : String )
	{
		switch( m_logSource )
		{
			case ESenseLogSource.AddToBlacklistEvent:
				Log( category, "OnAddToBlacklist: " + message );
			break;
			case ESenseLogSource.RemoveFromBlacklistEvent:
				Log( category, "OnRemoveFromBlacklist: " + message );
			break;
			case ESenseLogSource.SecurityAreaCrossingPerimeter:
				Log( category, "OnSecurityAreaCrossing: " + message );
			break;
			default:
				Log( category, message );
			break;
		}
	}

	private const function LogCategory( suffix : String ) : CName
	{
		var cat : String;
		cat = "senses";
		switch( m_logSource )
		{
			case ESenseLogSource.AddToBlacklistEvent:
			case ESenseLogSource.RemoveFromBlacklistEvent:
			case ESenseLogSource.SecurityAreaCrossingPerimeter:
				cat += ".interest";
			break;
			default:
				break;
		}
		if( StrLen( suffix ) > 0 )
		{
			cat += ( "." + suffix );
		}
		return StringToName( cat );
	}

	public function LogStart( source : ESenseLogSource, message : String )
	{
		m_logSource = source;
		if( !( LogEnabled() ) )
		{
			return;
		}
		LogBySource( LogCategory( "start" ), message );
	}

	public const function LogInfo( message : String )
	{
		if( !( LogEnabled() ) )
		{
			return;
		}
		LogBySource( LogCategory( "info" ), message );
	}

	public const function LogTrueFalse( message : String, value : Bool )
	{
		if( !( LogEnabled() ) )
		{
			return;
		}
		LogBySource( LogCategory( "info" ), ( message + " = " ) + ( ( value ) ? ( "TRUE" ) : ( "FALSE" ) ) );
	}

	public function ToggleComponent( condition : Bool )
	{
		SetHearingEnabled( condition );
		Toggle( condition );
	}

	public function ToggleSenses( condition : Bool )
	{
		Toggle( condition );
	}

	public function GetOwner() : GameObject
	{
		return ( ( GameObject )( GetEntity() ) );
	}

	public function GetOwnerDevice() : SensorDevice
	{
		return ( ( SensorDevice )( GetEntity() ) );
	}

	public function GetOwnerPuppet() : ScriptedPuppet
	{
		return ( ( ScriptedPuppet )( GetEntity() ) );
	}

	public const function GetConstOwnerPuppet() : ScriptedPuppet
	{
		return ( ( ScriptedPuppet )( GetEntity() ) );
	}

	public function TryCreateSenseMappin() : Bool
	{
		if( !( m_hideUIElements ) && m_enabledSenses )
		{
			CreateSenseMappin();
			return true;
		}
		return false;
	}

	public static function RequestMainPresetChange( obj : weak< GameObject >, const presetName : ref< String > )
	{
		var presetID : TweakDBID;
		presetID = TDBID.Create( "Senses." + presetName );
		RequestPresetChange( obj, presetID, true );
	}

	public static function RequestSecondaryPresetChange( obj : weak< GameObject >, const presetName : ref< String > )
	{
		var presetID : TweakDBID;
		presetID = TDBID.Create( "Senses." + presetName );
		RequestPresetChange( obj, presetID, false );
	}

	public static function RequestSecondaryPresetChange( obj : weak< GameObject >, const presetID : TweakDBID )
	{
		RequestPresetChange( obj, presetID, false );
	}

	public static function ResetPreset( obj : weak< GameObject > )
	{
		var evt : SensePresetChangeEvent;
		if( !( obj ) )
		{
			return;
		}
		evt = new SensePresetChangeEvent;
		evt.reset = true;
		obj.QueueEvent( evt );
	}

	public static function RequestPresetChange( obj : weak< GameObject >, const presetID : TweakDBID, const mainPreset : Bool )
	{
		var evt : SensePresetChangeEvent;
		if( !( obj ) || !( TDBID.IsValid( presetID ) ) )
		{
			return;
		}
		evt = new SensePresetChangeEvent;
		evt.presetID = presetID;
		evt.mainPreset = mainPreset;
		obj.QueueEvent( evt );
	}

	protected event OnSensePresetChangeEvent( evt : SensePresetChangeEvent )
	{
		if( evt.reset )
		{
			UsePreset( m_mainPreset );
			m_secondaryPreset = TDBID.None();
		}
		else if( evt.mainPreset )
		{
			if( !( TDBID.IsValid( m_secondaryPreset ) ) )
			{
				if( UsePreset( evt.presetID ) )
				{
					m_mainPreset = evt.presetID;
				}
			}
			else
			{
				m_mainPreset = evt.presetID;
			}
		}
		else
		{
			if( UsePreset( evt.presetID ) )
			{
				m_secondaryPreset = evt.presetID;
			}
		}
	}

	protected event OnSenseInitialize( evt : SenseInitializeEvent )
	{
		var puppet : weak< ScriptedPuppet >;
		var sensorDevice : weak< SensorDevice >;
		sensorDevice = ( ( SensorDevice )( GetEntity() ) );
		puppet = GetOwnerPuppet();
		if( puppet )
		{
			m_hideUIElements = puppet.GetBoolFromCharacterTweak( "hideUIElements", false );
		}
		if( forceDisableUI )
		{
			m_hideUIElements = true;
		}
		if( sensorDevice && ( ( SensorDeviceControllerPS )( sensorDevice.GetDevicePS() ) ).IsPartOfPrevention() )
		{
			if( sensorDevice.GetPreventionSystem().AreTurretsActive() )
			{
				m_enabledSenses = true;
				if( !( m_hideUIElements ) )
				{
					CreateSenseMappin();
				}
			}
		}
		else
		{
			m_enabledSenses = true;
			if( !( m_hideUIElements ) )
			{
				CreateSenseMappin();
			}
		}
		if( puppet )
		{
			m_mainPreset = TDBID.Create( "Senses." + ( ( ScriptedPuppet )( GetEntity() ) ).GetStringFromCharacterTweak( "relaxedSensesPreset", "Relaxed" ) );
			m_puppetBlackboard = puppet.GetPuppetStateBlackboard();
			if( m_puppetBlackboard )
			{
				m_highLevelCb = m_puppetBlackboard.RegisterListenerInt( GetAllBlackboardDefs().PuppetState.HighLevel, this, 'OnHighLevelChanged' );
				m_reactionCb = m_puppetBlackboard.RegisterListenerInt( GetAllBlackboardDefs().PuppetState.ReactionBehavior, this, 'OnReactionChanged' );
				m_wasPlayerLastReactionTarget = false;
			}
			m_uiWantedBarBB = GameInstance.GetBlackboardSystem( puppet.GetGame() ).Get( GetAllBlackboardDefs().UI_WantedBar );
			if( m_uiWantedBarBB )
			{
				m_wantedLevelChangedCb = m_uiWantedBarBB.RegisterListenerInt( GetAllBlackboardDefs().UI_WantedBar.CurrentWantedLevel, this, 'OnCurrentWantedLevelChanged' );
			}
		}
	}

	private export function OnDetach()
	{
		var puppet : weak< ScriptedPuppet >;
		RemoveSenseMappin();
		RemoveHearingMappin();
		puppet = GetOwnerPuppet();
		if( puppet )
		{
			m_puppetBlackboard = puppet.GetPuppetStateBlackboard();
			if( m_puppetBlackboard )
			{
				m_puppetBlackboard.UnregisterListenerInt( GetAllBlackboardDefs().PuppetState.ReactionBehavior, m_reactionCb );
				m_puppetBlackboard.UnregisterListenerInt( GetAllBlackboardDefs().PuppetState.HighLevel, m_highLevelCb );
			}
			m_uiWantedBarBB = GameInstance.GetBlackboardSystem( puppet.GetGame() ).Get( GetAllBlackboardDefs().UI_WantedBar );
			if( m_uiWantedBarBB )
			{
				m_uiWantedBarBB.UnregisterListenerInt( GetAllBlackboardDefs().UI_WantedBar.CurrentWantedLevel, m_wantedLevelChangedCb );
			}
		}
		if( m_playerInPerception )
		{
			PlayerExitedPercpetion( m_playerInPerception );
		}
	}

	protected event OnHighLevelChanged( value : Int32 )
	{
		m_highLevelState = ( ( gamedataNPCHighLevelState )( value ) );
		switch( m_highLevelState )
		{
			case gamedataNPCHighLevelState.Dead:
				RemoveHearingMappin();
			break;
			case gamedataNPCHighLevelState.Relaxed:
			case gamedataNPCHighLevelState.Combat:
			case gamedataNPCHighLevelState.Alerted:
				ReevaluateDetectionOverwrite( GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject() );
			break;
			default:
				break;
		}
	}

	protected event OnCurrentWantedLevelChanged( value : Int32 )
	{
		var wantedLevel : EPreventionHeatStage;
		var hasDeescalated : Bool;
		var reevaluatedInterest : Bool;
		if( !( GetOwner().IsPrevention() ) )
		{
			return true;
		}
		wantedLevel = ( ( EPreventionHeatStage )( value ) );
		hasDeescalated = wantedLevel == EPreventionHeatStage.Heat_0;
		if( !( hasDeescalated ) )
		{
			return true;
		}
		ReevaluateDetectionOverwrite( GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject() );
		reevaluatedInterest = ReevaluatePlayerInterestFromSecuritySystemPOV();
		if( reevaluatedInterest )
		{
			LogInfo( "set player INTERESTING from Security System POV" );
		}
		else
		{
			LogInfo( "set player NOT INTERESTING from Security System POV" );
		}
		SetIsPlayerInterestingFromSecuritySystemPOV( reevaluatedInterest );
	}

	protected event OnDeath( evt : gameDeathEvent )
	{
		ToggleComponent( false );
		RemoveHearingMappin();
	}

	protected event OnDefeated( evt : DefeatedEvent )
	{
		if( !( IsMultiplayer() ) )
		{
			ToggleComponent( false );
			RemoveHearingMappin();
		}
	}

	protected event OnResurrect( evt : ResurrectEvent )
	{
		if( !( IsMultiplayer() ) )
		{
			ToggleComponent( true );
		}
	}

	protected event OnReactionChanged( value : Int32 )
	{
		var reactionData : AIReactionData;
		var targetToReevaluate : weak< GameObject >;
		reactionData = GetOwnerPuppet().GetStimReactionComponent().GetActiveReactionData();
		if( !( reactionData ) )
		{
			reactionData = GetOwnerPuppet().GetStimReactionComponent().GetDesiredReactionData();
		}
		if( !( reactionData ) && !( m_wasPlayerLastReactionTarget ) )
		{
			return true;
		}
		targetToReevaluate = ( ( reactionData.stimTarget ) ? ( reactionData.stimTarget ) : ( GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject() ) );
		ReevaluateDetectionOverwrite( targetToReevaluate );
		m_wasPlayerLastReactionTarget = targetToReevaluate.IsPlayer();
	}

	protected event OnTargetAssessmentRequest( evt : TargetAssessmentRequest )
	{
		var owner : GameObject;
		var threat : TrackedLocation;
		var threats : array< TrackedLocation >;
		var target : GameObject;
		var i : Int32;
		owner = GetOwner();
		if( owner.IsDevice() )
		{
			return false;
		}
		if( evt.targetToAssess )
		{
			if( owner.GetTargetTrackerComponent().ThreatFromEntity( evt.targetToAssess, threat ) )
			{
				ReevaluateDetectionOverwrite( evt.targetToAssess );
			}
		}
		else
		{
			threats = owner.GetTargetTrackerComponent().GetThreats( true );
			for( i = 0; i < threats.Size(); i += 1 )
			{
				target = ( ( GameObject )( threats[ i ].entity ) );
				if( target )
				{
					ReevaluateDetectionOverwrite( target );
				}
			}
		}
	}

	protected event OnSenseEnabledEvent( evt : SenseEnabledEvent )
	{
		if( evt.isEnabled )
		{
			m_enabledSenses = true;
			if( !( m_hideUIElements ) )
			{
				CreateSenseMappin();
			}
		}
	}

	protected event OnSenseVisibilityEvent( evt : SenseVisibilityEvent )
	{
		RefreshCombatDetectionMultiplier( ( ( ScriptedPuppet )( evt.target ) ) );
		if( evt.target.IsPlayer() )
		{
			if( evt.isVisible )
			{
				PlayerEnteredPerception( ( ( PlayerPuppet )( evt.target ) ) );
			}
			else
			{
				PlayerExitedPercpetion( ( ( PlayerPuppet )( evt.target ) ) );
			}
		}
		else
		{
			ReevaluateDetectionOverwrite( evt.target, evt.isVisible );
		}
	}

	protected event OnAddToBlacklistEvent( evt : AddToBlacklistEvent )
	{
		var reevaluatedInterest : Bool;
		LogStart( ESenseLogSource.AddToBlacklistEvent, "processing event" );
		if( !( evt.isPlayerEntity ) )
		{
			LogInfo( "not player in blacklist" );
			return false;
		}
		if( evt.reason != BlacklistReason.COMBAT )
		{
			LogInfo( "blacklist for non-combat reason" );
			return false;
		}
		reevaluatedInterest = ReevaluatePlayerInterestFromSecuritySystemPOV();
		if( reevaluatedInterest )
		{
			LogInfo( "set player INTERESTING from Security System POV" );
		}
		else
		{
			LogInfo( "set player NOT INTERESTING from Security System POV" );
		}
		SetIsPlayerInterestingFromSecuritySystemPOV( reevaluatedInterest );
		ReevaluateDetectionOverwrite( GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject() );
	}

	protected event OnRemoveFromBlacklistEvent( evt : RemoveFromBlacklistEvent )
	{
		var reevaluatedInterest : Bool;
		LogStart( ESenseLogSource.RemoveFromBlacklistEvent, "processing event" );
		if( !( evt.isPlayerEntity ) )
		{
			LogInfo( "not player in blacklist" );
			return false;
		}
		reevaluatedInterest = ReevaluatePlayerInterestFromSecuritySystemPOV();
		if( reevaluatedInterest )
		{
			LogInfo( "set player INTERESTING from Security System POV" );
		}
		else
		{
			LogInfo( "set player NOT INTERESTING from Security System POV" );
		}
		SetIsPlayerInterestingFromSecuritySystemPOV( reevaluatedInterest );
		ReevaluateDetectionOverwrite( GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject() );
	}


	private var m_playerTakedownStateCallbackID : CallbackHandle;
	private var m_playerUpperBodyStateCallbackID : CallbackHandle;
	private var m_playerCarryingStateCallbackID : CallbackHandle;
	private var m_playerInPerception : weak< PlayerPuppet >;

	private function PlayerEnteredPerception( player : weak< PlayerPuppet > )
	{
		SenseComponent.RequestDetectionOverwriteReevaluation( GetOwner(), player, 0.5 );
		if( !( HasSenseMappin() ) && !( m_enabledSenses ) )
		{
			return;
		}
		if( m_playerInPerception )
		{
			PlayerExitedPercpetion( m_playerInPerception );
		}
		m_playerInPerception = player;
		ReevaluateDetectionOverwrite( m_playerInPerception, true );
		if( !( m_playerTakedownStateCallbackID ) )
		{
			m_playerTakedownStateCallbackID = m_playerInPerception.GetPlayerStateMachineBlackboard().RegisterListenerInt( GetAllBlackboardDefs().PlayerStateMachine.Takedown, this, 'OnPlayerTakedownStateChange', false );
		}
		if( !( m_playerUpperBodyStateCallbackID ) )
		{
			m_playerUpperBodyStateCallbackID = m_playerInPerception.GetPlayerStateMachineBlackboard().RegisterListenerInt( GetAllBlackboardDefs().PlayerStateMachine.UpperBody, this, 'OnPlayerUpperBodyStateChange', false );
		}
		if( !( m_playerCarryingStateCallbackID ) )
		{
			m_playerCarryingStateCallbackID = m_playerInPerception.GetPlayerStateMachineBlackboard().RegisterListenerBool( GetAllBlackboardDefs().PlayerStateMachine.Carrying, this, 'OnPlayerCarryingStateChange', false );
		}
	}

	private function PlayerExitedPercpetion( player : weak< PlayerPuppet > )
	{
		if( !( m_playerInPerception ) || ( player != m_playerInPerception ) )
		{
			return;
		}
		if( m_playerTakedownStateCallbackID )
		{
			m_playerInPerception.GetPlayerStateMachineBlackboard().UnregisterListenerInt( GetAllBlackboardDefs().PlayerStateMachine.Takedown, m_playerTakedownStateCallbackID );
		}
		if( m_playerUpperBodyStateCallbackID )
		{
			m_playerInPerception.GetPlayerStateMachineBlackboard().UnregisterListenerInt( GetAllBlackboardDefs().PlayerStateMachine.UpperBody, m_playerUpperBodyStateCallbackID );
		}
		if( m_playerCarryingStateCallbackID )
		{
			m_playerInPerception.GetPlayerStateMachineBlackboard().UnregisterListenerBool( GetAllBlackboardDefs().PlayerStateMachine.Carrying, m_playerCarryingStateCallbackID );
		}
		m_playerInPerception = NULL;
	}

	private function OnPlayerTakedownStateChange( takedownState : Int32 )
	{
		ReevaluateDetectionOverwrite( m_playerInPerception );
	}

	private function OnPlayerUpperBodyStateChange( upperBodyState : Int32 )
	{
		ReevaluateDetectionOverwrite( m_playerInPerception );
	}

	private function OnPlayerCarryingStateChange( carrying : Bool )
	{
		ReevaluateDetectionOverwrite( m_playerInPerception );
	}

	protected event OnDetectedEvent( evt : OnDetectedEvent ) {}

	protected event OnDetectionReachedZero( evt : OnRemoveDetection )
	{
		ReevaluateDetectionOverwrite( evt.target );
	}

	protected event OnAttitudeChanged( evt : AttitudeChangedEvent )
	{
		ReevaluateDetectionOverwrite( ( ( GameObject )( evt.otherAgent.GetEntity() ) ) );
	}

	protected event OnAttitudeGroupChanged( evt : AttitudeGroupChangedEvent )
	{
		ReevaluateDetectionOverwrite( GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject() );
	}

	protected event OnSuspiciousObjectEvent( evt : SuspiciousObjectEvent )
	{
		ReevaluateDetectionOverwrite( evt.target );
	}

	protected event OnStatusEffectApplied( evt : ApplyStatusEffectEvent )
	{
		ReevaluateDetectionOverwrite( GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject() );
	}

	protected event OnSecurityAreaCrossingPerimeter( evt : SecurityAreaCrossingPerimeter )
	{
		var owner : weak< GameObject >;
		var breacher : weak< GameObject >;
		var securitySystem : SecuritySystemControllerPS;
		var isBlacklistedForCombat : Bool;
		var isEnteringOrExitingWhenBlacklisted : Bool;
		var shouldStartDetectingBreacher : Bool;
		var reevaluatedInterest : Bool;
		owner = ( ( GameObject )( GetEntity() ) );
		breacher = evt.GetWhoBreached();
		LogStart( ESenseLogSource.SecurityAreaCrossingPerimeter, "processing event" );
		if( !( owner ) || !( breacher ) )
		{
			LogInfo( "no owner or breacher" );
			return false;
		}
		shouldStartDetectingBreacher = ReevaluateDetectionOverwrite( breacher );
		if( !( breacher.IsPlayerControlled() ) )
		{
			LogInfo( "breacher is not player" );
			return false;
		}
		securitySystem = owner.GetSecuritySystem();
		isBlacklistedForCombat = securitySystem && securitySystem.IsEntityBlacklistedForAtLeast( breacher, BlacklistReason.COMBAT );
		isEnteringOrExitingWhenBlacklisted = evt.GetEnteredState() || isBlacklistedForCombat;
		LogTrueFalse( "enteredState", evt.GetEnteredState() );
		LogTrueFalse( "isBlacklistedForCombat", isBlacklistedForCombat );
		LogTrueFalse( "shouldStartDetectingBreacher", shouldStartDetectingBreacher );
		reevaluatedInterest = shouldStartDetectingBreacher && isEnteringOrExitingWhenBlacklisted;
		if( reevaluatedInterest )
		{
			LogInfo( "set player INTERESTING from Security System POV" );
		}
		else
		{
			LogInfo( "set player NOT INTERESTING from Security System POV" );
		}
		SetIsPlayerInterestingFromSecuritySystemPOV( reevaluatedInterest );
	}

	private export function ReevaluatePlayerInterestFromSecuritySystemPOV() : Bool
	{
		return IsTargetInterestingForSecuritySystem( GetPlayer( GetGame() ) );
	}

	public static function RequestDetectionOverwriteReevaluation( obj : weak< GameObject >, target : weak< Entity >, optional delay : Float )
	{
		var evt : ReevaluateDetectionOverwriteEvent;
		if( !( obj ) || !( target ) )
		{
			return;
		}
		evt = new ReevaluateDetectionOverwriteEvent;
		evt.target = target;
		if( delay > 0.0 )
		{
			GameInstance.GetDelaySystem( obj.GetGame() ).DelayEvent( obj, evt, delay, false );
		}
		else
		{
			obj.QueueEvent( evt );
		}
	}

	protected event OnReevaluateDetectionOverwriteEvent( evt : ReevaluateDetectionOverwriteEvent )
	{
		ReevaluateDetectionOverwrite( ( ( GameObject )( evt.target ) ) );
	}

	protected event OnSensorOwnerChangedEvent( evt : SensorOwnerChangedEvent )
	{
		SetVisionBlockersIgnoredBySensor( GetIgnoredVisionBlockerTypes( GetGame(), evt.newOwnerId ) );
	}

	public function UpdateVisionBlockersIgnoredBySensor()
	{
		SetVisionBlockersIgnoredBySensor( GetIgnoredVisionBlockerTypes( GetGame(), GetEntity().GetEntityID() ) );
	}

	private function GetGame() : GameInstance
	{
		return GetOwner().GetGame();
	}

	private function IsTargetPlayer( target : weak< GameObject > ) : Bool
	{
		if( target && target.IsPlayer() )
		{
			return true;
		}
		return false;
	}

	public static function ShouldIgnoreIfPlayerCompanion( owner : weak< Entity >, target : weak< Entity > ) : Bool
	{
		var targetPuppet : weak< ScriptedPuppet >;
		var commandCombatTarget : weak< GameObject >;
		var aiControllerComponent : AIHumanComponent;
		var ownerPuppet : ScriptedPuppet;
		var playerPuppet : ScriptedPuppet;
		var trackers : array< Entity >;
		var i : Int32;
		var trackedLocation : TrackedLocation;
		targetPuppet = ( ( ScriptedPuppet )( target ) );
		if( targetPuppet == NULL )
		{
			return false;
		}
		if( !( ScriptedPuppet.IsPlayerCompanion( targetPuppet ) ) )
		{
			return false;
		}
		ownerPuppet = ( ( ScriptedPuppet )( owner ) );
		if( ownerPuppet )
		{
			aiControllerComponent = ownerPuppet.GetAIControllerComponent();
			if( aiControllerComponent )
			{
				commandCombatTarget = ( ( weak< weak< GameObject > > )( aiControllerComponent.GetBehaviorArgument( 'CommandCombatTarget' ) ) );
				if( commandCombatTarget && ( commandCombatTarget == target ) )
				{
					return false;
				}
			}
		}
		playerPuppet = ( ( ScriptedPuppet )( GameInstance.GetPlayerSystem( targetPuppet.GetGame() ).GetLocalPlayerControlledGameObject() ) );
		trackers = playerPuppet.GetTargetTrackerComponent().CollectTrackers( true, false );
		for( i = 0; i < trackers.Size(); i += 1 )
		{
			if( TargetTrackingExtension.GetTrackedLocation( ( ( ScriptedPuppet )( trackers[ i ] ) ), playerPuppet, trackedLocation ) )
			{
				if( trackedLocation.accuracy > 0.1 )
				{
					return false;
				}
			}
		}
		return true;
	}

	private function GetSecuritySystem() : SecuritySystemControllerPS
	{
		var owner : weak< GameObject >;
		owner = ( ( GameObject )( GetEntity() ) );
		if( owner )
		{
			return owner.GetSecuritySystem();
		}
		return NULL;
	}

	private function IsTargetInterestingForSecuritySystem( target : weak< GameObject > ) : Bool
	{
		var owner : GameObject;
		var sec : SecuritySystemControllerPS;
		owner = ( ( GameObject )( GetEntity() ) );
		sec = owner.GetSecuritySystem();
		if( ( ( owner && target ) && sec ) && sec.ShouldReactToTarget( target.GetEntityID(), owner.GetEntityID() ) )
		{
			return true;
		}
		return false;
	}

	public function RefreshCombatDetectionMultiplier( target : ScriptedPuppet )
	{
		var cssi : CombatSquadScriptInterface;
		var tte : TargetTrackingExtension;
		var tl : TrackedLocation;
		AISquadHelper.GetCombatSquadInterface( target, cssi );
		if( !( GetOwnerPuppet() ) )
		{
			return;
		}
		tte = ( ( TargetTrackingExtension )( GetOwnerPuppet().GetTargetTrackerComponent() ) );
		if( !( cssi ) || !( tte ) )
		{
			return;
		}
		if( tte.IsSquadTracked( cssi ) )
		{
			SetDetectionMultiplier( target.GetEntityID(), 100.0 );
		}
		else if( tte.ThreatFromEntity( target, tl ) && ( tl.sharedAccuracy > 0.0 ) )
		{
			SetDetectionMultiplier( target.GetEntityID(), 10.0 );
		}
		else if( target.IsPlayer() && ( ( PlayerPuppet )( target ) ).IsInCombat() )
		{
			SetDetectionMultiplier( target.GetEntityID(), 2.0 );
		}
		else
		{
			SetDetectionMultiplier( target.GetEntityID(), 1.0 );
		}
	}

	private function IsTargetInteresting( target : weak< GameObject > ) : Bool
	{
		var owner, member : ScriptedPuppet;
		var reactionComponent : ReactionManagerComponent;
		var cssi : CombatSquadScriptInterface;
		var membersList : array< weak< Entity > >;
		var i, count : Int32;
		owner = GetOwnerPuppet();
		reactionComponent = owner.GetStimReactionComponent();
		if( !( owner ) )
		{
			return false;
		}
		if( !( target ) )
		{
			return false;
		}
		if( !( owner.IsAggressive() ) )
		{
			return false;
		}
		if( IsPlayerRecentlyDroppedThreat( target ) )
		{
			return true;
		}
		if( reactionComponent && reactionComponent.IsTargetInterestingForPerception( target ) )
		{
			return true;
		}
		if( m_highLevelState == gamedataNPCHighLevelState.Alerted )
		{
			AISquadHelper.GetCombatSquadInterface( target, cssi );
			AISquadHelper.GetSquadmates( owner, membersList );
			count = membersList.Size();
			for( i = 0; i < count; i += 1 )
			{
				member = ( ( ScriptedPuppet )( membersList[ i ] ) );
				if( member.GetHighLevelStateFromBlackboard() == gamedataNPCHighLevelState.Alerted )
				{
					reactionComponent = member.GetStimReactionComponent();
					if( reactionComponent && reactionComponent.IsTargetInterestingForPerception( target ) )
					{
						return true;
					}
				}
			}
		}
		return false;
	}

	private function IsPlayerRecentlyDroppedThreat( target : weak< GameObject > ) : Bool
	{
		var tte : weak< TargetTrackingExtension >;
		var threatData : DroppedThreatData;
		var threatObject : weak< GameObject >;
		if( !( TargetTrackingExtension.Get( GetOwnerPuppet(), tte ) ) )
		{
			return false;
		}
		threatData = tte.GetRecentlyDroppedThreat();
		if( !( threatData.threat ) )
		{
			return false;
		}
		threatObject = ( ( GameObject )( threatData.threat ) );
		return threatObject && threatObject.IsPlayer();
	}

	private function IsOwnerHostileTowardsPlayer() : Bool
	{
		return IsOwnerHostileTowardsTarget( GetPlayer( GetGame() ) );
	}

	private function IsOwnerHostileTowardsTarget( target : weak< GameObject > ) : Bool
	{
		var owner : weak< GameObject >;
		owner = ( ( GameObject )( GetEntity() ) );
		if( ( owner && target ) && GameObject.GetAttitudeTowards( owner, target ) == EAIAttitude.AIA_Hostile )
		{
			return true;
		}
		return false;
	}

	private function IsOwnerFriendlyTowardsPlayer() : Bool
	{
		return IsOwnerFriendlyTowardsTarget( GetPlayer( GetGame() ) );
	}

	private function IsOwnerFriendlyTowardsTarget( target : weak< GameObject > ) : Bool
	{
		var owner : weak< GameObject >;
		owner = ( ( GameObject )( GetEntity() ) );
		if( ( owner && target ) && GameObject.GetAttitudeTowards( owner, target ) == EAIAttitude.AIA_Friendly )
		{
			return true;
		}
		return false;
	}

	private function InitDetectionOverwrite()
	{
		var owner : weak< GameObject >;
		owner = ( ( GameObject )( GetEntity() ) );
		if( !( owner ) )
		{
			return;
		}
		SetDetectionOverwrite( GameInstance.GetPlayerSystem( owner.GetGame() ).GetLocalPlayerControlledGameObject().GetEntityID() );
		SetDetectionAttitudeOverwrite( 'player' );
	}

	public function ReevaluateDetectionOverwrite( target : weak< GameObject >, optional isVisible : Bool ) : Bool
	{
		if( !( IsEnabled() ) )
		{
			SetDetectionOverwrite( target.GetEntityID() );
			if( target.IsPlayer() )
			{
				SetDetectionAttitudeOverwrite( 'player' );
				m_shouldStartDetectingPlayerCached = false;
			}
			return false;
		}
		if( !( target ) )
		{
			return false;
		}
		if( ShouldStartDetecting( target ) )
		{
			RemoveDetectionOverwrite( target.GetEntityID() );
			if( target.IsPlayer() )
			{
				RemoveDetectionAttitudeOverwrite( 'player' );
				m_shouldStartDetectingPlayerCached = true;
			}
			SendDetectionRiseEvent( target, isVisible );
			if( ( target.IsPlayer() && ( GetDetection( target.GetEntityID() ) == 0.0 ) ) && IsAgentVisible( target ) )
			{
				AIActionHelper.PreloadCoreAnimations( GetOwnerPuppet() );
				PlayerPuppet.SendOnBeingNoticed( ( ( PlayerPuppet )( target ) ), GetOwner() );
			}
			return true;
		}
		SetDetectionOverwrite( target.GetEntityID() );
		if( target.IsPlayer() )
		{
			SetDetectionAttitudeOverwrite( 'player' );
			m_shouldStartDetectingPlayerCached = false;
		}
		return false;
	}

	private function ShouldStartDetecting( target : GameObject ) : Bool
	{
		var isDevice : Bool;
		isDevice = GetOwnerDevice() != NULL;
		if( ScriptedPuppet.IsBlinded( GetOwner() ) )
		{
			return false;
		}
		if( isDevice && !( ( ( SensorDeviceControllerPS )( GetOwnerDevice().GetDevicePS() ) ).GetBehaviourCanDetectIntruders() ) )
		{
			return false;
		}
		if( IsTargetPlayer( target ) )
		{
			return ShouldStartDetectingPlayer( ( ( PlayerPuppet )( target ) ) );
		}
		if( ShouldIgnoreIfPlayerCompanion( GetEntity(), target ) )
		{
			return false;
		}
		return true;
	}

	public function ShouldStartDetectingPlayer( player : PlayerPuppet ) : Bool
	{
		var owner : weak< GameObject >;
		var ownerPuppet : weak< ScriptedPuppet >;
		owner = GetOwner();
		if( IsOwnerFriendlyTowardsTarget( player ) )
		{
			return false;
		}
		if( owner.IsPuppet() )
		{
			ownerPuppet = GetOwnerPuppet();
			if( !( TargetTrackingExtension.IsThreatInThreatList( ownerPuppet, player, false, true ) ) )
			{
				if( ( !( ownerPuppet.IsAggressive() ) && ownerPuppet.GetStimReactionComponent().GetReactionPreset().Type() != gamedataReactionPresetType.Civilian_Guard ) && ownerPuppet.GetStimReactionComponent().GetReactionPreset().Type() != gamedataReactionPresetType.Civilian_Grabbable )
				{
					return false;
				}
			}
			if( ownerPuppet.IsPrevention() && !( PreventionSystem.ShouldReactionBeAgressive( owner.GetGame() ) ) )
			{
				return false;
			}
		}
		if( IsOwnerHostileTowardsTarget( player ) )
		{
			return true;
		}
		if( owner.IsPuppet() && IsTargetInteresting( player ) )
		{
			return true;
		}
		if( IsTargetInterestingForSecuritySystem( player ) )
		{
			return true;
		}
		return false;
	}

	public export function GetShouldStartDetectingPlayerCached() : Bool
	{
		return m_shouldStartDetectingPlayerCached;
	}

	private function SendDetectionRiseEvent( target : weak< GameObject >, isVisible : Bool )
	{
		var evtRise : DetectionRiseEvent;
		evtRise = new DetectionRiseEvent;
		evtRise.target = target;
		evtRise.isVisible = isVisible;
		QueueEntityEvent( evtRise );
	}

	protected event OnHACK_UseSensePresetEvent( evt : HACK_UseSensePresetEvent )
	{
		UsePreset( evt.sensePreset );
	}

}

import enum senseEShapeType
{
	INVALID,
	BOX,
	SPHERE,
	CONE,
	ANGLE_RANGE,
}

importonly class SenseInitializeEvent extends Event
{
}

importonly class SenseVisibilityEvent extends Event
{
	import var target : weak< GameObject >;
	import var isVisible : Bool;
	import var shapeId : TweakDBID;
	import var description : CName;
}

importonly class OnBeingDetectedEvent extends Event
{
	import var isVisible : Bool;
	import var shapeId : TweakDBID;

	public import function GetSourceObject() : weak< GameObject >;
}

importonly class PlayerDetectionChangedEvent extends Event
{
	import var oldDetectionValue : Float;
	import var newDetectionValue : Float;
}

importonly class SenseEnabledEvent extends Event
{
	import var isEnabled : Bool;
}

importonly class OnDetectedEvent extends SenseVisibilityEvent
{
}

importonly class EnterShapeEvent extends SenseVisibilityEvent
{
}

importonly class ExitShapeEvent extends SenseVisibilityEvent
{
}

importonly class OnRemoveDetection extends Event
{
	import var target : weak< GameObject >;
}

importonly class VisibleObject extends IScriptable
{
	import var description : CName;
	import var visibilityDistance : Float;
}

importonly class VisibleObjectComponent extends IPlacedComponent
{
	import var visibleObject : VisibleObject;

	public import function GetDetectionMultiplier() : Float;
	public import function SetVisibleObjectTypeInvalid();
}

importonly abstract class ISenseShape extends IScriptable
{
	public import function GetType() : senseEShapeType;
}

importonly class SenseBox extends ISenseShape
{
	import var box : Box;
}

importonly class SenseSphere extends ISenseShape
{
	import var sphere : Sphere;
}

importonly class SenseCone extends ISenseShape
{
	import var position1 : Vector4;
	import var position2 : Vector4;
	import var radius1 : Float;
	import var radius2 : Float;
}

importonly class SenseAngleRange extends ISenseShape
{
	import var position : Vector4;
	import var angle : Float;
	import var range : Float;
	import var halfHeight : Float;
}

class DetectionRiseEvent extends SenseVisibilityEvent
{
}

importonly class SensePresetChangeEvent extends SenseVisibilityEvent
{
	import var presetID : TweakDBID;
	import var mainPreset : Bool;
	import var reset : Bool;
}

importonly class VisibleObjectDetectionMultEvent extends Event
{
	import var multiplier : Float;
}

importonly class VisibleObjectTypeEvent extends Event
{
	import var type : gamedataSenseObjectType;
}

importonly class VisibleObjectDistanceEvent extends Event
{
	import var distance : Float;
}

importonly class VisibleObjectSecondaryPositionEvent extends Event
{
	import var offset : Vector3;
}

importonly class VisibleObjectetSecondaryDistanceEvent extends Event
{
	import var distance : Float;
	import var extraEvaluationDistance : Float;
}

importonly class SensorOwnerChangedEvent extends Event
{
	import var newOwnerId : EntityID;
}

class ReevaluateDetectionOverwriteEvent extends Event
{
	var target : weak< Entity >;
}

class SetNPCSensesMainPreset extends AIbehaviortaskScript
{
	editable var newSensesPresetName : String;

	protected override function Activate( context : ScriptExecutionContext )
	{
		SenseComponent.RequestMainPresetChange( ScriptExecutionContext.GetOwner( context ), newSensesPresetName );
	}

}

class ResetNPCSensesMainPreset extends AIbehaviortaskScript
{
	editable var newSensesPresetName : String;

	protected override function Activate( context : ScriptExecutionContext )
	{
		SenseComponent.ResetPreset( ScriptExecutionContext.GetOwner( context ) );
	}

}

import class HideUIDetectionEvent extends Event
{
	import var shouldHideUIDetection : Bool;
}

