/*
Gamebots Pogamut derivation Copyright (c) 2010-2011, Michal Bida, Radek Pibil

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software must also be in compliance with the Epic Games Inc. license for mods which states the following: "Your mods must be distributed solely for free, period. Neither you, nor any other person or party, may sell them to anyone, commercially exploit them in any way, or charge anyone for receiving or using them without prior written consent of Epic Games Inc. You may exchange them at no charge among other end-users and distribute them to others over the Internet, on magazine cover disks, or otherwise for free." Please see http://www.epicgames.com/ut2k4_eula.html for more information.

*/

class GBClientClass extends TcpLink config(GameBotsUDK) dependson(GBGameHandler);
	;

//Maximum number of arguments
const ArgsMaxCount = 128;

// the main variables where we have incoming messages stored
var string ReceivedData;
var string ReceivedArgs[ArgsMaxCount];
var string ReceivedVals[ArgsMaxCount];

//for logging purposes
var string lastGBCommand;

// set true for iterative mode
var config bool bIterative;

// enables disables cheating - invulnerability, spawning items for bots
var config bool bAllowCheats;

// if control server or bots can or cannot pause the game
var config bool bAllowPause;

//this is helper variable so we can cast from class<Inventory> to class<Weapon> in fc. GetItemAmount
var class<Weapon> curWeapon;

//For bot exporting (ExportPlayers())
var bool bExportHumanPlayers;
var bool bExportRemoteBots;
var bool bExportUnrealBots;

var GBClientClass Next; //create list of all classes

struct PickupStruct
{
	var() class<PickupFactory> PickupClass;
	var() class<Inventory> InventoryClass;
};

var array<PickupStruct> ExportedPickup;


//Here is where we handle incoming commands
/* Commands expected to look like:
runto {Argument value} {Arg value}...
Currently hard coded to take no more than ArgsMaxCount args
Command type and arguments can be
any length, but first space terminates the name. Values can
have spaces or any other kind of character.
*/
function ReceivedLine(string S)
{
	local string cmdType, argBody, rem;
	local int endloc, wordsplit, attrNum;

	if(bDebug)
		`log(S);

	wordsplit = InStr(S," ");
	if( wordsplit == -1)
		wordsplit = Len(S);

	cmdType = left(S,wordsplit);
	rem = mid(S,InStr(S,"{"));

	attrNum = 0;
	// clear previously received attr/val pairs
	while(attrNum < ArgsMaxCount)
	{
		if (ReceivedArgs[attrNum] == "")
			break;

		ReceivedArgs[attrNum] = "";
		ReceivedVals[attrNum] = "";

		attrNum++;
	}

	attrNum = 0;

	//iterate through attr/val pairs, storring them in the
	//parallel arrays ReceivedArgs and ReceivedVals
	while(attrNum < ArgsMaxCount && rem != "")
	{
		endloc = InStr(rem,"}");
		argBody = mid(rem,1,(endloc - 1));

		wordsplit = InStr(argBody," ");
		ReceivedArgs[attrNum] = left(argBody,wordsplit);
		ReceivedVals[attrNum] = mid(argBody,(wordsplit + 1));

		rem = mid(rem,1); //advance
		rem = mid(rem,InStr(rem,"{"));
		attrNum++;
	}

	cmdType = Caps(cmdType);

	ProcessAction(cmdType);
}

//Recieve info - parse into lines and call RecievedLine
event ReceivedText( string Text )
{
	local int i;
	local string S;

    if(bDebug)
    	`log("Recieved:"$Text);

	ReceivedData = ReceivedData $ Text;
	//for logging purposes
	lastGBCommand = Text;

	// remove a LF which arrived in a new packet
	// and thus didn't get cleaned up by the code below
	if(Left(ReceivedData, 1) == Chr(10))
		ReceivedData = Mid(ReceivedData, 1);
	i = InStr(ReceivedData, Chr(13));
	while(i != -1)
	{
		S = Left(ReceivedData, i);
		i++;
		// check for any LF following the CR.
		if(Mid(ReceivedData, i, 1) == Chr(10))
			i++;

		ReceivedData = Mid(ReceivedData, i);

		ReceivedLine(S);

		if(LinkState != STATE_Connected)
			return;

		i = InStr(ReceivedData, Chr(13));
	}
}

function ProcessAction(string cmdType)
{
	//Shouldnt be called, just for inheritance
	`log("Error - we are in GBClientClass, ProcessAction() ");
}

function bool hasArg(string argName)
{
	local int i;
	for ( i=0; i < ArgsMaxCount && ReceivedArgs[i] != ""; ++i )
	{
		if (ReceivedArgs[i] ~= argName)
		{
			return true;
		}
	}
	
	return false;
}

// function for getting string value of input received attribute
function string getArgVal(string argName)
{
	local int i;
	while (i < ArgsMaxCount && ReceivedArgs[i] != "")
	{
		if (ReceivedArgs[i] ~= argName)
			return ReceivedVals[i];
		i++;
	}
	
	return "";
}

// should use int's for locations rather than floats
// we don't need to be that precise
function ParseVector(out vector v, string vecName)
{
	local int i;
	local string rem;
	local string delim;

	delim = " ";

	rem = GetArgVal(vecName);
	if(rem != "")
	{
		if( InStr(rem,delim) == -1 )
			delim = ",";
		i = InStr(rem,delim);
		v.X = float(left(rem,i));
		rem = mid(rem,i+1);
		i = InStr(rem,delim);
		v.Y = float(left(rem,i));
		v.Z = float(mid(rem,i+1));
	}
	else
	{
		v.x = float( GetArgVal("x") );
		v.y = float( GetArgVal("y") );
		v.z = float( GetArgVal("z") );
	}
}

// function for parsing rotation
function ParseRot(out rotator rot, string rotName)
{
    local int i;
    local string rem;
    //local float y,p,r;
    local string delim;

    delim = " ";

	rem = GetArgVal(rotName);
	if(rem != "")
	{
        if( InStr(rem,delim) == -1 )
        	delim = ",";
        i = InStr(rem,delim);
        rot.Pitch = float(left(rem,i));
        rem = mid(rem,i+1);
        i = InStr(rem,delim);
        rot.Yaw = float(left(rem,i));
        rot.Roll = float(mid(rem,i+1));
	}
	else
	{
		rot.Pitch = float( GetArgVal("pitch") );
		rot.Yaw = float( GetArgVal("yaw") );
		rot.Roll = float( GetArgVal("roll") );
	}
}

//Send a line to the client
function SendLine(string Text, optional bool bNoCRLF)
{
    if(bDebug)
    	`log("    Sending: "$Text);
	if(bNoCRLF)
		SendText(Text);
	else
		SendText(Text$Chr(13)$Chr(10));
}

