class PassiveIsPlayerCompanionCondition extends PassiveAutonomousCondition
{
	protected var m_roleCbId : Uint32;

	protected export function Activate( context : ScriptExecutionContext )
	{
		m_roleCbId = ScriptExecutionContext.AddBehaviorCallback( context, 'OnAIRoleChanged', this );
	}

	protected export function Deactivate( context : ScriptExecutionContext )
	{
		ScriptExecutionContext.RemoveBehaviorCallback( context, m_roleCbId );
	}

	protected export function CalculateValue( context : ScriptExecutionContext ) : Variant
	{
		if( ScriptedPuppet.IsPlayerCompanion( ScriptExecutionContext.GetOwner( context ) ) )
		{
			return true;
		}
		return false;
	}

}

class IsFollowTargetInCombat extends AIAutonomousConditions
{

	protected override function Activate( context : ScriptExecutionContext )
	{
		SetUpdateInterval( context, RandomizeOffsetForUpdateInterval( 0.2 ) );
	}

	protected override function Check( context : ScriptExecutionContext ) : AIbehaviorConditionOutcomes
	{
		var combatState : gamePSMCombat;
		var followTarget : GameObject;
		followTarget = ScriptExecutionContext.GetArgumentObject( context, 'FriendlyTarget' );
		combatState = ( ( gamePSMCombat )( AIActionChecks.GetPSMBlackbordInt( ( ( PlayerPuppet )( followTarget ) ), GetAllBlackboardDefs().PlayerStateMachine.Combat ) ) );
		return combatState == gamePSMCombat.InCombat;
	}

}

class IsPlayerCompanion extends AIAutonomousConditions
{

	protected export override function Activate( context : ScriptExecutionContext )
	{
		SetUpdateInterval( context, RandomizeOffsetForUpdateInterval( 0.2 ) );
	}

	protected export override function Check( context : ScriptExecutionContext ) : AIbehaviorConditionOutcomes
	{
		if( ScriptedPuppet.IsPlayerCompanion( ScriptExecutionContext.GetOwner( context ) ) )
		{
			return true;
		}
		return false;
	}

}

class IsFriendlyToPlayer extends AIAutonomousConditions
{

	protected override function Activate( context : ScriptExecutionContext )
	{
		SetUpdateInterval( context, RandomizeOffsetForUpdateInterval( 0.2 ) );
	}

	protected override function Check( context : ScriptExecutionContext ) : AIbehaviorConditionOutcomes
	{
		if( GameObject.GetAttitudeBetween( ScriptExecutionContext.GetOwner( context ), GameInstance.GetPlayerSystem( ScriptExecutionContext.GetOwner( context ).GetGame() ).GetLocalPlayerMainGameObject() ) == EAIAttitude.AIA_Friendly )
		{
			return true;
		}
		return false;
	}

}

class FollowerFindTeleportPositionAroundTarget extends AIbehaviortaskScript
{
	editable inlined var m_target : AIArgumentMapping;
	editable inlined var m_outPositionArgument : AIArgumentMapping;
	private var m_lastResultTimestamp : Float;

