//=============================================================================
// BotzFunctionManager.
// En vez de incluir todas las funciones en cada entidad que las use, prefiero
// hacer que todas estén en una base general accesable por cualquier 'actor'
// relacionado o no a los botz, esto es útil para reducir lineas innecesarias
// y hacer los scripts más cortos.
// No vendria nada mal pasar unas cuantas cositas a codigo nativo
//
// For testing purposes, do not modify or redistribute
// Have suggestions? Contact me.
// caco_fff@hotmail.com - Higor at UT99.org
//=============================================================================
class BotzFunctionManager expands InfoPoint;

var() class<BotzProjectileStore> defaultBPS;

function Initialize( Botz aBotZ)
{
}

function UnInit()
{
}

//Get a botz's highest rated weapon based on Botz's ThisWeaponOnBest
function Weapon BotzBestWeapon( Botz B)
{
	local float rating;
	local inventory Inv;
	local weapon Best;

	For ( inv=B.Inventory ; inv!=none ; inv=inv.Inventory )
	{
		if ( Weapon(inv) != none )
			B.ThisWeaponOnBest( rating, Best, Weapon(inv) );
	}
	return Best;
}

//More expensive function
function Weapon BotzRateWeapons( Botz B, float MinRating, out int RateCount, out int HealthSum)
{
	local float orat, erat;
	local inventory Inv;
	local Weapon Best;

	For ( inv=B.Inventory ; inv!=none ; inv=inv.Inventory )
	{
		if ( Weapon(inv) == none )
		{
			if ( inv.bIsAnArmor)
				HealthSum += inv.Charge;
			continue;
		}

		erat = B.ThisWeaponOnBest( orat, Best, Weapon(inv) );
		if ( erat > MinRating)
			RateCount++;
	}
	return Best;
}

//Soporte multiplayer sin FerBotz.u replicado
//Nos identifica al PlayerPawn simulado
function PlayerPawn FindMyDummy( botz aBotZ)
{
	local PlayerPawn P;
	ForEach AllActors (class'PlayerPawn', P)
	{
		if ( P.PlayerReplicationInfo == aBotZ.PlayerReplicationInfo )
			return P;
	}
	return none;
}


//Encontrar jugador viendo a este pawn
function PlayerPawn FindViewer(pawn P)
{
	local PlayerPawn PP;

	ForEach AllActors (class'PlayerPawn', PP)
	{
		if ( (ViewPort(PP.Player) == none) && (PP.ViewTarget == P) )
			return PP;
	}
	return none;
}


/*
//Analizar plano de apoyo:
// Obtener:  - 2 Vectores normalizado de apoyo ( X, Y, O - centro de apoyo- )
//           - Posible caida de barranco
//           - Posible obstaculo a saltar
static function LinealPlaneDetect( actor DetectFor, vector StartLocation, vector EndLocation, optional Float MinDist, optional Float MaxDifference)
{//Registrar 12 puntos entre los vectores iniciales y comparar plano(s) de apoyo
	local vector HitNormals[12], HitLocations[12];
	local float TotalDist, Factor1, Factor2;
	local vector Current, vX, vY;
	local float fX, fY;
	local int i, j; // Iteradores
	
	TotalDist = VSize( StartLocation - EndLocation);
	if ( (TotalDist < MinDist) || (DetectFor == none) )
		return;

}
*/

//ATENCION!!, formato dual: consumir (+CPU -MEMORIA) TheEnt cualquiera, (-CPU +MEMORIA) TheEnt es BotzMutator
function bool FoundBLP( class<BaseLevelPoint> Sample, actor TheEnt)
{
	local int i;
	local BaseLevelPoint BLP;

	if ( (TheEnt != none) && TheEnt.IsA('BotzMutator') )
		For ( i=0 ; i<96 ; i++ )
			if ( (BotzMutator(TheEnt).GetBLP(i) != none) && classIsChildOf(BotzMutator(TheEnt).GetBLP(i).class, Sample) )
				return True;
	else
		ForEach AllActors(class'BaseLevelPoint', BLP)
			if ( classIsChildOf( BLP.class, Sample) )
				return True;

	return False;
}