//When we need to tell something to all
function GlobalSendLine(string Text, bool bNotifyAllControlServers, bool bNotifyAllBots, optional bool bNoCRLF)
{
	local GBClientClass G;

	if ((GBGameInterface(WorldInfo.Game).GetGameHandler().theControlServer != none) && bNotifyAllControlServers)
	{
		for (G = GBGameInterface(WorldInfo.Game).GetGameHandler().theControlServer.ChildList; G != None; G = G.Next )
		{
			G.SendLine(Text, bNoCRLF);
		}
	}

	if (bNotifyAllBots)
	{
		for (G = GBGameInterface(WorldInfo.Game).GetGameHandler().theBotServer.ChildList; G != None; G = G.Next )
		{
		    if (BotConnection(G).theBot != none)
				G.SendLine(Text, bNoCRLF);
		}
	}
}

//sends NFO message
function SendGameInfo()
{
	local string gameInfoStr, levelName, PauseResult;

	gameInfoStr = GBGameInterface(WorldInfo.Game).GetGameHandler().GetGameInfo();
	levelName = WorldInfo.GetMapName(true);

	if (WorldInfo.Pauser != None)
		PauseResult = "True";
	else
		PauseResult = "False";

	SendLine("NFO {Gametype " $ WorldInfo.Game.class $
		"} {WeaponStay " $ UTGame(WorldInfo.Game).bWeaponStay $
		"} {GamePaused " $ PauseResult $
		"} {BotsPaused " $ WorldInfo.bPlayersOnly $
		"} {Level " $ levelName $
		"}" $ gameInfoStr);   //last part may differ according to the game type
}

//Get amount of goodies we are provided when we pick various items
public function int GetPickupFactoryItemAmount(PickupFactory Pickup)
{
	local int amount;

	amount = 0;
	if (Pickup.IsA('UTHealthPickupFactory'))
	{
		amount = UTHealthPickupFactory(Pickup).HealingAmount;
	}
	else if (Pickup.IsA('UTArmorPickupFactory'))
	{
		amount = UTArmorPickupFactory(Pickup).ShieldAmount;
	}
	else if (Pickup.IsA('UTAmmoPickupFactory'))
	{
		amount = UTAmmoPickupFactory(Pickup).AmmoAmount;
	}
	else if (Pickup.IsA('UTWeaponPickupFactory'))
	{
		// Spawned weapon.
		amount = class<UTWeapon>(pickup.InventoryType).default.AmmoCount;
		// Is there a more obvious way of getting a gun's initial ammo amount?
	}
	else if (Pickup.IsA('UTWeaponLocker'))
	{
		amount = 1;
	}
	return amount;
}

public function int GetDroppedItemAmount(DroppedPickup droppedPickup)
{
	local int amount;

	amount = 1;
	if (droppedPickup.IsA('UTDroppedShieldBelt'))
	{
		amount = UTDroppedShieldBelt(droppedPickup).ShieldAmount;
	}
	else if (droppedPickup.Inventory.IsA('UTWeapon'))
	{
		// Spawned weapon.
		amount = UTWeapon(droppedPickup.Inventory).AmmoCount;
		// Is there a more obvious way of getting a gun's initial ammo amount?
	}

	return amount;
}

public function string GetWeaponClassString(coerce string weaponClass)
{
	// return (Len(weaponClass) < 8 || Right(weaponClass, 8) == "_Content") ? Mid(weaponClass, 7) : weaponClass;
	return Mid(weaponClass, 7);
}

/** Get unique ID of Unreal object
 */
public function string GetUniqueId(Actor inputActor)
{
	if (inputActor == none)
	{
		return "None";
	}

	if ( inputActor.IsA('Pawn') && !inputActor.IsA('Vehicle') && !inputActor.IsA('Projectile') )
	{
    	if ( Pawn(inputActor).Controller != none)
    	{
			return string( Pawn(inputActor).Controller );
		}
	}
	
	return string( inputActor );
}


public function string GetPickupFactoryType(PickupFactory pickupFactory)
{
	if (pickupFactory.IsA('UTAmmoPickupFactory'))
	{		
		return GetInventoryClassId(pickupFactory) $ ".AmmoPickup";
	}
	else if (pickupFactory.IsA('UTArmorPickupFactory'))
	{		
		return GetInventoryClassId(pickupFactory) $ ".ArmorPickup";
	}
	else if (pickupFactory.IsA('UTHealthPickupFactory'))
	{
		return GetInventoryClassId(pickupFactory) $ ".HealthPickup";
	}
	else if (pickupFactory.IsA('UTWeaponPickupFactory'))
	{		
		return GetInventoryClassId(pickupFactory) $ ".WeaponPickup";
	}
	else if (PickupFactory.IsA('UTWeaponLocker'))
	{
		return GetInventoryClassId(pickupFactory) $ ".LockerPickup";
	}
	else if (pickupFactory.IsA('PickupFactory'))
	{		
		return GetInventoryClassId(pickupFactory) $ ".Pickup";
	}
}