	protected export override function Update( context : ScriptExecutionContext ) : AIbehaviorUpdateOutcome
	{
		var followTarget : weak< GameObject >;
		var telportPosition : Vector4;
		var navigationSystem : AINavigationSystem;
		var movePoliciesComponent : MovePoliciesComponent;
		var ratioCurveName : CName;
		if( m_lastResultTimestamp > 0.0 )
		{
			if( EngineTime.ToFloat( ScriptExecutionContext.GetAITime( context ) ) < ( m_lastResultTimestamp + 0.5 ) )
			{
				return AIbehaviorUpdateOutcome.IN_PROGRESS;
			}
			m_lastResultTimestamp = 0.0;
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		followTarget = ( ( weak< weak< GameObject > > )( ScriptExecutionContext.GetMappingValue( context, m_target ) ) );
		if( !( followTarget ) || !( followTarget.IsAttached() ) )
		{
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		navigationSystem = GameInstance.GetAINavigationSystem( ScriptExecutionContext.GetOwner( context ).GetGame() );
		if( !( navigationSystem ) )
		{
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		if( !( navigationSystem.IsPointOnNavmesh( ScriptExecutionContext.GetOwner( context ), followTarget.GetWorldPosition() ) ) )
		{
			m_lastResultTimestamp = EngineTime.ToFloat( ScriptExecutionContext.GetAITime( context ) );
			return AIbehaviorUpdateOutcome.IN_PROGRESS;
		}
		movePoliciesComponent = ( ( ScriptedPuppet )( ScriptExecutionContext.GetOwner( context ) ) ).GetMovePolicesComponent();
		if( movePoliciesComponent && movePoliciesComponent.GetTopPolicies() )
		{
			ratioCurveName = movePoliciesComponent.GetTopPolicies().GetMaxPathLengthToDirectDistanceRatioCurve();
		}
		if( !( navigationSystem.GetFurthestNavmeshPointBehind( followTarget, 3.0, 4, telportPosition, -( followTarget.GetWorldForward() ) * 1.5, true, ratioCurveName ) ) )
		{
			m_lastResultTimestamp = EngineTime.ToFloat( ScriptExecutionContext.GetAITime( context ) );
			return AIbehaviorUpdateOutcome.IN_PROGRESS;
		}
		ScriptExecutionContext.SetMappingValue( context, m_outPositionArgument, telportPosition );
		m_lastResultTimestamp = EngineTime.ToFloat( ScriptExecutionContext.GetAITime( context ) );
		return AIbehaviorUpdateOutcome.SUCCESS;
	}

}

class AIFollowerTakedownCommandHandler extends AIbehaviortaskScript
{
	editable inlined var m_inCommand : AIArgumentMapping;

	protected export override function Activate( context : ScriptExecutionContext )
	{
		SetUpdateInterval( context, RandomizeOffsetForUpdateInterval( 0.2 ) );
	}

	protected export override function Update( context : ScriptExecutionContext ) : AIbehaviorUpdateOutcome
	{
		var rawCommand : IScriptable;
		var typedCommand : AIFollowerTakedownCommand;
		var target : weak< GameObject >;
		var targetEntityIds : array< EntityID >;
		if( !( m_inCommand ) )
		{
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		rawCommand = ScriptExecutionContext.GetScriptableMappingValue( context, m_inCommand );
		typedCommand = ( ( AIFollowerTakedownCommand )( rawCommand ) );
		if( !( typedCommand ) )
		{
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		if( typedCommand.target )
		{
			target = typedCommand.target;
		}
		else
		{
			GetFixedEntityIdsFromEntityReference( typedCommand.targetRef, ScriptExecutionContext.GetOwner( context ).GetGame(), targetEntityIds );
			if( !( SelectBestTarget( context, targetEntityIds, target ) ) )
			{
				target = ScriptExecutionContext.PuppetRefToObject( context, typedCommand.targetRef );
				if( target && ( !( ScriptedPuppet.IsActive( target ) ) || ScriptedPuppet.IsBeingGrappled( target ) ) )
				{
					target = NULL;
				}
			}
		}
		if( !( target ) )
		{
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		if( ScriptExecutionContext.GetArgumentObject( context, 'CombatTarget' ) != target )
		{
			ScriptExecutionContext.SetArgumentObject( context, 'CombatTarget', target );
		}
		NPCPuppet.ChangeHighLevelState( ScriptExecutionContext.GetOwner( context ), gamedataNPCHighLevelState.Stealth );
		return AIbehaviorUpdateOutcome.IN_PROGRESS;
	}

	private export override final function Deactivate( context : ScriptExecutionContext )
	{
		var rawCommand : IScriptable;
		var takedownCommand : AIFollowerTakedownCommand;
		var aiComponent : AIHumanComponent;
		rawCommand = ScriptExecutionContext.GetScriptableMappingValue( context, m_inCommand );
		takedownCommand = ( ( AIFollowerTakedownCommand )( rawCommand ) );
		aiComponent = GetPuppet( context ).GetAIControllerComponent();
		if( takedownCommand )
		{
			aiComponent.StopExecutingCommand( takedownCommand, true );
		}
		ScriptExecutionContext.SetArgumentObject( context, 'CombatTarget', NULL );
		ScriptExecutionContext.SetMappingValue( context, m_inCommand, NULL );
	}

	private final function SelectBestTarget( context : ScriptExecutionContext, const targetEntityIds : ref< array< EntityID > >, out target : weak< GameObject > ) : Bool
	{
		var i : Int32;
		var validTargets : array< GameObject >;
		var distToTarget : Float;
		var shortestDistance : Float;
		var selectedTargetIndex : Int32;
		if( targetEntityIds.Size() == 0 )
		{
			return false;
		}
		for( i = 0; i < targetEntityIds.Size(); i += 1 )
		{
			target = ( ( GameObject )( GameInstance.FindEntityByID( ScriptExecutionContext.GetOwner( context ).GetGame(), targetEntityIds[ i ] ) ) );
			if( ( target && ScriptedPuppet.IsActive( target ) ) && !( ScriptedPuppet.IsBeingGrappled( target ) ) )
			{
				validTargets.PushBack( target );
			}
		}
		selectedTargetIndex = -1;
		for( i = 0; i < validTargets.Size(); i += 1 )
		{
			distToTarget = Vector4.Distance( ScriptExecutionContext.GetOwner( context ).GetWorldPosition(), validTargets[ i ].GetWorldPosition() );
			if( ( i == 0 ) || ( distToTarget < shortestDistance ) )
			{
				shortestDistance = distToTarget;
				selectedTargetIndex = i;
			}
		}
		if( selectedTargetIndex >= 0 )
		{
			target = validTargets[ selectedTargetIndex ];
			return target != NULL;
		}
		target = NULL;
		return false;
	}

}

class AIFollowerTakedownCommandDelegate extends ScriptBehaviorDelegate
{
	editable inlined var m_inCommand : AIArgumentMapping;
	var approachBeforeTakedown : Bool;
	var doNotTeleportIfTargetIsVisible : Bool;

	public export function OnActivate( context : ScriptExecutionContext ) : Bool
	{
		var rawCommand : IScriptable;
		var typedCommand : AIFollowerTakedownCommand;
		if( m_inCommand )
		{
			rawCommand = ScriptExecutionContext.GetScriptableMappingValue( context, m_inCommand );
			typedCommand = ( ( AIFollowerTakedownCommand )( rawCommand ) );
		}
		if( typedCommand )
		{
			approachBeforeTakedown = typedCommand.approachBeforeTakedown;
			doNotTeleportIfTargetIsVisible = typedCommand.doNotTeleportIfTargetIsVisible;
		}
		return true;
	}

	public constexpr export function OnDeactivate( context : ScriptExecutionContext ) : Bool
	{
		return true;
	}

}

class AIFollowerInterpolateFollowingSpeed extends AIbehaviortaskScript
{
	[ customEditor = "TweakDBGroupInheritance;AIActionCondition" ]
	editable var m_enterCondition : TweakDBID;
	[ customEditor = "TweakDBGroupInheritance;AIActionCondition" ]
	editable var m_exitCondition : TweakDBID;
	editable var m_minInterpolationDistanceToDestination : Float;
	editable var m_maxInterpolationDistanceToDestination : Float;
	editable var m_maxTimeDilation : Float;
	private var m_enterConditionInstance : weak< AIActionCondition_Record >;
	private var m_exitConditionInstace : weak< AIActionCondition_Record >;
	private var m_isActive : Bool;
	private var m_reason : CName;
	default m_reason = 'Following';

	protected export override function Activate( context : ScriptExecutionContext )
	{
		m_enterConditionInstance = TweakDBInterface.GetAIActionConditionRecord( m_enterCondition );
		m_exitConditionInstace = TweakDBInterface.GetAIActionConditionRecord( m_exitCondition );
		m_isActive = false;
		SetUpdateInterval( context, RandomizeOffsetForUpdateInterval( 0.2 ) );
	}

	private function MakeActive( context : ScriptExecutionContext )
	{
		m_isActive = true;
	}

	private function MakeInactive( context : ScriptExecutionContext )
	{
		m_isActive = false;
		if( ScriptExecutionContext.GetOwner( context ).HasIndividualTimeDilation( m_reason ) )
		{
			ScriptExecutionContext.GetOwner( context ).UnsetIndividualTimeDilation( 'KereznikovDodgeEaseOut' );
		}
	}

	protected export override function Update( context : ScriptExecutionContext ) : AIbehaviorUpdateOutcome
	{
		var alpha : Float;
		var distance : Float;
		var movePoliciesComponent : MovePoliciesComponent;
		if( m_isActive && AICondition.CheckActionCondition( context, m_exitConditionInstace ) )
		{
			MakeInactive( context );
		}
		else if( !( m_isActive ) && AICondition.CheckActionCondition( context, m_enterConditionInstance ) )
		{
			MakeActive( context );
		}
		if( m_isActive )
		{
			if( !( ScriptExecutionContext.GetOwner( context ).HasIndividualTimeDilation() ) || ScriptExecutionContext.GetOwner( context ).HasIndividualTimeDilation( m_reason ) )
			{
				movePoliciesComponent = ( ( ScriptedPuppet )( ScriptExecutionContext.GetOwner( context ) ) ).GetMovePolicesComponent();
				distance = movePoliciesComponent.GetDistanceToDestination();
				if( ScriptedPuppet.IsPlayerCompanion( ScriptExecutionContext.GetOwner( context ) ) )
				{
					distance += Vector4.Distance( GameInstance.GetPlayerSystem( ScriptExecutionContext.GetOwner( context ).GetGame() ).GetLocalPlayerControlledGameObject().GetWorldPosition(), movePoliciesComponent.GetDestination() );
				}
				alpha = ( distance - m_minInterpolationDistanceToDestination ) / ( m_maxInterpolationDistanceToDestination - m_minInterpolationDistanceToDestination );
				alpha = ClampF( alpha, 0.0, 1.0 );
				ScriptExecutionContext.GetOwner( context ).SetIndividualTimeDilation( m_reason, 1.0 + ( alpha * ( m_maxTimeDilation - 1.0 ) ), -1.0, '', '' );
			}
		}
		return AIbehaviorUpdateOutcome.IN_PROGRESS;
	}

	private export override final function Deactivate( context : ScriptExecutionContext )
	{
		MakeInactive( context );
	}

}

class AIFollowerBeforeTakedown extends AIbehaviortaskScript
{

	protected export override function Update( context : ScriptExecutionContext ) : AIbehaviorUpdateOutcome
	{
		var combatTarget : weak< GameObject >;
		combatTarget = ScriptExecutionContext.GetArgumentObject( context, 'CombatTarget' );
		if( !( combatTarget ) )
		{
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		if( !( ScriptedPuppet.IsActive( combatTarget ) ) )
		{
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		if( ScriptedPuppet.IsBeingGrappled( combatTarget ) )
		{
			return AIbehaviorUpdateOutcome.FAILURE;
		}
		TakedownUtils.SetTargetBodyType( ScriptExecutionContext.GetOwner( context ), combatTarget, true );
		TakedownUtils.SetTargetBodyType( combatTarget, ScriptExecutionContext.GetOwner( context ), true );
		return AIbehaviorUpdateOutcome.SUCCESS;
	}

}