//ATENCION!!, formato dual: consumir (+CPU -MEMORIA) SourceEnt cualquiera, (-CPU +MEMORIA) SourceEnt es BotzMutator
//TEAM -1 = no equipo
//bRejectByTeam: rechazar ese BLP en vez de tomarlo
function BaseLevelPoint PickRandomBLP( class<BaseLevelPoint> Sample, actor SourceEnt, int Team, optional bool bRejectByTeam, optional vector TraceFrom, optional bool bLogResults)
{
	local int i, j;
	local BaseLevelPoint BLP, TheList[96];

	if ( bLogResults )
		Log("================== BFM.PickRandomBLP =================");
	i = 0;
	if ( (SourceEnt != none) && SourceEnt.IsA('BotzMutator') )
	{
		if ( bLogResults )
			Log("Source es Mutador");
		For ( j=0 ; j<96 ; j++ )
		{
			if (BotzMutator(SourceEnt).GetBLP(j) == none )
				break;
			if ( !classIsChildOf(BotzMutator(SourceEnt).GetBLP(j).class, Sample) )
			{
				if ( bLogResults )
					Log( SourceEnt.GetItemName(string(BotzMutator(SourceEnt).GetBLP(j)))@"descartado, razón: incompatible");
				continue;
			}
			if ( (TraceFrom != vect(0,0,0)) && !SourceEnt.FastTrace( TraceFrom, BotzMutator(SourceEnt).GetBLP(j).Location ) )
			{
				if ( bLogResults )
					Log( SourceEnt.GetItemName(string(BotzMutator(SourceEnt).GetBLP(j)))@"descartado, razón: no visible");
				continue;
			}
			if ( Team == -1)
			{	TheList[i] = BotzMutator(SourceEnt).GetBLP(j);
				i++;
			}
			else if ( bRejectByTeam )
			{	if ( BotzMutator(SourceEnt).GetBLP(j).Team != Team) 
				{	TheList[i] = BotzMutator(SourceEnt).GetBLP(j);
					i++;	}
				else if ( bLogResults )
					Log( SourceEnt.GetItemName(string(BotzMutator(SourceEnt).GetBLP(j)))@"descartado, razón: pertenece a equipo no deseado");
			}
			else
			{	if (  BotzMutator(SourceEnt).GetBLP(j).Team == Team) 
				{	TheList[i] = BotzMutator(SourceEnt).GetBLP(j);
					i++;	}
				else if ( bLogResults )
					Log( SourceEnt.GetItemName(string(BotzMutator(SourceEnt).GetBLP(j)))@"descartado, razón: no pertenece a equipo deseado");
			}
		}		
	}
	else
	{
		if ( bLogResults )
			Log("Source es"@SourceEnt.GetItemName( string(SourceEnt) ) );
		ForEach AllActors(class'BaseLevelPoint', BLP)
			if ( classIsChildOf( BLP.class, Sample) )
			{
				if ( (TraceFrom != vect(0,0,0)) && !BLP.FastTrace( TraceFrom) )
					continue;
				if ( Team == -1)
				{	TheList[i] = BLP;
					i++;
				}
				else if ( bRejectByTeam )
					if ( int(BLP.Team) != Team )
					{	TheList[i] = BLP;
						i++;
					}
				else
					if ( int(BLP.Team) == Team )
					{	TheList[i] = BLP;
						i++;
					}
			}
	}
	if ( bLogResults )
		For ( j=0 ; j<i ; j++ )
			Log("BFM:"@Sample@"Nº"@(j + 1)@"es"@TheList[j]);
	return TheList[rand(i)];
}

static function bool SetVisibleAndValid( pawn Other)
{
	if ( Other.Mesh == none )
		return false;
	Other.bHidden = False;
	Other.DrawType = DT_Mesh;
	Other.Style = STY_Normal;
	Other.SetCollision( True, True, True);
	Other.bProjTarget = True;
	Other.bCollideWorld = True;
	return True;
}

static function bool SetInvisiblaAndInvalid( pawn Other)
{
	if ( !Other.bIsPlayer )
		return false;
	Other.bHidden = True;
	Other.SetCollision( False, False, False);
	Other.bProjTarget = False;
	Other.bCollideWorld = True;
	return True;
}

//This will return a point we can actually reach, I should globalize this later
function vector SlantStep( vector Origin, float ColRadius, float AddHeight)
{
	local vector Hitloc, HitNorm;
	if ( Trace( Hitloc, HitNorm, Origin - vect(0,0,1) * AddHeight, Origin) == none)
		return Origin;
	HitLoc.Z += AddHeight + ColRadius * (1 - HitNorm.Z);
	return HitLoc;
}
static function vector _SlantStep( Actor A, vector Origin, float ColRadius, float AddHeight)
{
	local vector Hitloc, HitNorm;
	if ( A.Trace( Hitloc, HitNorm, Origin - vect(0,0,1) * AddHeight, Origin) == none)
		return Origin;
	HitLoc.Z += AddHeight + ColRadius * (1 - HitNorm.Z);
	return HitLoc;
}

