/*
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 GBGameHandler extends Actor
	config(GameBotsUDK);

var GBGameInterface game;
var UTDeathMatch utGame;

var BotServer		theBotServer;
var ControlServer		theControlServer;
var bool		bServerLoaded, bBoolResult;
var int			NumRemoteBots;

var config string RemoteBotController;

var config string BotServerClass;

var config string ControlServerClass;

var PlayerReplicationInfo LevelPauserFeed;

var array<Actor> IndexedActors;

var config int BotServerPort;
var config int ControlServerPort;

//here we store if our connections to bot and cotrol server are protected by pass
//will force a bit changed initial protocol (to check password)
var bool bPasswordProtected;

//here we store Password for bot and control connections
var string Password;

//who initiated the password protection
var string PasswordByIP;

// This class is used for pausing the game. We supply it to WorldInfo.Pauser variable.
var PauserFeed WorldInfoPauserFeed;
//Here we store all available maps
var array<string> Maps;

var float tempSkill;

//will be filled up with all movers in the level
var array<InterpActor> MoverArray;
//will be filled up with all inv spots in the level
var array<PickupFactory> InvSpotArray;
//will be filled up with all doors in the level
var array<DoorMarker> DoorArray;
//will be filled up with all lift centers in the level
var array<LiftCenter> LiftCenterArray;

//this function is called even before PreBeginPlay, we parse GB parameters from
//command line here
function InitGame(string Options, out string Error )
{
	local string InOpt;

	if (GBGameInterface(Owner) == none || !isCorrectOwnerType())
	{
		`log("InitGame GBGH - failed! " $ (GBGameInterface(Owner) == none) $ " || " $ !Owner.IsA('UTDeathMatch'));
		Destroy();
		return;
	}

	game = GBGameInterface(Owner);
	utGame = UTDeathMatch(Owner);

	game.SuperInitGame(Options, Error);

	BotServerPort = Clamp(utGame.GetIntOption( Options, "BotServerPort", BotServerPort ),2000,32000);
	ControlServerPort = Clamp(utGame.GetIntOption( Options, "ControlServerPort", ControlServerPort ),2000,32000);


	InOpt = utGame.ParseOption( Options, "Password");
	if (InOpt != "")
	{
	    bPasswordProtected = true;
	    // PasswordByIP = "127.0.0.1" $ WorldInfo.GetAddressURL();
		PasswordByIP = WorldInfo.GetAddressURL();
	    Password = InOpt;
	}
	else
	{
		bPasswordProtected = false;
	}
	//init our static objects arrays
	initStaticObjectsArrays();
}

//movers are static, we will put all movers to the dynamic array, this
//significantly decrease the time we need to process them all in checkVision() fc
//the same holds for navigation points
function initStaticObjectsArrays()
{
	local InterpActor M;
	local LiftCenter LC;
	local PickupFactory PF;
	local DoorMarker DM;

	foreach AllActors(class'InterpActor',M) {
		MoverArray.InsertItem(MoverArray.Length, M);
	}
	foreach DynamicActors(class'PickupFactory',PF){ 
		InvSpotArray.InsertItem(InvSpotArray.Length,PF);
	}
	foreach DynamicActors(class'LiftCenter',LC){ 
		LiftCenterArray.InsertItem(LiftCenterArray.Length,LC);
	}
	foreach DynamicActors(class'DoorMarker',DM){ 
		DoorArray.InsertItem(DoorArray.Length, DM);
	}
}

function bool isCorrectOwnerType()
{
	return Owner.IsA('UTDeathMatch');
}

//This function is automaticaly called after beginning of the game
function HandlerPostBeginPlay()
{
	local int GameIndex;
	local int i;

	game.SuperPostBeginPlay();

	if(!bServerLoaded)
    {
		theBotServer = Spawn(class<BotServer>(DynamicLoadObject(BotServerClass, class'Class')), self);
                theBotServer.Init();
		theBotServer.ListenPort = BotServerPort;
		//theBotServer.bClosed = false;
		//theBotServer.Listen();

		theControlServer = Spawn(class<ControlServer>(DynamicLoadObject(ControlServerClass, class'Class')), self);
                theControlServer.Init();
		theControlServer.ListenPort = ControlServerPort;
		//theControlServer.bClosed = false;
		//theControlServer.Listen();
		bServerLoaded = true;
	} else {
		theBotServer.bClosed = false;
		theBotServer.Listen();

		theControlServer.bClosed = false;
		theControlServer.Listen();
	}

	//HACK? Set to true, so the match will start imediatelly
	//We should implement support for StartMatch function...
    utgame.bQuickStart = true;

	WorldInfoPauserFeed = Spawn(class'PauserFeed');

	//RemoteBotConfig.Difficulty = AdjustedDifficulty;

	GameIndex = utGame.class.default.GameSpecificMapCycles.Find('GameClassName', utGame.class.name);
	`log("GameIndex: " $ GameIndex $ " "  $ utGame.class.Name);
	if (GameIndex > 0) {
		Maps = utgame.GameSpecificMapCycles[GameIndex].Maps;
	} else {
		Maps.Length = 0;
	}

	`log("Maps: ");
	for (i = 0; i < utGame.class.default.GameSpecificMapCycles.Length; ++i)
	{
		for (GameIndex = 0; GameIndex < utGame.class.default.GameSpecificMapCycles[i].Maps.Length; ++GameIndex)
		{
			`log(utGame.class.default.GameSpecificMapCycles[i].GameClassName $ " " $ utGame.class.default.GameSpecificMapCycles[i].Maps[GameIndex]);
		}
	}

	LevelPauserFeed = Spawn(class'PauserFeed', self);

	`log("GB server on.");
	`log(" BotServerPort:" $ theBotServer.ListenPort$ " ControlServerPort:" $ theControlServer.ListenPort);
}

//we override this to disable accepting of connections when game is ending
function EndGame(PlayerReplicationInfo Winner, string Reason )
{
	`log("In EndGame");
	if (theBotServer.LinkState == STATE_Listening) {
		theBotServer.bClosed = true;
		theBotServer.Close();
	}

	if (theControlServer != none && theControlServer.LinkState == STATE_Listening) {
		theControlServer.bClosed = true;
		theControlServer.Close();
	}
/*
	if (theObservingServer != none && theObservingServer.LinkState == STATE_Listening) {
		theObservingServer.bClosed = true;
		theObservingServer.Close();
	}
*/
	game.SuperEndGame(Winner, Reason);
}