public function string GetDroppedPickupType(DroppedPickup droppedPickup)
{
	if (droppedPickup.IsA('UTDroppedShieldBelt'))
	{		
		return GetDroppedInventoryClassId(droppedPickup) $ ".ArmorPickup";
	}
	else if (droppedPickup.InventoryClass == class'Weapon')
	{		
		return GetDroppedInventoryClassId(droppedPickup) $ ".WeaponPickup";
	}
	else if (droppedPickup.IsA('DroppedPickup'))
	{		
		return GetDroppedInventoryClassId(droppedPickup) $ ".Pickup";
	}
}

//Here the unique IDs are created from objects in UT
public function string GetInventoryClassId(PickupFactory pickupFactory)
{
	if (pickupFactory == none)
		return "None";

	if (pickupFactory.isA('UTWeaponPickupFactory')) {
		// weapon
		return GetWeaponClassString(pickupFactory.InventoryType);
	} else {
		return GetInventoryClassIdFromFactoryClass(pickupFactory.class);
	}
	return "None";
}

public function string GetInventoryClassIdFromFactoryClass(class<PickupFactory> pickupFactoryClass)
{
	if (pickupFactoryClass == none)
	return "None";
	
	if (ClassIsChildOf(pickupFactoryClass, class'UTAmmoPickupFactory')) {
		// ammo
		return Mid(pickupFactoryClass, 7) $ "Ammo";
	} else if (ClassIsChildOf(pickupFactoryClass, class'UTWeaponPickupFactory')) {
		// weapon - we just cant!! WeaponClass gets loaded when pickupfactory is instantiated on map
		return "None";
	} else if (ClassIsChildOf(pickupFactoryClass, class'UTHealthPickupFactory')) {
		// health
		return Mid(pickupFactoryClass, 16);
	} else if (ClassIsChildOf(pickupFactoryClass, class'UTArmorPickupFactory')) {
		// armor
		return Mid(pickupFactoryClass, 14);
	} else if (ClassIsChildOf(pickupFactoryClass, class'UTWeaponLocker')) {
		// weapon locker
		return "Weapons";
	}  else {
		// other
		return Mid(pickupFactoryClass.default.InventoryType, 2);
	}
	return "None";
}

//Here the unique IDs are created from objects in UT
public function string GetDroppedInventoryClassId(DroppedPickup droppedPickup)
{
	if (droppedPickup == none)
		return "None";

	if (droppedPickup.isA('UTDroppedShieldBelt')) {
		// shield belt
		return Mid(droppedPickup.class, 9) $ ".ArmorPickup";
	} else if (droppedPickup.Inventory.isA('Weapon')) {
		// weapon
		return GetWeaponClassString(droppedPickup.InventoryClass) $ ".WeaponPickup";
	} else {
		// other
		return Mid(droppedPickup.InventoryClass, 2) $ ".Pickup";
	}
	return "None";
}

//Here the unique IDs are created from objects in UT
public function string GetUniquePickupFactoryInventoryId(PickupFactory pickupFactory)
{
	if (pickupFactory == none)
		return "None";

	if (pickupFactory.isA('UTAmmoPickupFactory')) {
		// ammo
		return Mid(pickupFactory.class, 7) $ "_" $ GetRightMost(pickupFactory);
	} else if (pickupFactory.isA('UTWeaponPickupFactory')) {
		// weapon
		return GetWeaponClassString(pickupFactory.InventoryType) $ "_" $ GetRightMost(pickupFactory);
	} else if (pickupFactory.isA('UTHealthPickupFactory')) {
		// health
		return Mid(pickupFactory.class, 16) $ "_" $ GetRightMost(pickupFactory);
	} else if (pickupFactory.isA('UTArmorPickupFactory')) {
		// armor
		return Mid(pickupFactory.class, 14) $ "_" $ GetRightMost(pickupFactory);
	} else if (pickupFactory.isA('UTWeaponLocker')) {
		// weapon locker
		return "Weapons_" $ GetRightMost(pickupFactory);
	}  else {
		// other
		return Mid(pickupFactory.InventoryType, 2) $ "_" $ GetRightMost(pickupFactory);
	}
	return "None";
}

//Here the unique IDs are created from objects in UT
public function string GetUniqueDroppedInventoryId(DroppedPickup droppedPickup)
{
	if (droppedPickup == none)
		return "None";

	if (droppedPickup.isA('UTDroppedShieldBelt')) {
		// shield belt
		return Mid(droppedPickup.class, 9) $ "_" $ GetRightMost(droppedPickup.Inventory);
	} else if (ClassIsChildOf(droppedPickup.InventoryClass, class'UTWeapon')) {
		// weapon
		return Mid(droppedPickup.InventoryClass, 9) $ "_" $ GetRightMost(droppedPickup.Inventory);
	} else {
		// other
		return Mid(droppedPickup.InventoryClass, 2) $ "_" $ GetRightMost(droppedPickup.Inventory);
	}
	return "None";
}

public function string GetInventoryTypeFromPickupType(string pickupType)
{
	local array<string> parts;

	parts = SplitString(pickupType, ".", true);
	return (parts.Length == 2) ? parts[0] : "";
}