static function BOOL CanFallTo(pawn Botz, actor JumpDest)
{	local float FallTime;
	local float FallDist;
	local float RunTime;
	local float HDist;
	local float HVel;
	local vector testa, Hitloc, HitNorm;


	if ( (Botz == none) || (JumpDest == none))
		return false;

//Utilizar version alternativa, las caidas no son para tocar un objeto por su punto 0,
//sino que para aterrizar justo en su zona media
	testa = _SlantStep( JumpDest, JumpDest.Location, Botz.CollisionRadius, Botz.CollisionHeight * 2); //Ugly

	FallDist = Botz.Location.Z - testa.Z;
	if ( FallDist * Botz.Region.Zone.ZoneGravity.Z > 0 ) //Negative means can fall
		return false;
	FallTime = sqrt( (abs(FallDist) * 2) / (abs(Botz.Region.Zone.ZoneGravity.Z) ) );

	HDist = HSize(testa - Botz.Location);
	RunTime = HDist / Botz.GroundSpeed;

	return RunTime <= FallTime;
}

static function BOOL CanFlyTo( vector Origin, vector Dest, float Gravity, float JumpZ, float MaxSpeed, optional actor TraceActor)
{
	local float HighestPoint;	//Si altura supera punto mas alto, salto no posible
	local float AscensionSec;
	local float DeltaTa, DeltaTb, DeltaT;
	local float DeltaY, disc;
	local float HDist, HVel;

	if ( Gravity >= -0.1 )	return False;

	DeltaY = Dest.Z - Origin.Z;
	disc = JumpZ*JumpZ - 4 * (-DeltaY) * (Gravity * 0.5); //b^2 - 4*c*a
	if ( disc < 0 ) //No hay solucion, no llego tan alto
		return false;

	disc = sqrt(disc);

	//Para salto con caida
	DeltaT = (-JumpZ - disc) / Gravity; //b - disc    /  2*a

	HDist = HSize(Origin - Dest);
	HVel = HDist / DeltaT;
	if ( HVel > MaxSpeed * 1.02)
		return false; //I require a higher horizontal speed

	if ( TraceActor != none ) //Implementar aca el salto con checkeo de obstaculos?
		return TraceActor.FastTrace( Origin);

	return True;
}

static function vector AdvancedJump( vector Origin, vector Dest, float Gravity, float JumpZ, float MaxSpeed, optional bool bSuperAccel)
{
	local float DeltaTa, DeltaTb, DeltaT;
	local float DeltaY, disc;
	local float HDist, HVel;
	local vector Result;

	DeltaY = Dest.Z - Origin.Z;

	disc = JumpZ*JumpZ - 4 * (-DeltaY) * (Gravity * 0.5); //b^2 - 4*c*a
	if ( disc < 0 ) //Evitar un caso 0
	{
		HVel = MaxSpeed;
		Goto END;
	}
	disc = sqrt(disc);

	if ( bSuperAccel ) //Para salto de lleno
	{
		DeltaT = (-JumpZ + disc) / Gravity; //b - disc    /  2*a
		HDist = HSize(Origin - Dest);
		HVel = HDist / DeltaT;
		if ( HVel <= MaxSpeed * 1.05 )
			Goto END;
	}

	//Para salto con caida
	DeltaT = (-JumpZ - disc) / Gravity; //b - disc    /  2*a

// ******************************
	HDist = HSize(Origin - Dest);
	HVel = fMin( HDist/DeltaT, MaxSpeed);

END:
	Result = Normal((Dest - Origin) * vect(1,1,0) ) * HVel;
	Result.Z = JumpZ;

	return Result;
}