//This function returns text information about game
function string GetGameInfo()
{
	local string outStr;

	outStr = " {FragLimit " $ utgame.GoalScore $
		"} {TimeLimit " $ utgame.TimeLimit $ "}";

	return outStr;
}

//Main function for adding bot to the game (also creates controller)
function RemoteBot AddRemoteBot
(
	BotConnection theConnection,
	string clientName,
	int TeamNum,
	optional string className,
	optional string DesiredSkin,
	optional float DesiredSkill,
	optional bool ShouldLeadTarget
)
{
	local RemoteBot NewBot;
	local CharacterInfo CI;

	`log("AddRemoteBot()");

	//I dont think location here is necessary. Its just controller class
	NewBot = Spawn(class<RemoteBot>(DynamicLoadObject(RemoteBotController, class'Class')), self);

	if ( NewBot == None )
	{
		////`log("In AddRemoteBot() - Cant spawn RemoteBot ");
		return None;
	}

	if (className != "")
		NewBot.PawnClass = class<UTPawn>(DynamicLoadObject(className, class'Class'));


	//hook up connection to socket
	NewBot.myConnection = theConnection;

	NewBot.bIsPlayer = true;

	// Set the player's ID.
	NewBot.PlayerReplicationInfo.PlayerID = utgame.CurrentID++;

	//Increase numbers properly, so no epic bot will join the game
	NumRemoteBots++;
	utgame.NumPlayers++;

	if ( clientName != "" )
	{
		utgame.changeName( newBot, clientName, false );
	}

	//Turns on strafing ability
	Newbot.StrafingAbility = 1.0;
    //From 0 to 7
    if ((DesiredSkill > 0) && (DesiredSkill <= 7))
    	Newbot.Skill = DesiredSkill;
    else
		Newbot.Skill = 7;
	//Shooting ahead of targets - disabled? - aiming is thing of a client
	NewBot.bLeadTarget = ShouldLeadTarget;

	NewBot.Initialize(Newbot.Skill, CI);

	//We will let the bots know that new bots came to server
	RemoteNotifyLogin(newBot);

	return NewBot;
}

