//=============================================================================
// The extended navigation point
// Native branch class only, rename this file if compiled in normal mode
// This actor should automatically become bNoDelete after being spawned
//
// For testing purposes, do not modify or redistribute
// Have suggestions? Contact me.
// caco_fff@hotmail.com - Higor at UT99.org
//=============================================================================
class Botz_NavigBase expands NavigationPoint
	native;

#exec TEXTURE IMPORT NAME=BWP_Normal FILE=..\CompileData\BWP_Normal.bmp FLAGS=2

var float MaxDistance; //Maximum hook distance, used by automatic pather, setting to 0 ignores all pathing
var bool bPushSave;
var bool bOneWayInc; //Limits Incoming paths
var bool bOneWayOut; //Limits Outgoing paths
var bool bFlying; //Used to force special reachspec creation
var bool bHighPath; //This path is a high node, don't start walk-only connections here (only fall)
var bool bCustomFlags; //Call ModifyFlags() event here on USCRIPT when the reachflags are being set
var bool bDirectConnect; //Dynamic Player will display the hook target
var bool bNeverPrune; //This node is dynamic, do not prune paths
var int ReservePaths; //Reserve this amount of slots (during native path node detection)
var int ReserveUpstreamPaths; //Reserve this amount of slots
var string FriendlyName;
var Botz_PathLoader MyLoader;

enum EPathMode
{
	PM_None, //Don't path
	PM_Normal, //Do normal processing
	PM_Forced //Force this path!
};

event PostBeginPlay()
{
	bHighPath = !FastLowCheck( Location);
}

//Called after botZ decides this is the path to take
function bool PostPathEvaluate( botz other);
function QueuedForNavigation( Botz Other, byte Slot);

function bool PathVisible( NavigationPoint Start, NavigationPoint End, optional bool IgnoreMovers)
{
	local Actor A;
	local vector HitLocation, HitNormal;
	
	ForEach TraceActors (class'Actor', A, HitLocation, HitNormal, End.Location, Start.Location)
	{
		if ( A == Level )
			return false;
		if ( A.IsA('BlockMonsters') )
			A.SetCollision(false,false,false);
		if ( A.IsA('BlockAll') )
			return false;
		if ( !IgnoreMovers && Mover(A) != none )
			return false;
	}
	return true;
}

//********************LockActor - Safely sets bNoDelete and hooks to navigation point list
native final function LockActor( bool bLock, optional NavigationPoint Other); 

//************************CreateReachSpec - Inserts a reachspec into memory, returns it's index
native final function int CreateReachSpec();

//********************PathCandidates - Runs an automatic path build routine in runtime
native final function PathCandidates();

//************************ExistingPath - returns index of reachspec if already exists, -1 for no reachspec
native final function int ExistingPath( actor Dest);

//*************************EditReachSpec - Runtime setting of a reachspec, useful for placeable teleporters (siege)
native final function bool EditReachSpec( int ReachSpec, actor Start, actor End, optional float CollisionHeight, optional float CollisionRadius, optional bool bTele);

//************************UnusedReachSpec - Returns a reachspec with no start and end (dynamic reachspec), -1 for failure
native final function int UnusedReachSpec();

//************************************ConnectedDests - returns linked paths and corresponding reachspecs
native static final function iterator ConnectedDests( NavigationPoint Start, out Actor End, out int ReachSpecIdx);
//Expect a crash if iterated withouth a Start point

//*******************************IsConnectedTo - Fast connection check, can include pruned paths
native static final function int IsConnectedTo( NavigationPoint N, NavigationPoint Other, optional bool bIncPruned);

//***************************ClearAllPaths - Makes a navigation point safe to delete
native static final function ClearAllPaths( NavigationPoint Other);

//*******************************Find unused slot in array
native static final function int FreePathSlot( NavigationPoint N);
native static final function int FreeUpstreamSlot( NavigationPoint N);