//No deberia llamar esta funcion sin antes comprobar que puedo llegar con CanFlyTo()
static function BOOL JumpCollision( pawn Other, vector Start, vector End, float Gravity,float zVel, optional float zExtent, optional int Steps)
{
	local float totalTime, alpha;
	local vector aVel, thisLoc, nextLoc;
	local int i;

	if ( Other == none)
		return false;
	if ( Steps == 0)
		Steps = 4;
	
	//Note: steps means Points to check before end point
	//if steps == 1, only one simple trace will be done
	aVel = AdvancedJump( Start, End, Gravity, zVel, 999999.0, false); //Delta is 0 here
	totalTime = HSize(Start-End) / HSize(aVel); //alpha es la seccion del tiempo que tarda en llegar

//	Log("Speed is X="$aVel.X$"; Y="$aVel.Y$"; Z="$aVel.Z);

	thisLoc = Start;

	For ( i=0 ; i<Steps ; i++ )
	{
		alpha = totalTime / float(Steps);
		alpha *= float(i+1);
		NextLoc.X = Start.X + aVel.X * alpha; //MRU
		NextLoc.Y = Start.Y + aVel.Y * alpha; //MRU
		NextLoc.Z = Start.Z + zVel * alpha + Gravity * 0.5 * square(alpha); //MRUA
//		Log("Point nº "$i+1$" is X="$NextLoc.X$"; Y="$NextLoc.Y$"; Z="$NextLoc.Z$"; Alpha="$alpha);

		if ( zExtent > 0)
		{
			if ( !Other.FastTrace( NextLoc + VectZ(zExtent*0.5), thisLoc - VectZ(zExtent*0.5) ) )
				return false;
		}
		else if ( !Other.FastTrace( NextLoc, thisLoc) )
			return false;
		thisLoc = NextLoc;
	}
	return true;
}

//Location of bot above destination if floating at max speed, accel taken into account?
static function vector SuperFlyLocation( Pawn Other, vector Land)
{
	local float AddedTime, BaseTime, t;
	local vector aVec;

	aVec = HNormal( Land - Other.Location);
	AddedTime = (((Other.Velocity dot aVec) - Other.AirSpeed) / (Other.AccelRate * Other.AirSpeed)) * -1;
	BaseTime = HSize( Land - Other.Location) / Other.AirSpeed;
	t = AddedTime + BaseTime;

	aVec = Land;
	aVec.Z = Other.Location.Z + (Other.Velocity.Z * t + Other.Region.Zone.ZoneGravity.Z * square(t) / 2);

	if ( Botz(Other).DebugMode && (FRand() < 0.08) )
		Log("SFL: "$AddedTime @ BaseTime @ (aVec-Land).Z );

	return aVec;
}

static function vector ThrowAt( vector Origin, vector Dest, float Gravity, float Velocity, optional bool bAltCurve)
{
	local float aTang, bTang;
	local float Yf, Disc;
	local float Xf, HVel;
	local vector Result;
	
	Yf = Dest.Z - Origin.Z;
	Xf = HSize( Origin - Dest);
	
	Gravity *= -1; //Tamos asumiendo aca, por las dudas...
	
	Disc = 1 - ((2 * Gravity * Yf) / square(Velocity));
//	Log( "Discriminant is "$Disc );
	
	Disc -= (square(Gravity) * (Xf**1.9)) / (square(Velocity) * square(Velocity));

//	Log( "Discriminant is "$Disc );
	
	if ( Disc < 0 )
			return vect(0,0,0); //No chance
	
	bTang = square(Velocity) / (Gravity * Xf);
	aTang = bTang * (1 + sqrt(Disc) ); //Aim up
	bTang *= 1 - sqrt(Disc); //Aim down

//	Log( "Tangents are: "$aTang$", "$bTang );

	if ( Gravity < 0 ) //Gravity pulls me upwards
		bAltCurve = !bAltCurve;

	if ( bAltCurve )
		Result = Normal( (Dest - Origin) * vect(1,1,0) ) + (vect(0,0,1) * aTang);
	else
		Result = Normal( (Dest - Origin) * vect(1,1,0) ) + (vect(0,0,1) * bTang);
	return Normal( Result) * Velocity;
}

static function BOOL CompareRotation(rotator A, rotator B, int Tolerance, bool NoPitch)
{
	A = ValidateRotation(A - B);
	return ( (abs(A.Yaw) < Tolerance) && ( NoPitch || (abs(A.Pitch) < Tolerance)) );
}



static function rotator ValidateRotation( rotator TestRot)
{
	While( TestRot.Pitch > 32768 )
		TestRot.Pitch -= 65536;
	While( TestRot.Pitch < -32768 )
		TestRot.Pitch += 65536;
	While( TestRot.Yaw > 32768 )
		TestRot.Yaw -= 65536;
	While( TestRot.Yaw < -32768 )
		TestRot.Yaw += 65536;
	return TestRot;
}



static function vector HNormal( vector aVector){	return Normal(aVector * vect(1,1,0) );	}



static function vector VectZ( float Height)
{	local vector NewV; NewV.Z = Height; return NewV;	}