public function array<string> GetPickupFactoryFromPickupType(string pickupType)
{
	local array<string> parts;

	ParseStringIntoArray(pickupType, parts, ".", true);

	if (parts[1] == "WeaponPickup") {
		parts[1] = "UTWeap_" $ parts[0];
		parts[0] = "UTWeaponPickupFactory";		

		return parts;
	}

	if (parts[1] == "AmmoPickup") {
		parts[1] = "UTAmmo_" $ Mid(parts[0], 0, Len(parts[0]) - 4);
		parts[0] = "UTAmmoPickupFactory";		
		
		return parts;
	}

	if (parts[1] == "ArmorPickup") {
		parts[1] = "UTArmorPickup_" $ parts[0];
		parts[0] = "UTArmorPickupFactory";		
		return parts;
	}

	if (parts[1] == "HealthPickup") {
		parts[1] = "UTPickupFactory_" $ parts[0];
		parts[0] = "UTHealthPickupFactory";		

		return parts;
	}

	if (parts[1] == "LockerPickup") {
		parts.Remove(0, parts.Length);
		return parts;
	}
}

//Get Info about firing mode
function string GetFireModeInfo(class<UTWeapon> weapon, byte fireMode)
{
	local string outstring;

	local bool SplashDamage, SplashJump, RecSplashDamage, Tossed, LeadTarget, InstantHit;
	local bool FireOnRelease, WaitForRelease, ModeExclusive;
	local float FireRate, BotRefireRate;
	local int AmmoPerFire, AmmoClipSize;
	local float AimError, Spread;
	local int SpreadStyle;
	local int FireCount;
	local float DamageAtten;
	local int DamageMin, DamageMax;
	local class<Projectile> proj;

	local string i;

	i = (fireMode == 0) ? "Pri" : "Sec";

    outstring = " {"$i$"FireModeType " $ weapon $ "_FireMode_" $ fireMode $ "}";

	if (weapon != None)
	{
		proj = ((fireMode < weapon.default.WeaponProjectiles.length) ? weapon.default.WeaponProjectiles[fireMode] : None);

		//bools
		SplashDamage = (proj != None) ? proj.Default.DamageRadius > 0 : false;
		SplashJump = weapon.Default.bSplashJump;
		RecSplashDamage = weapon.Default.bRecommendSplashDamage;				
		Tossed = (proj != None) ? (proj.default.Physics == PHYS_Falling) : false;
		LeadTarget = weapon.Default.bLeadTarget;
		InstantHit = weapon.Default.bInstantHit;

		FireOnRelease = (weapon.default.ShouldFireOnRelease.Length>0 &&
			weapon.default.ShouldFireOnRelease[fireMode]!=0);// if true, shot will be fired when button is released, HoldTime will be the time the button was held for
		WaitForRelease = false;// if true, fire button must be released between each shot
		ModeExclusive = true;// if true, no other fire modes can be active at the same time as this one

		FireRate = weapon.default.FireInterval[fireMode]; //float
		BotRefireRate = weapon.default.FireInterval[fireMode]; //float
		AmmoPerFire = 1; //int
		AmmoClipSize = 1000; //int

		AimError = weapon.Default.AimError; //float 0=none 1000=quite a bit
		Spread = weapon.Default.Spread[fireMode]; //float rotator units. no relation to AimError
		SpreadStyle = 0; //ESpreadStyle

		FireCount = 1; //int
		DamageAtten = 1.0f; //float, attenuate instant-hit/projectile damage by this multiplier

		//Export info to outstring message
		outstring = outstring $ " {"$i$"SplashDamage " $ SplashDamage $
			"} {"$i$"SplashJump " $ SplashJump $
			"} {"$i$"RecomSplashDamage " $ RecSplashDamage $
			"} {"$i$"Tossed " $ Tossed $
       		"} {"$i$"LeadTarget " $ LeadTarget $
			"} {"$i$"InstantHit " $ InstantHit $
			"} {"$i$"FireOnRelease " $ FireOnRelease $
			"} {"$i$"WaitForRelease " $ WaitForRelease $
			"} {"$i$"ModeExclusive " $ ModeExclusive $
			"} {"$i$"FireRate " $ FireRate $
			"} {"$i$"BotRefireRate " $ BotRefireRate  $
			"} {"$i$"AmmoPerFire " $ AmmoPerFire $
			"} {"$i$"AmmoClipSize " $ AmmoClipSize $
			"} {"$i$"AimError " $ AimError $
			"} {"$i$"Spread " $ Spread $
			"} {"$i$"SpreadStyle " $ SpreadStyle $
			"} {"$i$"FireCount " $ FireCount $
			"} {"$i$"DamageAtten " $ DamageAtten $
			"}";


		if (weapon.default.WeaponFireTypes[fireMode] == EWFT_InstantHit)
		{
			DamageMin = weapon.default.InstantHitDamage[fireMode];
			DamageMax = DamageMin;

			//Export info to outstring message
			outstring = outstring $ " {"$i$"DamageMin " $ DamageMin $
			"} {"$i$"DamageMax " $ DamageMax $ "}";
		}
	}
	return outstring;
}

function string GetProjectileInfo(string i, class<UTProjectile> projClass)
{
	local string outstring;

	local float Damage, Speed, MaxSpeed, ProjLifeSpan, DamageRadius, TossZ, MaxEffectDistance;

	outstring = " {"$i$"ProjType " $ string(projClass) $ "}";

	if (projClass != none)
	{
		Damage = projClass.Default.Damage; //float
		Speed = projClass.default.Speed; //float
		MaxSpeed = projClass.default.MaxSpeed; //float
		ProjLifeSpan = projClass.default.LifeSpan; //float
		DamageRadius = projClass.default.DamageRadius; //float
		TossZ = projClass.default.TossZ; //float
		MaxEffectDistance = projClass.default.MaxEffectDistance;

		//Export info to outstring message
    	outstring = outstring $ " {"$i$"Damage " $ Damage $
			"} {"$i$"Speed " $ Speed $
			"} {"$i$"MaxSpeed " $ MaxSpeed $
			"} {"$i$"LifeSpan " $ ProjLifeSpan $
			"} {"$i$"DamageRadius " $ DamageRadius $
			"} {"$i$"TossZ " $ TossZ $
			"} {"$i$"MaxEffectDistance " $ MaxEffectDistance $
			"}";
	}

	return outstring;
}