//** This event will alter the ReachFlags between self and Dest
event int ModifyFlags( NavigationPoint Dest, int CurFlags)
{
	return CurFlags;
}

//** This event describes how a custom navig-base should connect
// Forced prevents OtherIsCandidate from being called, so pair it with a Trace if necessary
event EPathMode IsCandidateTo( Botz_NavigBase Other)
{
	return PM_Normal;
}

//** This event describes how all paths connect to this, called after IsCandidateTo on NavigBases
event EPathMode OtherIsCandidate( NavigationPoint Nav)
{
	if ( Nav.IsA('LiftCenter') ) //No lift centers
		return PM_None;
	if ( Nav.IsA('SpawnPoint') && (VSize(Nav.Location - Location) > MaxDistance * 0.5) ) //Avoid distant SpawnPoints
		return PM_None;
	if ( PathVisible( Self, Nav) )
		return PM_Normal;
	return PM_None;
}

//** Called after selection routine decides pathing is possible
//** This function is called a second time with reversed End-Start if we're connecting to a non-NavigBase point
event AddPathHere( NavigationPoint Start, NavigationPoint End, bool bForce)
{
	local int WlkCost;
	local int aReach, aSlot, uSlot, uReach;
	local actor A;
	local vector aDir, eDir;

	//Custom prune check, avoid creating paths from legacy navigation points to here
	if ( (FreePathSlot(self) < 0) || (FreeUpstreamSlot( End) < 0) ||  MyLoader.IsPruned( Start, End) )
	{
//		if ( Start.IsA('Botz_NavigBase') && End.IsA('Botz_NavigBase') )
//			Log("PRE PATH CHECK APPLIED");
		return;
	}

	if ( !bForce ) //See if this is a likely prune dest, discard before expensive checks
	{
		if ( Start == self  && !bNeverPrune )
		{
			ForEach ConnectedDests( self, A, uReach)
			{
				if ( (VSize(A.Location - Location) < VSize(End.Location - Location)) && !NavigationPoint(A).bSpecialCost ) //This path is further away...
				{
					if ( IsConnectedTo( NavigationPoint(A), End) < 0 || IsConnectedTo( End, NavigationPoint(A)) < 0 || (FreeUpstreamSlot(NavigationPoint(A)) < 0) )
						continue;
					aDir = Normal( (Location - A.Location) * vect(1,1,0.5) ); //Greater Z tolerance
					eDir = Normal( (Location - End.Location) * vect(1,1,0.5) );
					if ( VSize( aDir - eDir) < 0.5 ) //Prune threshold
					{
						MyLoader.AddPrune( self, End ); //Add a prune rule here
						MyLoader.AddPrune( End, self);
	//					Log("PRUNE RULE APPLIED!");
						return;
					}
				}
			}
		}
		WlkCost = WalkableCost( Start, End);
	}

	//This path is walkable, do reverse, 1 sided pruning now (discard distant path in favor of closer one)
	if ( WlkCost == 0  && !bNeverPrune )
	{
		ForEach ConnectedDests( Start, A, uReach)
		{
			if ( (VSize(A.Location - Start.Location) > VSize(End.Location - Location)) && !NavigationPoint(A).bSpecialCost )
			{
				if ( IsConnectedTo( NavigationPoint(A), End) < 0 || IsConnectedTo( End, NavigationPoint(A)) < 0 )
					continue;
				aDir = Normal( (Location - A.Location) * vect(1,1,0.5) ); //Greater Z tolerance
				eDir = Normal( (Location - End.Location) * vect(1,1,0.5) );
				if ( VSize( aDir - eDir) < 0.5 ) //Prune threshold
				{
					MyLoader.AddPrune( Start, NavigationPoint(A) ); //Add a prune rule here
					EditReachSpec( uReach, none, none);
					For ( uSlot=0 ; uSlot<16 ; uSlot++ )
						if ( Start.Paths[uSlot] == uReach )
						{
							Start.Paths[uSlot] = -1;
							break;
						}
					For ( uSlot=0 ; uSlot<16 ; uSlot++ )
						if ( End.UpstreamPaths[uSlot] == uReach )
						{
							End.UpstreamPaths[uSlot] = -1;
							break;
						}
					if ( !A.IsA('Botz_NavigBase') && Start.IsA('Botz_NavigBase') ) //Avoid overloading of upstream paths
						MyLoader.AddPrune( NavigationPoint(A), Start);
	//				Log("PRUNE RULE APPLIED! CLOSER METHOD");
					return;
				}
			}
		}
	}

	if ( WlkCost == 0 || WlkCost == 1 )
	{
		aReach = UnusedReachSpec();
		if ( aReach < 0 )
			aReach = CreateReachSpec();
		EditReachSpec( aReach, Start, End);
		aSlot = FreePathSlot( Start);
		uSlot = FreeUpstreamSlot( End);
		if ( aSlot < 0 || uSlot < 0 )
		{
			EditReachSpec( aReach, none, none);
			Log("REACHSPECS FULL");
			return;
		}
		Start.Paths[aSlot] = aReach;
		End.UpstreamPaths[uSlot] = aReach;
		
		if ( !bForce && WlkCost == 0) //2 way normal path
			AddPathHere( End, Start, true); //Set forced flag, we alreay got a good check
	}
}