//We send a notification to our remote bots, that new player joined the server
function RemoteNotifyLogin( Controller LoggingIn )
{
	local GBClientClass A;
	local string outstring;
	//local Controller c;

	outstring = "JOIN {Id " $ LoggingIn $
		"} {Name " $ LoggingIn.PlayerReplicationInfo.PlayerName $
		"}";

   	//Finding any GBClientClass for sending the message
	A = theBotServer.ChildList;

	if (A == none)
	{
		if (theControlServer != None)
		{
			A = theControlServer.ChildList;
			if (A != none)
			{
				A.GlobalSendLine(outstring, true, true);
			}
		}
	}
	else
	{
		A.GlobalSendLine(outstring, true, true);
	}

/*
    for (c = Level.ControllerList; c != none; c = c.nextController) {
        if (c.IsA('ObservedPlayer')) ObservedPlayer(c).PlayerJoined(Logging);
        if (c.IsA('ObservedRemoteBot')) ObservedRemoteBot(c).PlayerJoined(Logging);
    }
*/
}

//Notification about player leaving the server
function RemoteNotifyLogout(Controller Exiting)
{
	local GBClientClass A;
	local string outstring;
	//local Controller c;

	outstring = "LEFT {Id " $ Exiting $
		"} {Name " $ Exiting.PlayerReplicationInfo.PlayerName $
		"}";

    //Finding any GBClientClass for sending the message
	A = theBotServer.ChildList;

	if (A == none)
	{
		if (theControlServer != None)
		{
			A = theControlServer.ChildList;
			if (A != none)
				A.GlobalSendLine(outstring,true,true);
		}
	}
	else
	{
		A.GlobalSendLine(outstring,true,true);
	}
/*
    for (c = Level.ControllerList; c != none; c = c.nextController) {
        if (c.IsA('ObservedPlayer')) ObservedPlayer(c).PlayerLeft(Exiting);
        if (c.IsA('ObservedRemoteBot')) ObservedRemoteBot(c).PlayerLeft(Exiting);
    }
*/
}

//Called when new human player enters the game
event PlayerController Login(string Portal, string Options, const UniqueNetId UniqueId, out string ErrorMessage)
{
	local PlayerController LoggingIn;
	//local GBReplicationInfo PRI;

	LoggingIn = game.SuperLogin( Portal, Options, UniqueId, ErrorMessage );

	// Add custom GBReplicationInfo
	//PRI = Spawn(class'GBReplicationInfo', LoggingIn);
	//PRI.myPRI = LoggingIn.PlayerReplicationInfo;

	RemoteNotifyLogin(LoggingIn);

	return LoggingIn;
}

//Called when somebody (bot/player) leaves game
function Logout(controller Exiting)
{
	RemoteNotifyLogout(Exiting);
	if(!exiting.IsA('RemoteBot'))
	{
		game.SuperLogout(Exiting);
	}
	else
	{
		//RemoveBotFromList(RemoteBot(Exiting)); //test
		game.GameInfoLogout(Exiting);
		--NumRemoteBots;
		--utgame.NumPlayers; //we count RemoteBots as players too
	}
}