function string ExportWeaponInfo(class<UTWeapon> currentWeaponClass)
{
	local string outstring;

	local bool bUseAlternateAmmo;

	if (currentWeaponClass == None)
		return "";

    //determines if the weapon uses two separate ammos
    bUseAlternateAmmo = false;

	/*
	if (currentWeaponClass.default.FireModeClass[0] != None && currentWeaponClass.default.FireModeClass[0].default.AmmoClass != none)
	{
		if (currentWeaponClass.default.FireModeClass[1] != None && currentWeaponClass.default.FireModeClass[1].default.AmmoClass != none)
		{
			if (currentWeaponClass.default.FireModeClass[0].default.AmmoClass != currentWeaponClass.default.FireModeClass[1].default.AmmoClass)
			{
                bUseAlternateAmmo = true;
			}
		}
    }
	*/

	outstring = " {Melee " $ currentWeaponClass.default.bMeleeWeapon $
		"} {Sniping " $ currentWeaponClass.default.bSniping $
		"} {UsesAltAmmo " $ bUseAlternateAmmo $ "}";

	//Get Info about primary firing mode
    outstring = outstring $ GetFireModeInfo(currentWeaponClass, 0);
	//info from Ammunition class
    outstring = outstring $ GetAmmunitionInfo(currentWeaponClass, 0);
	//info from projectile class
	outstring = outstring $ GetProjectileInfo("Pri",
		class<UTProjectile>(currentWeaponClass.default.WeaponProjectiles[0]));


	//Export info about secondary firing mode
    outstring = outstring $ GetFireModeInfo(currentWeaponClass, 1);
    //info from Ammunition class
    outstring = outstring $ GetAmmunitionInfo(currentWeaponClass, 1);
    //info from projectile class
    outstring = outstring $ GetProjectileInfo("Sec",
    	class<UTProjectile>(currentWeaponClass.default.WeaponProjectiles[0]));

	return outstring;
}

//Exports complete info about Ammunition - including Projectile info
function string ExportAmmoInfo(class<UTWeapon> weapon)
{
	local string outstring;

	if (weapon == None)
		return "";

	outstring = GetAmmunitionInfo(weapon, 0);
	//Add info about projectile if any

	outstring = outstring $ " {Amount " $ weapon.default.AmmoPickupClass.default.AmmoAmount $ "}";

	return outstring;
}

//Export info just from Ammunition class and DamageType class - prepare the classes for serialization! doesnt work like this!
function string GetAmmunitionInfo(class<UTWeapon> weapon, byte fireMode)
{
	local string outstring;

	//helper class
	local class<DamageType> damageType;

	local string AmmoClass;
	local int InitialAmount, MaxAmount;
	local float MaxRange;
	local bool ArmorStops, Special, DetonatesGoop, SuperWeapon, ExtraMomZ;

	local string i;

	i = (fireMode == 0) ? "Pri" : "Sec";

	AmmoClass = GetInventoryClassIdFromFactoryClass(weapon.default.AmmoPickupClass);
    outstring = " {"$i$"AmmoType " $ AmmoClass $ ".AmmoPickup}";

    if (weapon != none)
    {

		InitialAmount = weapon.default.AmmoCount; //int
		MaxAmount = weapon.default.MaxAmmoCount; //int
		MaxRange = weapon.default.WeaponRange; //float for autoaim

		//Export info to outstring message
	    outstring = outstring $ " {"$i$"InitialAmount " $ InitialAmount $
			"} {"$i$"MaxAmount " $ MaxAmount $
			"} {"$i$"MaxRange " $ MaxRange $
			"}";

		damageType = (weapon.default.WeaponFireTypes[fireMode] == EWFT_InstantHit)?
			weapon.default.InstantHitDamageTypes[fireMode] : weapon.default.WeaponProjectiles[fireMode].default.MyDamageType;

		//Add information about DamageType
		if (damageType != none)
		{
			//bools
			ArmorStops = damageType.default.bArmorStops;
			Special = false;
			DetonatesGoop = false;
			SuperWeapon = weapon.default.bSuperWeapon;		// if true, also damages teammates even if no friendlyfire
			ExtraMomZ = damageType.default.bExtraMomentumZ;	// Add extra Z to momentum on walking pawns

			//Export info to outstring message
    	    outstring = outstring $ " {"$i$"DamageType " $ damageType $
				"} {"$i$"ArmorStops " $ ArmorStops $
				"} {"$i$"Special " $ Special $
				"} {"$i$"DetonatesGoop " $ DetonatesGoop $
				"} {"$i$"SuperWeapon " $ SuperWeapon $
				"} {"$i$"ExtraMomZ " $ ExtraMomZ $
				"}";
		}
	}

	return outstring;
}

//Returns game status - list of players and their score
function ExportGameStatus()
{
	local Controller C;

	foreach WorldInfo.AllControllers(class'Controller', C)
	{
		if( (C.IsA('RemoteBot')) && !C.IsA('Spectator') )
		{
			SendLine("PLS {Id " $ C $
				"} {Score " $ int(C.PlayerReplicationInfo.Score) $
				"} {Deaths " $ C.PlayerReplicationInfo.Deaths $
				"}");
		}
	}
}

