importonly struct AINavigationSystemQuery
{
	import var source : AIPositionSpec;
	import var target : AIPositionSpec;
	import var allowedTags : array< CName >;
	import var blockedTags : array< CName >;
}

importonly struct AINavigationSystemResult
{
	import var hasFailed : Bool;
	import var hasPath : Bool;
	import var hasClosestPointOnNavmesh : Bool;
	import var hasClosestReachablePoint : Bool;
	import var lastSourcePosition : WorldPosition;
	import var lastTargetPosition : WorldPosition;
	import var closestPointOnNavmesh : WorldPosition;
	import var closestReachablePoint : WorldPosition;
}

import abstract class AIINavigationSystem extends IGameSystem
{
}

import class AINavigationSystem extends AIINavigationSystem
{
	public import function StartPathfinding( query : ref< AINavigationSystemQuery > ) : Uint32;
	public import function StopPathfinding( id : Uint32 ) : Bool;
	public import function GetResult( id : Uint32, result : ref< AINavigationSystemResult > ) : Bool;
	public import const function CalculatePathForCharacter( startPoint : Vector4, endPoint : Vector4, findPointTolerance : Float, owner : Entity, optional costModCircle : NavigationCostModCircle ) : NavigationPath;
	public import function FindWallInLineForCharacter( startPoint : Vector4, endPoint : Vector4, findPointTolerance : Float, owner : Entity ) : NavigationFindWallResult;
	public import const function FindPointInSphereForCharacter( center : Vector4, radius : Float, owner : Entity ) : NavigationFindPointResult;
	public import const function FindPointInBoxForCharacter( center : Vector4, extents : Vector4, owner : Entity ) : NavigationFindPointResult;

	public const function IsPointOnNavmesh( const character : weak< Entity >, point : Vector4, optional tolerance : Float ) : Bool
	{
		var pointResults : NavigationFindPointResult;
		if( tolerance <= 0.0 )
		{
			tolerance = 0.5;
		}
		pointResults = FindPointInSphereForCharacter( point, tolerance, character );
		if( pointResults.status != worldNavigationRequestStatus.OK )
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	public const function IsPointOnNavmesh( const character : weak< Entity >, point : Vector4, tolerance : Vector4 ) : Bool
	{
		var pointResults : NavigationFindPointResult;
		pointResults = FindPointInBoxForCharacter( point, tolerance, character );
		if( pointResults.status != worldNavigationRequestStatus.OK )
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	public const function IsPointOnNavmesh( const character : weak< Entity >, point : Vector4, tolerance : Vector4, out navmeshPoint : Vector4 ) : Bool
	{
		var pointResults : NavigationFindPointResult;
		pointResults = FindPointInBoxForCharacter( point, tolerance, character );
		navmeshPoint = pointResults.point;
		if( pointResults.status != worldNavigationRequestStatus.OK )
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	public const function FindPointBelowForCharacter( origin : Vector4, querySphereRadius : Float, numberOfSpheres : Int32, const character : weak< Entity >, out resultingZOffset : Float ) : NavigationFindPointResult
	{
		var i : Int32;
		var pointResults : NavigationFindPointResult;
		for( i = 0; i < numberOfSpheres; i += 1 )
		{
			pointResults = FindPointInSphereForCharacter( origin, querySphereRadius, character );
			if( pointResults.status == worldNavigationRequestStatus.OK )
			{
				break;
			}
			origin.Z -= querySphereRadius;
		}
		resultingZOffset = ( -1.0 * ( ( Float )( i ) ) ) * querySphereRadius;
		return pointResults;
	}

	public const function FindPointBelowForCharacter( origin : Vector4, querySphereRadius : Float, numberOfSpheres : Int32, const character : weak< Entity > ) : NavigationFindPointResult
	{
		var resultingZOffset : Float;
		return FindPointBelowForCharacter( origin, querySphereRadius, numberOfSpheres, character, resultingZOffset );
	}

	public const function GetNearestNavmeshPointBelow( const character : weak< Entity >, origin : Vector4, querySphereRadius : Float, numberOfSpheres : Int32 ) : Vector4
	{
		var pointResults : NavigationFindPointResult;
		var point : Vector4;
		pointResults = FindPointBelowForCharacter( origin, querySphereRadius, numberOfSpheres, character );
		if( pointResults.status == worldNavigationRequestStatus.OK )
		{
			point = pointResults.point;
		}
		return point;
	}

	public const function GetNearestNavmeshPointBehind( const origin : weak< Entity >, const querySphereRadius : Float, const numberOfSpheres : Int32, out point : Vector4, optional checkPathToOrigin : Bool ) : Bool
	{
		var i : Int32;
		var pointResults : NavigationFindPointResult;
		var originPosition : Vector4;
		var originHeading : Vector4;
		var center : Vector4;
		var navigationPath : NavigationPath;
		if( !( origin ) )
		{
			false;
		}
		originPosition = origin.GetWorldPosition();
		originHeading = origin.GetWorldForward();
		for( i = 1; i <= numberOfSpheres; i += 1 )
		{
			center = originPosition - ( ( originHeading * querySphereRadius ) * ( ( Float )( i ) ) );
			pointResults = FindPointInSphereForCharacter( center, querySphereRadius, origin );
			if( pointResults.status != worldNavigationRequestStatus.OK )
			{
				continue;
			}
			else
			{
				point = pointResults.point;
				if( checkPathToOrigin )
				{
					navigationPath = CalculatePathForCharacter( point, origin.GetWorldPosition(), 0.5, origin );
					if( navigationPath.path.Size() <= 0 )
					{
						continue;
					}
				}
				return true;
			}
		}
		return false;
	}

	public const function GetFurthestNavmeshPointBehind( const origin : weak< Entity >, const querySphereRadius : Float, const numberOfSpheres : Int32, out point : Vector4, optional const offsetFromOrigin : Vector4, optional checkPathToOrigin : Bool, optional ratioCurveName : CName ) : Bool
	{
		var i : Int32;
		var pointResults : NavigationFindPointResult;
		var originPosition : Vector4;
		var originHeading : Vector4;
		var center : Vector4;
		var navigationPath : NavigationPath;
		var statsDataSystem : StatsDataSystem;
		var pathLength : Float;
		var directLength : Float;
		var ratio : Float;
		if( !( origin ) )
		{
			false;
		}
		statsDataSystem = GameInstance.GetStatsDataSystem( ( ( GameObject )( origin ) ).GetGame() );
		originPosition = origin.GetWorldPosition() + offsetFromOrigin;
		originHeading = origin.GetWorldForward();
		for( i = numberOfSpheres; i > 0; i -= 1 )
		{
			center = originPosition - ( ( originHeading * querySphereRadius ) * ( ( Float )( i ) ) );
			pointResults = FindPointInSphereForCharacter( center, querySphereRadius, origin );
			if( pointResults.status != worldNavigationRequestStatus.OK )
			{
				continue;
			}
			else
			{
				point = pointResults.point;
				if( checkPathToOrigin )
				{
					navigationPath = CalculatePathForCharacter( point, origin.GetWorldPosition(), 0.5, origin );
					if( navigationPath.path.Size() <= 0 )
					{
						continue;
					}
					if( ratioCurveName != '' && statsDataSystem )
					{
						directLength = Vector4.Length( originPosition - point );
						pathLength = navigationPath.CalculateLength();
						ratio = statsDataSystem.GetValueFromCurve( 'pathLengthToDirectDistancesRatio', pathLength, ratioCurveName );
						if( ( ratio > 1.0 ) && ( pathLength > ( ratio * directLength ) ) )
						{
							continue;
						}
					}
				}
				return true;
			}
		}
		return false;
	}

	public const function GetFurthestNavmeshPointInFront( const origin : weak< Entity >, const querySphereRadius : Float, const numberOfSpheres : Int32, out point : Vector4, optional const offsetFromOrigin : Vector4, optional checkPathToOrigin : Bool, optional ratioCurveName : CName ) : Bool
	{
		var i : Int32;
		var pointResults : NavigationFindPointResult;
		var originPosition : Vector4;
		var originHeading : Vector4;
		var center : Vector4;
		var navigationPath : NavigationPath;
		var statsDataSystem : StatsDataSystem;
		var pathLength : Float;
		var directLength : Float;
		var ratio : Float;
		if( !( origin ) )
		{
			false;
		}
		statsDataSystem = GameInstance.GetStatsDataSystem( ( ( GameObject )( origin ) ).GetGame() );
		originPosition = origin.GetWorldPosition() + offsetFromOrigin;
		originHeading = origin.GetWorldForward();
		for( i = numberOfSpheres; i > 0; i -= 1 )
		{
			center = originPosition - ( ( originHeading * querySphereRadius ) * ( ( Float )( i ) ) );
			pointResults = FindPointInSphereForCharacter( center, querySphereRadius, origin );
			if( pointResults.status != worldNavigationRequestStatus.OK )
			{
				continue;
			}
			else
			{
				point = pointResults.point;
				if( checkPathToOrigin )
				{
					navigationPath = CalculatePathForCharacter( point, origin.GetWorldPosition(), 0.5, origin );
					if( navigationPath.path.Size() <= 0 )
					{
						continue;
					}
					if( ratioCurveName != '' && statsDataSystem )
					{
						directLength = Vector4.Length( originPosition - point );
						pathLength = navigationPath.CalculateLength();
						ratio = statsDataSystem.GetValueFromCurve( 'pathLengthToDirectDistancesRatio', pathLength, ratioCurveName );
						if( ( ratio > 1.0 ) && ( pathLength < ( ratio * directLength ) ) )
						{
							continue;
						}
					}
				}
				return true;
			}
		}
		return false;
	}

	public const function TryToFindNavmeshPointAroundPoint( const owner : weak< Entity >, const originPosition : Vector4, const originOrientation : Quaternion, const probeDimensions : Vector4, const numberOfSpheres : Int32, const sphereDistanceFromOrigin : Float, out point : Vector4, optional checkPathToOrigin : Bool ) : Bool
	{
		var i : Int32;
		var pointResults : NavigationFindPointResult;
		var currentCheckPosition : Vector4;
		var currentAngle : Float;
		var currentAngleRad : Float;
		var quat : Quaternion;
		var navigationPath : NavigationPath;
		for( i = numberOfSpheres; i > 0; i -= 1 )
		{
			Quaternion.SetIdentity( quat );
			currentAngleRad = Deg2Rad( currentAngle );
			Quaternion.SetZRot( quat, currentAngleRad );
			quat = originOrientation * quat;
			currentCheckPosition = originPosition + ( Quaternion.GetForward( quat ) * sphereDistanceFromOrigin );
			pointResults = FindPointInBoxForCharacter( currentCheckPosition, probeDimensions, owner );
			currentAngle += ( ( Float )( ( 360 / numberOfSpheres ) ) );
			if( pointResults.status != worldNavigationRequestStatus.OK )
			{
				continue;
			}
			else
			{
				point = pointResults.point;
				if( checkPathToOrigin )
				{
					navigationPath = CalculatePathForCharacter( point, originPosition, 0.5, owner );
					if( navigationPath.path.Size() <= 0 )
					{
						continue;
					}
				}
				return true;
			}
		}
		return false;
	}

	public const function HasPathForward( const sourceObject : weak< GameObject >, const distance : Float ) : Bool
	{
		var originPoint : Vector4;
		var originPointNavmeshResult : NavigationFindPointResult;
		var destinationPoint : Vector4;
		var destinationPointNavmeshResult : NavigationFindPointResult;
		var navigationPath : NavigationPath;
		originPoint = sourceObject.GetWorldPosition();
		destinationPoint = originPoint + ( Vector4.Normalize( sourceObject.GetWorldForward() ) * distance );
		originPointNavmeshResult = FindPointInBoxForCharacter( originPoint, Vector4( 0.2, 0.2, 0.75, 1.0 ), sourceObject );
		destinationPointNavmeshResult = FindPointInBoxForCharacter( destinationPoint, Vector4( 0.2, 0.2, 0.75, 1.0 ), sourceObject );
		if( originPointNavmeshResult.status == worldNavigationRequestStatus.OK && destinationPointNavmeshResult.status == worldNavigationRequestStatus.OK )
		{
			navigationPath = CalculatePathForCharacter( originPointNavmeshResult.point, destinationPointNavmeshResult.point, 0.5, sourceObject );
			if( navigationPath.path.Size() > 0 )
			{
				return true;
			}
		}
		return false;
	}

	public static function HasPathFromAtoB( const owner : weak< Entity >, game : GameInstance, originPoint : Vector4, targetPoint : Vector4 ) : Bool
	{
		var navigationPath : NavigationPath;
		if( !( GameInstance.IsValid( game ) ) )
		{
			return false;
		}
		navigationPath = GameInstance.GetAINavigationSystem( game ).CalculatePathForCharacter( originPoint, targetPoint, 0.5, owner );
		if( navigationPath.path.Size() <= 0 )
		{
			return false;
		}
		return true;
	}

}

class ExampleNavigationTask extends AIbehaviortaskScript
{
	private var m_queryId : Uint32;
	private var m_queryStarted : Bool;

	protected override function Activate( context : ScriptExecutionContext )
	{
		var target : weak< GameObject >;
		var system : AINavigationSystem;
		var query : AINavigationSystemQuery;
		m_queryStarted = false;
		target = ScriptExecutionContext.GetArgumentObject( context, 'CombatTarget' );
		if( target )
		{
			system = GameInstance.GetAINavigationSystem( ScriptExecutionContext.GetOwner( context ).GetGame() );
			AIPositionSpec.SetEntity( query.source, ScriptExecutionContext.GetOwner( context ) );
			AIPositionSpec.SetEntity( query.target, target );
			m_queryId = system.StartPathfinding( query );
			m_queryStarted = true;
		}
	}

	protected override function Deactivate( context : ScriptExecutionContext )
	{
		var system : AINavigationSystem;
		if( m_queryStarted )
		{
			system = GameInstance.GetAINavigationSystem( ScriptExecutionContext.GetOwner( context ).GetGame() );
			system.StopPathfinding( m_queryId );
		}
	}

}

class FindNavmeshPointAroundThePlayer extends AIbehaviortaskScript
{
	editable inlined var m_outPositionArgument : AIArgumentMapping;

	protected override function Activate( context : ScriptExecutionContext )
	{
		var owner : ScriptedPuppet;
		var player : GameObject;
		var navigationSystem : AINavigationSystem;
		var playerPosition : Vector4;
		var navmeshPosition : Vector4;
		var pointResults : NavigationFindPointResult;
		var tolerance : Vector4;
		owner = GetPuppet( context );
		player = GameInstance.GetPlayerSystem( owner.GetGame() ).GetLocalPlayerControlledGameObject();
		playerPosition = player.GetWorldPosition();
		navigationSystem = GameInstance.GetAINavigationSystem( owner.GetGame() );
		tolerance = Vector4( 0.5, 0.5, 0.5, 1.0 );
		if( owner.GetNPCType() == gamedataNPCType.Cerberus )
		{
			tolerance = Vector4( 3.0, 3.0, 5.0, 1.0 );
		}
		if( navigationSystem.IsPointOnNavmesh( owner, playerPosition, 0.2 ) )
		{
			ScriptExecutionContext.SetMappingValue( context, m_outPositionArgument, playerPosition );
			return;
		}
		pointResults = navigationSystem.FindPointInBoxForCharacter( playerPosition, tolerance, owner );
		if( pointResults.status == worldNavigationRequestStatus.OK )
		{
			ScriptExecutionContext.SetMappingValue( context, m_outPositionArgument, pointResults.point );
			return;
		}
		if( navigationSystem.TryToFindNavmeshPointAroundPoint( owner, playerPosition, player.GetWorldOrientation(), tolerance, 5, 3.0, navmeshPosition, true ) )
		{
			ScriptExecutionContext.SetMappingValue( context, m_outPositionArgument, navmeshPosition );
			return;
		}
		ScriptExecutionContext.SetMappingValue( context, m_outPositionArgument, playerPosition );
	}

}