//Here we spawn and respawn the bot Pawn - thats the visible avatar of the bot
function SpawnPawn
(
	RemoteBot NewBot,
	optional vector startLocation,
	optional rotator startRotation
)
{
	local PlayerStart startSpot;
	local int TeamNum;
	local array<SequenceObject> Events;
	local SeqEvent_PlayerSpawned SpawnedEvent;
	local float Dist;
	local int Idx;

	`log("SpawnPawn:L:" $startLocation $ ";R:" $startRotation);
	if (NewBot == None)
	{
    	`log("In SpawnPawn(), - NewBot is None! ");
    	return;
	}

	if (NewBot.Pawn != None)
	{
		`log("In SpawnPawn(), - "  $ NewBot $ " Pawn already spawned ");
		return;
	}

	TeamNum = ((NewBot.PlayerReplicationInfo == None) || (NewBot.PlayerReplicationInfo.Team == None)) ?
		255 : NewBot.PlayerReplicationInfo.Team.TeamIndex;

	if ( startLocation != vect(0,0,0) && startRotation != rot(0,0,0)) {
		NewBot.Pawn = Spawn(NewBot.PawnClass, newBot,, startLocation, startRotation);
	} else if (StartLocation != vect(0,0,0)) {
		newBot.Pawn = Spawn(NewBot.PawnClass, newBot,, startLocation);		
	} else {
		startSpot = PlayerStart(utgame.FindPlayerStart(NewBot, TeamNum));
		//startSpot = PlayerStart(NewBot.Pawn.GetBestAnchor(NewBot.Pawn, NewBot.Pawn.Location, true, false, Dist));

		if ( startRotation == rot(0,0,0) )			
			startRotation = startSpot.Rotation;

		newBot.Pawn = Spawn(NewBot.PawnClass, newBot,, startSpot.Location, startRotation);

		// initialize and start it up
		NewBot.Pawn.SetAnchor(startSpot);
		if (newBot.Pawn == none)
			`log("Error spawning pawn, startSpot none in SpawnPawn()");

	}
	
	if (NewBot.Pawn == None) {
    	`log("In SpawnPawn() - Cant spawn the pawn of bot " $ NewBot $ ";Location:" $ startLocation $ ";Rotation:" $ startRotation);
		return;
	} else {
		NewBot.Possess(NewBot.Pawn, false);

		/*
		if ( PlayerController(NewPlayer) != None )
		{
			PlayerController(NewPlayer).TimeMargin = -0.1;
			anchorSpot.AnchoredPawn = None; // SetAnchor() will set this since IsHumanControlled() won't return true for the Pawn yet
		}*/
		NewBot.Pawn.LastStartSpot = startSpot;
		NewBot.Pawn.LastStartTime = WorldInfo.TimeSeconds;

		NewBot.Pawn.PlayTeleportEffect(true, true);

		if (startRotation == rot(0,0,0)) {
			NewBot.ClientSetRotation(NewBot.Pawn.Rotation, true);
		} else {
			NewBot.SetRotation(startRotation);
			NewBot.ClientSetRotation(startRotation, true);			
			NewBot.Pawn.SetRotation(startRotation);
			NewBot.Pawn.ClientSetRotation(startRotation);			
		}

		utgame.SetPlayerDefaults(NewBot.Pawn);

		// activate spawned events
		if (WorldInfo.GetGameSequence() != None)
		{
			WorldInfo.GetGameSequence().FindSeqObjectsByClass(class'SeqEvent_PlayerSpawned', true, Events);
			for (Idx = 0; Idx < Events.Length; Idx++)
			{
				SpawnedEvent = SeqEvent_PlayerSpawned(Events[Idx]);
				if (SpawnedEvent != None &&
					SpawnedEvent.CheckActivate(NewBot, NewBot))
				{
					SpawnedEvent.SpawnPoint = startSpot;
					SpawnedEvent.PopulateLinkedVariableValues();
				}
			}
		}
	}

	//For disabling automatic pickup (items picked through command PICK)
    newBot.Pawn.bCanPickupInventory = !newBot.bDisableAutoPickup;

    //Multiply Pawn GroundSpeed by our custom GB SpeedMultiplier
	if (NewBot.SpeedMultiplier > 0) {//TODO?
		newBot.Pawn.GroundSpeed = newBot.SpeedMultiplier * newBot.Pawn.Default.GroundSpeed;
		NewBot.Pawn.AirSpeed = newBot.SpeedMultiplier * newBot.Pawn.Default.AirSpeed;
		NewBot.Pawn.WaterSpeed = newBot.SpeedMultiplier * newBot.Pawn.Default.WaterSpeed;
		NewBot.Pawn.LadderSpeed = newBot.SpeedMultiplier * newBot.Pawn.Default.LadderSpeed;    
	}

   	//Setting some initial Pawn properties
	Newbot.Pawn.PeripheralVision = -0.3;
	Newbot.Pawn.bAvoidLedges = false;
	Newbot.Pawn.bStopAtLedges = false;
	Newbot.Pawn.bCanJump = true;

	if (NewBot.bAutoTrace && NewBot.myConnection.RayManager.RayCount() == 0)
	{
		NewBot.myConnection.RayManager.AddDefaultRays();
	}

	if (!NewBot.myConnection.bBotInitialized)
		NewBot.myConnection.bBotInitialized = true;
	//Notify spawning
	NewBot.myConnection.SendLine("SPW");

	if (!WorldInfo.bNoDefaultInventoryForPlayer)
	{
		AddDefaultInventory(NewBot.Pawn);
	}


 	// broadcast a welcome message.
	//BroadcastLocalizedMessage(GameMessageClass, 1, NewBot.PlayerReplicationInfo);
	NewBot.GotoState('Alive', 'DoStop'); //TODO: Really here?		
}


function AddDefaultInventory( pawn PlayerPawn )
{
	local int i;
	local string InventoryId;
	local class Inv;
	local int amount;
	local string pickupType;

	game.SuperAddDefaultInventory(PlayerPawn);

	if (PlayerPawn != none && PlayerPawn.Controller != none && PlayerPawn.Controller.isA('RemoteBot') ) {

		for (i = 0; i < utGame.DefaultInventory.Length; ++i) {
			
			Inv = utGame.DefaultInventory[i];			
			if (ClassIsChildOf(Inv, class'UDKWeapon')) {
				amount =  class<UDKWeapon>(Inv).default.AmmoCount;
				InventoryId = RemoteBot(PlayerPawn.Controller).myConnection.GetWeaponClassString(Inv);
				pickupType = InventoryId $ ".WeaponPickup";
			} else {
				InventoryId = string(Inv);
				amount =  1;
				pickupType = InventoryId;
			}

			RemoteBot(PlayerPawn.Controller).myConnection.SendLine("IPK {Id " $
				PlayerPawn.InvManager.FindInventoryType(class<Inventory>(Inv)) $
				"} {InventoryId " $ InventoryId $
				"} {Location " $ PlayerPawn.Location $
				"} {Amount " $ amount $
				"} {Dropped false" $
				"} {Type " $ pickupType $
				"}");
		}
	}
}

function string getID(Actor actor)
{
	local int i;
	for (i = 0; i < IndexedActors.Length; ++i)
	{
		if (actor == IndexedActors[i])
			return string(i);
	}

	IndexedActors.AddItem(actor);

	return string(IndexedActors.Length - 1);
}

/* Initialize bot
*/
function InitializeBot(UTBot NewBot, UTTeamInfo BotTeam, const out CharacterInfo BotInfo)
{
	NewBot.Initialize(tempSkill, BotInfo);
	BotTeam.AddToTeam(NewBot);
	utGame.ChangeName(NewBot, BotInfo.CharName, false);
}

function bool AddEpicBot(optional string BotName, optional int TeamNumber, optional float Skill)
{
	local UTBot bot;

	tempSkill = Skill;
	bot = utGame.AddBot(BotName, utGame.bTeamGame, TeamNumber);
	if (bot.PlayerReplicationInfo.PlayerName != BotName)
		utGame.ChangeName(bot, BotName, false);

	return (bot == none);
}

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

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

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

DefaultProperties
{
}