//Sends list of all mutators on the server (MUT batch info mess.)
function ExportMutators()
{
	local Mutator M;

	SendLine("SMUT");

	for (M = WorldInfo.Game.BaseMutator; M != None; M = M.NextMutator)
	{
		SendLine("MUT {Id " $ M $
			"} {Name " $ M.Name $
			"}");
	}
	SendLine("EMUT");

}

//Sends list of all movers on the server (MOV batch info mess.)
function ExportMovers()
{
	local InterpActor Mov;
	local GbMoverOriginalSituation moverOriginalSituation;
	local string outstring;
	local GBGameHandler gameHandler;
	local int i;

	gameHandler = GBGameInterface(WorldInfo.Game).GetGameHandler();
	
	SendLine("SMOV");
	for ( i=0; i< gameHandler.MoverArray.Length; ++i )
	{
		Mov = gameHandler.MoverArray[i];
		moverOriginalSituation = gameHandler.MoverOriginalSituationArray[i];

		outstring = "MOV {Id " $ GetUniqueId(Mov) $
			"} {Location " $ Mov.Location $
			"} {DamageTrig " $ false $
			"} {Type " $ Mov.Class $
			
			// originally this was equal to Mov.bInterpolating, but bInterpolating does not exist in UDK, it did in UT2004.
			// Someone attempted to fix this by adding bInterpolating to InterpActor of Engine package. That was a bad idea:
			// 1) Engine packages should not be modified.
			// 2) bInterpolating simply sits there, no one is interacting with it in anyway. It is therefore always false. Maybe it looked the same in UT2004, but it was most likely accessed from C++.
			// The current solution might not behave as expected, I have no idea what bInterpolating did in UT2004.
			"} {IsMoving " $ Mov.bIsMoving  $
			
			"} {Velocity " $ Mov.Velocity $
			"} {MoveTime " $ 1.0f $
			"} {OpenTime " $ Mov.StayOpenTime $
			"} {State " $ Mov.GetStateName() $
			"} {BasePos " $ moverOriginalSituation.location $
			"} {BaseRot " $ moverOriginalSituation.rotation $
			"} {DelayTime " $ 0.0f $
			"}";

		if (Mov.myMarker != none) {
			outstring = outstring $ " {NavPointMarker " $ Mov.myMarker $ "}";
		}

		SendLine(outstring);

	}
	SendLine("EMOV");
}

//Exports all navpoints in a level also with their reachable graph
function ExportNavPoints()
{
	local string message;
	local NavigationPoint N;//, First;
	local int i,PathListLength;


	SendLine("SNAV");
	//First = Level.NavigationPointList.NextNavigationPoint; //will be used in second loop, to eliminate already procesed objects

	foreach WorldInfo.AllNavigationPoints(class'NavigationPoint', N)
	{
		message = "NAV {Id " $ GetUniqueId(N) $
			"} {Location " $ N.Location $
			"} {Visible false}";

		if (N.IsA('PickupFactory'))
		{
			// The items, which have pickup base DOES NOT have unique id.
			// We need to send unique id, so sending id of pickup base.
			message = message $ " {InvSpot True}";

			if (PickupFactory(N).InventoryType != none) {
				message = message $ " {Item " $ GetUniquePickupFactoryInventoryId(PickupFactory(N)) $
					"} {ItemClass " $ GetPickupFactoryType(PickupFactory(N)) $
					"} {ItemSpawned " $ PickupFactory(N).IsInState('Pickup') $ "}";
			}
		}
		else {
			if (N.IsA('UDKTeamPlayerStart'))
			{
				message $= " {PlayerStart True" $
					"} {TeamNumber " $ PlayerStart(N).TeamIndex $ "}";
			}
			else if (N.IsA('PlayerStart'))
			{
				message $= " {PlayerStart True}";

			}
			else if (N.IsA('DoorMarker'))
			{
		 		message $= " {Door True" $
			 		"} {Mover " $ DoorMarker(N).MyDoor $ "}";
			}
			else if (N.IsA('LiftCenter'))
			{
				message $= " {LiftCenter True" $
					"} {Mover " $ LiftCenter(N).MyLift $
					"} {LiftOffset " $ LiftCenter(N).LiftOffset $ "}";	// starting vector between MyLift location and LiftCenter location
			}
			else if (N.IsA('LiftExit'))
			{
				message $= " {LiftExit True" $
					"} {Mover " $ LiftExit(N).MyLiftCenter.MyLift $
					"} {LiftJumpExit " $ LiftExit(N).MyLiftCenter.bJumpLift $
					"} {NoDoubleJump " $ false $ "}";
			}
			else if (N.IsA('JumpSpot'))
			{
        		message $= " {JumpSpot True}";
			}/*
			else if (N.IsA('JumpDest'))
			{
        		message $= " {JumpDest True}";
			}*/
			else if (N.IsA('Teleporter'))
			{
        		message $= " {Teleporter True}";
			}
			else if (N.IsA('UDKScriptedNavigationPoint'))
			{
				message $= " {AIMarker true}";
			}
			else if (N.IsA('UTCTFBase'))
			{
				message $= " {FlagOfTeam " $ UTCTFBase(N).myFlag.Team.TeamIndex $ "}";
			}			
		}
		SendLine(message);

		
		PathListLength = N.PathList.Length;
		SendLine("SNGP");
		for (i = 0; i < PathListLength; ++i)
		{
			message = "INGP {Id " $ GetUniqueId(N.PathList[i].GetEnd()) $
				"} {Flags " $ N.PathList[i].reachFlags $
				"} {CollisionR " $ N.PathList[i].CollisionRadius $
				"} {CollisionH " $ N.PathList[i].CollisionHeight $
				"}";
			/*
			if (N.PathList[i].End.IsA('JumpDest'))
			{
				message = message $" {ForceDoubleJump " $ JumpDest(N.PathList[i].End).bForceDoubleJump $
					"}" ;
				for (j = 0; j < JumpDest(N.PathList[i].End).NumUpstreamPaths; j++)
				{
					//message = message $ " {Path"$j$" "$ JumpDest(N.PathList[i].End).UpstreamPaths[j].Start $"}";

					//Searching the record which refers to the path we are now exporting
					if (JumpDest(N.PathList[i].End).UpstreamPaths[j].Start == N)
					{
						message = message $ " {CalculatedGravityZ " $ JumpDest(N.PathList[i].End).CalculatedGravityZ[j] $
							"} {NeededJump " $ JumpDest(N.PathList[i].End).NeededJump[j] $
							"}";
					}
				}

			}
			*/

			SendLine(message);
			
		}
		SendLine("ENGP");

	}
	SendLine("ENAV");

}