//** Called when this path has already iterated, do post reachspec treatment here
event FinishedPathing();


//Return description:
//2: no connection
//1: must jump hole or obstacle
//0: Fully walkable
//Visibility check assumed
function int WalkableCost( NavigationPoint Start, NavigationPoint End)
{
	local vector HitLocation, HitNormal;;
	local float SteepDistance, LastZHit;
	local vector SteepStart, SteepEnd;
	local bool bHasHoles; //Must jump!
	local float AlphaRoute, AlphaInc;
	local vector CurV, LowV, Trajectory;
	local int RealSteps, curSteps, i;
	local bool bCurSteep;
	local float jZ;

	if ( Start.Region.Zone.bWaterZone && End.Region.Zone.bWaterZone )
		return 0;

	jZ = class'Botz'.default.JumpZ * fMin(1.18, Level.Game.PlayerJumpZScaling());

	Trajectory = End.Location - Start.Location;
	AlphaRoute = VSize(Trajectory * vect(1,1,0) );
	if ( Class'BotzFunctionManager'.static.CanFlyTo( Start.Location, End.Location, Start.Region.Zone.ZoneGravity.Z, jZ, class'Botz'.default.GroundSpeed) ) //Jumpable lol
		return 1;
	else if ( AlphaRoute < Trajectory.Z ) //Unjumpable
	{
//		Log("FAIL ON PASS 1");
		return 2;
	}
	else if ( IsHighPath(Start) || IsHighPath(End) )
		return 2;

	RealSteps = AlphaRoute / 8; //Horizontal steps every 8 units or so
	AlphaInc = VSize(Trajectory) / RealSteps;

	if ( RealSteps <= 1 ) //Paths' horizontal uber close
	{
		AlphaRoute = VSize(Trajectory);
		if ( AlphaRoute < 60 )
			return 0;
		else if ( Start.Location.Z > End.Location.Z )
			return 1;
//		Log("FAIL ON PASS 2");
		return 2;
	}

	i=1;
	AlphaRoute = AlphaInc;
	Trajectory = Normal(Trajectory);
	CollideTrace( LowV, HitNormal, Start.Location - vect(0,0,300), Start.Location);

	THIS_ALPHA: //Iterator
	if ( i >= RealSteps )
	{
		if ( bCurSteep )
		{
			SteepStart.Z += 39;
			if ( !Class'BotzFunctionManager'.static.CanFlyTo( SteepStart, End.Location, End.Region.Zone.ZoneGravity.Z, jZ, class'Botz'.default.GroundSpeed) )
			{
//				Log("FAIL ON PASS 3");
				return 2;
			}
			return 1;
		}


		if ( !bHasHoles )
			return 0;
		return 1;
	}

	CurV = Start.Location + Trajectory * AlphaRoute;

	if ( !CollideTrace( HitLocation, HitNormal, CurV - vect(0,0,300), CurV) )
	{
		if ( !bCurSteep )
		{
			SteepStart = LowV;
			bCurSteep = true;
		}
	}
	else
	{
		if ( !bCurSteep )
		{
			if ( HitLocation.Z - LowV.Z > 40 ) //Path's suddenly gained altitude...
				if ( !Class'BotzFunctionManager'.static.CanFlyTo( LowV, HitLocation, Start.Region.Zone.ZoneGravity.Z, jZ, class'Botz'.default.GroundSpeed) ) //Can't jump over it
				{
					return 2;
				}
			if ( HitNormal.Z < 0.7854 )
			{
				SteepStart = LowV;
				bCurSteep = true;
			}
		}
		else
		{
			if ( abs( HitLocation.Z - SteepStart.Z) <  VSize((SteepStart - HitLocation) * vect(0.8,0.8,0)) )
			{
				SteepEnd = HitLocation;
				if ( !Class'BotzFunctionManager'.static.CanFlyTo( SteepStart, SteepEnd, Start.Region.Zone.ZoneGravity.Z, jZ, class'Botz'.default.GroundSpeed) )
				{
//					Log("FAIL ON PASS 4");
					return 2;
				}
				bCurSteep = false;
				bHasHoles = true;
			}
		}

		LowV = HitLocation;
	}
	i++;
	AlphaRoute += AlphaInc;
	Goto THIS_ALPHA;
}