static function INT IRango(int Min, int Max, int Valor, optional bool Invert)
{	if (Min == Max)
		return Min;
	if (Min > Max)
	{	if (Invert) return Max;
		else return Min;
	}
	if ( Valor < Min )
		Valor = Min;
	else if (Valor > Max)
		Valor = Max;
	return Valor;
}




static function FLOAT FRango(float Min, float Max, float Valor)
{	if (Min == Max)
		return Min;
	if (Min > Max)
		return Max;
	if ( Valor < Min )
		Valor = Min;
	else if (Valor > Max)
		Valor = Max;
	return Valor;
}




static function VECTOR SetPointByRotation(rotator TheRotation, vector TheOrigin, optional int Dist)
{	local vector ExtensionV;
	if (Dist == 0)
		Dist = 2000;
	ExtensionV = ( Vector(TheRotation) * Dist );
	return ( ExtensionV + TheOrigin );
}



static function FLOAT ParaNormal(float TotalPerc, float Valor, optional bool bCanExceed)
{	if ( (Valor > TotalPerc) && !bCanExceed)
		Valor = TotalPerc;
	return (Valor / TotalPerc);
}




static function BOOL InRadiusEntity(actor BaseEnt, actor TheEnt)
{	local float Float1;
	if (TheEnt == none || BaseEnt == none)
		return false;
	Float1 = HSize(TheEnt.Location - BaseEnt.Location);
	if ( (Float1 <= BaseEnt.CollisionRadius) && ( abs(BaseEnt.Location.Z - TheEnt.Location.Z) <= BaseEnt.CollisionHeight) )
		return True;
	return False;
}

static function BOOL ISpotCorrection( Botz B, InventorySpot I)
{
	if ( I == none || I.MarkedItem == none )
		return false;
	if ( VSize( I.Location - B.Location) < 200 )
	{
		if (HSize( I.Location - I.MarkedItem.Location) > (B.CollisionRadius + I.MarkedItem.CollisionRadius))
			return true;
	}
	return InRadiusEntity( B, I);
}

static function FLOAT HSize( vector TheVec )
{
	return VSize(TheVec * vect(1,1,0) );
}

static function ReplaceText(out string Text, string Replace, string With)
{
	local int i;
	local string Input;
		
	Input = Text;
	Text = "";
	i = InStr(Input, Replace);
	while(i != -1)
	{	
		Text = Text $ Left(Input, i) $ With;
		Input = Mid(Input, i + Len(Replace));	
		i = InStr(Input, Replace);
	}
	Text = Text $ Input;
}

function FindMonsters( Botz B)
{
	local ScriptedPawn P, bestP;
	local float Dist, BestDist;

	BestDist = 15000 - B.Punteria * 2000;
	ForEach B.AllActors (class'ScriptedPawn', P)
		if ( (P.Health > 0) && (P.AttitudeToPlayer != ATTITUDE_Follow) && (P.AttitudeToPlayer != ATTITUDE_Friendly) )
		{
			Dist = VSize( B.Location - P.Location);
			if ( (Dist < BestDist) && B.FastTrace(P.Location) )
			{
				BestP = P;
				BestDist = Dist;
			}
		}
	if ( BestP != none )
		B.SetEnemy( BestP);
}

//Can be slow, call every FEW times
function ScanEnemies( Botz B)
{
	local Pawn P, bestP;
	local float norm, BestNorm;
	local float aDist;
	local vector vNorm;

	BestNorm = 1.40 - B.TacticalAbility * 0.01 - B.Skill * 0.01;   // sqrt(2) with small ability to detect offscreen enemies
	aDist = 12000 + B.Skill * 1000 - B.Punteria * 1500; 
	vNorm = vector(B.Viewrotation);
	ForEach B.RadiusActors (class'Pawn', P, aDist)
		if ( B.SetEnemy(P,true) )
		{
			norm = vSize(vNorm + normal(P.Location - B.Location) );
			if ( (norm > BestNorm) && (B.FastTrace(P.Location) || B.FastTrace(P.Location + vect(0,0,15)) ) )
			{
				BestP = P;
				BestNorm = norm;
			}
		}
	B.Enemy = BestP;
}

static function string ByDelimiter( string Str, string Delimiter, optional int Skip)
{
	local int i;

	AGAIN:
	i = InStr( Str, Delimiter);
	if ( i < 0 )
	{
		if ( Skip == 0 )
			return Str;
		return "";
	}
	else
	{
		if ( Skip == 0 )
			return Left( Str, i);
		Str = Mid( Str, i + Len(Delimiter) );
		Skip--;
		Goto AGAIN;
	}
}

defaultproperties
{
}