function ExportVehicles()
{
	local UTVehicleFactory V;
	local class<UTVehicle> Veh;
	local string outstring;
	
	SendLine("SVEH");

	foreach WorldInfo.AllNavigationPoints(class'UTVehicleFactory', V)
	{
		// no armor?
		//"} {Armor " $ V.GetArmor() $

		Veh = class<UTVehicle>(DynamicLoadObject(V.default.VehicleClassPath, class'class'));
		outstring = "VEH {Id " $ Mid(V, 17) $
			"} {Location " $ V.Location $
			"} {Rotation " $ V.Rotation $
			"} {Velocity 0,0,0" $
			"} {Visible false" $
			"} {Team " $ V.GetTeamNum() $
			"} {Health " $ Veh.default.Health $
			"} {TeamLocked " $ !V.bStartNeutral $
			"} {Type " $ Veh $ "}";
		SendLine(outstring);
	}

	SendLine("EVEH");
}

function ExportInventory()
{
	local PickupFactory pickupFactory;
	local string outstring;
	local DroppedPickup droppedPickup;
	
	
	SendLine("SINV");

	// stuff that lies on pickup factories
	foreach DynamicActors(class'PickupFactory', pickupFactory)
	{
		outstring = "INV {Id " $ GetUniquePickupFactoryInventoryId(pickupFactory) $
			"} {Location " $ pickupFactory.Location $
			"} {Amount " $ GetPickupFactoryItemAmount(pickupFactory) $
			"} {Dropped false" $
			"} {Type " $ GetPickupFactoryType(pickupFactory) $
			"} {Visible false}" $
			" {NavPointId " $ pickupFactory $ "}";

		SendLine(outstring);
	}

	// stuff that lies around
	foreach DynamicActors(class'DroppedPickup', droppedPickup)
	{
		outstring = "INV {Id " $ GetUniqueDroppedInventoryId(droppedPickup) $
			"} {Location " $ droppedPickup.Location $
			"} {Amount " $ GetDroppedItemAmount(droppedPickup) $
			"} {Dropped true" $
			"} {Type " $ GetDroppedPickupType(droppedPickup) $
			"} {Visible false}" $
			" {NavPointId " $ droppedPickup.PickupCache $ "}";

		SendLine(outstring);
	}

	SendLine("EINV");

}

//Process Pickup and builds ITC message
function string GetPickupInfoToITC(PickupFactory P)
{
	local string outstring;

	outstring = "ITC {InventoryType ";

	//We determine what it is and call appropriate functions
	if (UTAmmoPickupFactory(P) != none)
    {
		outstring = outstring $ GetInventoryClassId(P) $ "} {PickupType " $ GetPickupFactoryType(P) $ "}";

		if (UTAmmoPickupFactory(P).InventoryType != None) {
    		outstring = outstring $ " {ItemCategory Ammo}";
    		outstring = outstring $ ExportAmmoInfo(UTAmmoPickupFactory(P).TargetWeapon);
		}
	}
	else if (UTWeaponPickupFactory(P) != none)
	{	
		outstring = outstring $ GetInventoryClassId(P) $ "} {PickupType " $ GetPickupFactoryType(P) $ "}";

		if (UTWeaponPickupFactory(P).WeaponPickupClass != None) {
			outstring = outstring $ " {ItemCategory Weapon}";
			outstring = outstring $ ExportWeaponInfo(UTWeaponPickupFactory(P).WeaponPickupClass);
		}
	}
	else if (UTHealthPickupFactory(P) != none)
	{
		outstring = outstring $ GetInventoryClassId(P)  $ "} {PickupType " $ GetPickupFactoryType(P) $ "}";

		if (UTHealthPickupFactory(P).InventoryType != None) {
			outstring = outstring $ " {ItemCategory Health}";
			outstring = outstring $ " {Amount " $ UTHealthPickupFactory(P).HealingAmount $
				"} {SuperHeal " $ UTHealthPickupFactory(P).bSuperHeal $
				"}";
		}
	}
	else if (UTArmorPickupFactory(P) != none)
	{
		outstring = outstring $ GetInventoryClassId(P) $ "} {PickupType " $ GetPickupFactoryType(P) $ "}";

		if (UTArmorPickupFactory(P).InventoryType != None) {
			outstring = outstring $ " {ItemCategory Armor}";
			outstring = outstring $ " {Amount " $
				UTArmorPickupFactory(P).ShieldAmount $
				"}";
		}
	}
	else
	{
		outstring = outstring $ GetInventoryClassId(P) $ "} {PickupType " $ GetPickupFactoryType(P) $ "} {ItemCategory Other}";
	}


	return outstring;
}