function bool IsHighPath( NavigationPoint N)
{
	if ( N.IsA('Botz_NavigBase') )
		return Botz_NavigBase(N).bHighPath;
	return !FastLowCheck( N.Location);
}

function bool CollideTrace( out vector HitLocation, out vector HitNormal, vector End, optional vector Start)
{
	local actor tempActor;

	tempActor = self;
	if ( Start == vect(0,0,0) )		Start = Location;

	ForEach TraceActors ( class'Actor', tempActor, HitLocation, HitNormal, End, Start)
	{
		if ( ((tempActor.bStatic || tempActor.bNoDelete) && (tempActor.bBlockActors || tempActor.bBlockPlayers)) || (tempActor == Level) )
			return true;
	}
	HitLocation = End;
	HitNormal = normal( Start - End);
	return false;
}

function bool FastLowCheck( vector Start)
{
	local vector HitLocation, HitNormal;
	local actor tempActor;

	tempActor = self;
	ForEach TraceActors ( class'Actor', tempActor, HitLocation, HitNormal, Start - vect(0,0,100), Start)
	{
		if ( ((tempActor.bStatic || tempActor.bNoDelete) && (tempActor.bBlockActors || tempActor.bBlockPlayers)) || (tempActor == Level) )
			return true;
	}
	return false;
}


event Destroyed()
{
	local int i;

	if ( bNoDelete || bStatic )
		return;
		
//Clear reachspecs and indices
	For ( i=0 ; i<16 ; i++ )
	{
		//Clear outgoing
		if ( Paths[i] != -1 )
			EditReachSpec( Paths[i], none, none, 1, 1);
		if ( PrunedPaths[i] != -1 )
			EditReachSpec( PrunedPaths[i], none, none, 1, 1);

		//Clear incoming
	}
}

defaultproperties
{
	FriendlyName="Base Waypoint"
	bStatic=False
	bNoDelete=False
	MaxDistance=200
	Texture=Texture'FerBotz.BWP_Normal'
	bCollideWhenPlacing=false
	Style=3
	SpriteProjForward=4
	DrawScale=0.5
}