//Exports all items with their attributes in ITC message
function ExportItemClasses()
{
	local PickupFactory P;

	local class<UTWeap_LinkGun> LinkGunClass;

	SendLine("SITC");

    //Export LinkGun - we get this gun automatically at the beginning	
    LinkGunClass = class'UTWeap_LinkGun';
	SendLine("ITC {InventoryType LinkGun} {PickupType LinkGun.WeaponPickup} {ItemCategory Weapon} " $ ExportWeaponInfo(LinkGunClass));
	ExportedPickup.Insert(ExportedPickup.Length,1);
	ExportedPickup[ExportedPickup.Length - 1].PickupClass = None;
	ExportedPickup[ExportedPickup.Length - 1].InventoryClass = LinkGunClass;

	foreach DynamicActors(class'PickupFactory',P)
	{
		ExportPickup(P);
	}
	SendLine("EITC");
}

function ExportPickup(PickupFactory pickupFactory)
{
	local bool bAlreadyExported;
	local int i;

	if (pickupFactory == none)
		return;

	//First check if we haven't exported this already
	bAlreadyExported = false;
	for (i = 0; i < ExportedPickup.Length; ++i)
	{
		if (ExportedPickup[i].InventoryClass == pickupFactory.InventoryType &&
			(pickupFactory.isA('UTWeaponPickupFactory') || ExportedPickup[i].PickupClass == pickupFactory.class))
		{
			bAlreadyExported = true;
			break;
		}
	}

	if (!bAlreadyExported)
	{
		SendLine(GetPickupInfoToITC(pickupFactory));

		//Add exported pickup class to list holding exported classes
		ExportedPickup.Insert(ExportedPickup.Length,1);
		ExportedPickup[ExportedPickup.Length - 1].PickupClass = pickupFactory.class;
		ExportedPickup[ExportedPickup.Length - 1].InventoryClass = pickupFactory.InventoryType;
	}
}

//Exports all players in the level
function ExportPlayers(optional bool bLimitedInfo)
{
	local Controller C;
	local string message, TeamIndex, Weapon;

	foreach WorldInfo.AllControllers(class'Controller', C)
	{
		if ((bExportRemoteBots && C.IsA('RemoteBot')) || (bExportHumanPlayers && C.IsA('PlayerController')))
		{

			if (C.PlayerReplicationInfo.Team != none)
				TeamIndex = string(C.PlayerReplicationInfo.Team.TeamIndex);
			else
    			TeamIndex = "255";

			if ((C.Pawn != none) && (C.Pawn.Weapon != none))
				Weapon = string(C.Pawn.Weapon);
			else
				Weapon = "None";

			message = "PLR {Id " $ string(C) $
				"} {Name " $ C.PlayerReplicationInfo.PlayerName $
				"} {Team " $ TeamIndex $
				"}";

			if (!bLimitedInfo)
			{
				if (C.IsA('RemoteBot')) {
					if (RemoteBot(C).jmx != "") {
		            	message = message $ " {Jmx " $ RemoteBot(C).jmx $ "}";
		            }
	            }

				if (C.Pawn != none)
					message = message$" {Location " $ C.Pawn.Location $
						"} {Rotation " $ C.Pawn.Rotation $
						"} {Velocity " $ C.Pawn.Velocity $
						"} {Weapon " $ Weapon $
						"}";
				else if ((C.IsA('PlayerController')) && (PlayerController(C).ViewTarget != none))
					message = message$" {Location " $ PlayerController(C).ViewTarget.Location $
						"} {Rotation " $ PlayerController(C).ViewTarget.Rotation $
						"} {Velocity " $ PlayerController(C).ViewTarget.Velocity $
						"}";
				else
					message = message$" {Location " $ C.Location $
						"} {Rotation " $ C.Rotation $
						"} {Velocity " $ C.Velocity $
						"}";

			}
			SendLine(message);
		}

    }
}

function GetPlayers()
{
	local Controller C;
	local string message;
	local string Dead;
	
	
	foreach WorldInfo.AllControllers(class'Controller', C)
	{
		if (C.IsA('RemoteBot') || C.IsA('Player'))
		{
			/*if (C.IsA('RemoteBot')
			{
				Type = "RemoteBot";
			}*/

			if (C.Pawn == None)
				Dead = "True";
			else
				Dead = "False";

			message = "PLR {Id " $ C $
				"} {Name " $ C.PlayerReplicationInfo.PlayerName $
				"} {PlayerType " $ C $
				"} {PlayerDead " $ Dead $"}";

			if (C.Pawn != none)
			{
				message = message $ " {Location " $ C.Pawn.Location $
					"} {Rotation " $ C.Pawn.Rotation $ "}";
			}

			SendLine(message);
		}

    }
}

static function SendMessageToAllBots(string message){
	local Controller C;
	local WorldInfo wi;
	
	wi = class'WorldInfo'.static.GetWorldInfo();

	foreach wi.AllControllers(class'Controller', C)
	{
		if( C.IsA('RemoteBot') )
		{
				RemoteBot(C).myConnection.SendLine(message);
		}
	}
}

static function SendMessageToAllControlConnections(string message){
	local GBClientClass G;
	local WorldInfo wi;
	
	wi = class'WorldInfo'.static.GetWorldInfo();

    if (GBGameInterface(wi.Game).GetGameHandler().theControlServer != None)
	{
		for (G = GBGameInterface(wi.Game).GetGameHandler().theControlServer.ChildList; G != None; G = G.Next )
		{
				G.SendLine(message);
		}
	}
}

/**
 *  Sends a message to all bots and all control connections
 */
static function SendMessageToAll(string message){
    SendMessageToAllBots(message);
    SendMessageToAllControlConnections(message);
}



defaultproperties
{
	bExportHumanPlayers=true
	bExportRemoteBots=true
	bExportUnrealBots=true
}
