//============================================================
// TTeamFixMut.uc	- Class for managing mutator-specific events
//============================================================
//	TitanTeamFix
//		- A modular team balancing tool initially coded for the Titan servers:
//			http://ut2004.titaninternet.co.uk/
//
//	Copyright (C) 2007-2008 John "Shambler" Barrett (JBarrett847@Gmail.com or Shambler@OldUnreal.com)
//
//	This program is free software; you can redistribute and/or modify
//	it under the terms of the Open Unreal Mod License version 1.1.
//
//============================================================
Class TTeamFixMut extends Mutator;

const TTFVersion="v1.0beta22";
var deprecated bool bUpdateVersionNumber;


var TTeamFix Master;

// ===== Variables controlling used code
var bool bExitingEvent;
var bool bJoiningEvent;
var bool bJoinedEvent;
var bool bDelayedJoinedEvent;

var float LastKeyGrab;

var array<PlayerController> DelayedJoinNotify;

var class<TTeamFix> BalancerClass;


// Variables used as compiler messages
var deprecated bool bTODO;
var deprecated bool bRemoveThisDebugCode;
var deprecated bool bRemoveWhenFinal;

// temp
var bool bBotDebug;
var Controller RecentBotExit;
var bool bNumBotsWasValid;


function PostBeginPlay()
{
	local Object TempObj;
	local UTUIDataProvider_Mutator DP;
	local GameEngine GE;
	local int i;

	Super.PostBeginPlay();


	// Check that the data provider for this mutator exists (necessary for viewing the mutator by webadmin)
	TempObj = new(Class.Outer, "TitanTeamFix") Class'Package'; // I need to do this so that the data provider is written to the correct package
	DP = new(TempObj, string(Class)) Class'UTUIDataProvider_Mutator';

	if (DP.ClassName != PathName(Class))
	{
		LogInternal("Storing data provider for '"$Class$"'", 'TitanTeamFix');

		DP.ClassName = PathName(Class);
		Class'UnrealScriptTools14.USTools'.static.SetFriendlyName(DP, "Titan Team Fix");
		Class'UnrealScriptTools14.USTools'.static.SetDescription(DP, "Configurable Team Balancer");

		DP.SaveConfig();
	}


	// ***
	bRemoveWhenFinal = True;

	// Remove TTeamFixGeneric from serveractors (if it's there); cleans up config settings from old betas
	GE = GameEngine(FindObject("Engine.Default__GameEngine", Class'GameEngine'));

	for (i=0; i<GE.ServerActors.Length; ++i)
		if (GE.ServerActors[i] == "TitanTeamFixUT3.TTeamFixGeneric")
			GE.ServerActors.Remove(i--, 1);

	GE.SaveConfig();
	// ***
}

// Used to re-initialize mutator after seamless travelling
function InitMutator(string Options, out string ErrorMessage)
{
	local TTeamFix TTF;

	Super.InitMutator(Options, ErrorMessage);

	if (Master == none)
	{
		foreach AllActors(Class'TTeamFix', TTF)
		{
			if (UTTeamGame(WorldInfo.Game) != none && TTF.EventMutator == none)
				TTF.InitializeBalancing();

			break;
		}
	}

	//if (Master == none)
	//	WarnInternal("TTeamFixMut::InitMutator: Failed to find master (note: TitanTeamFix must be run as a serveractor)");

	// temp
	//bTODO = true;
	// If the above happens (i.e. failed to find master) then the server admin might be (wrongly) trying to run TTF using this mutator class.
	// Perhaps add in code which auto-starts TTF under those circumstances? However, you would have to hard code it to auto start using the
	// TTeamFixGeneric class; it would just be confusing to server admins if you add config variables into this class to try and let them
	// startup with another balancer module. (after all, they SHOULD only be starting up using serveractors anyway...)


	// Update: TTF needs to run as a mutator now due to the UT3 WebAdmin not allowing modifications to the commandline; if you make a new
	// balancer class, you will also need to make a new mutator class to go with it (with a modified 'BalancerClass' variable)
	if (Master == none)
	{
		TTF = Spawn(BalancerClass);
		TTF.InitializeBalancing();
	}
}


function InitializeEvents(TTeamFix Caller)
{
	// Some checking statements
	if (Caller == none)
	{
		// Just added to remind me to update version numbers each time I recompile
		bUpdateVersionNumber = True;

		WarnInternal("TTeamFixMut::InitializeEvents Caller is none");
		return;
	}

	if (Master != none && Master != Caller)
		WarnInternal("TTeamFixMut::InitializeEvents Master was already set, initializing from new Caller and disregarding old Master");

	Master = Caller;

	bExitingEvent = Master.bNeedExitingEvent;
	bJoiningEvent = Master.bNeedJoiningEvent;
	bJoinedEvent = Master.bNeedJoinedEvent;
	bDelayedJoinedEvent = Master.bNeedDelayedJoinedEvent;
}

function NotifyLogout(Controller Exiting)
{
	local PlayerController PC;
	local AIController AC;
	local int NumBots;

	Super.NotifyLogout(Exiting);

	if (bExitingEvent && Master != none && Exiting != none)
	{
		// When players are in the middle of exiting the game, they are still counted on their old team; this accounts for that.
		// N.B. I'm using a dynamic array as a gross-overcompensation for the unlikely event of NotifyLogout being called recursively
		Master.ExitingPlayers.AddItem(Exiting);

		Master.PlayerExitingGame(Exiting);

		Master.ExitingPlayers.Length = Master.ExitingPlayers.Length - 1;
	}


	// ***
	bRemoveThisDebugCode = True;

	if (bBotDebug)
	{
		if (RecentBotExit != none && RecentBotExit == Exiting)
		{
			foreach WorldInfo.AllControllers(Class'PlayerController', PC)
			{
				if (PC.PlayerReplicationInfo != none && PC.PlayerReplicationInfo.bAdmin)
				{
					PC.ClientMessage("Bot '"$Exiting$"' ("$Exiting.PlayerReplicationInfo.GetPlayerAlias()$") logged out twice! Report this bug");
					PC.ClientMessage("Game state is:"@WorldInfo.Game.GetStateName());
				}
			}
		}

		if (Exiting != none)
		{
			RecentBotExit = Exiting;
		}
		else
		{
			foreach WorldInfo.AllControllers(Class'PlayerController', PC)
			{
				if (PC.PlayerReplicationInfo != none && PC.PlayerReplicationInfo.bAdmin)
				{
					PC.ClientMessage("Null controller logging out! Report this bug");
					PC.ClientMessage("Game state is:"@WorldInfo.Game.GetStateName());
				}
			}
		}


		// Check NumBots vs raw bot count
		foreach WorldInfo.AllControllers(Class'AIController', AC)
		{
			++NumBots;
		}

		if (NumBots == WorldInfo.Game.NumBots)
		{
			bNumBotsWasValid = True;
		}
		else if (bNumBotsWasValid)
		{
			foreach WorldInfo.AllControllers(Class'PlayerController', PC)
			{
				if (PC.PlayerReplicationInfo != none && PC.PlayerReplicationInfo.bAdmin)
				{
					PC.ClientMessage("NumBots became invalid afer '"$Exiting$"' ("$Exiting.PlayerReplicationInfo.PlayerName$") was logged out");
					PC.ClientMessage("Game state is:"@WorldInfo.Game.GetStateName());
				}
			}
		}
	}
	// ***
}

function ModifyLogin(out string Portal, out string Options)
{
	if (bJoiningEvent && Master != none)
		Master.PlayerJoiningGame(Portal, Options);

	Super.ModifyLogin(Portal, Options);
}

function NotifyLogin(Controller NewPlayer)
{
	//local UniqueNetId NullId;

	Super.NotifyLogin(NewPlayer);

	if (Master == none)
		return;


	if (bJoinedEvent)
		Master.PlayerJoinedGame(NewPlayer);


	if (bDelayedJoinedEvent)
	{
		if (PlayerController(NewPlayer) != none && (PlayerController(NewPlayer).HashResponseCache == "" || PlayerController(NewPlayer).HashResponseCache == "0"))
		{
			DelayedJoinNotify.AddItem(PlayerController(NewPlayer));
			Enable('Tick');
		}
		else
		{
			Master.DelayedJoinedGame(NewPlayer);
		}
	}
}

function Tick(float DeltaTime)
{
	local int i;

	for (i=0; i<DelayedJoinNotify.Length; ++i)
	{
		if (DelayedJoinNotify[i].HashResponseCache != "" && DelayedJoinNotify[i].HashResponseCache != "0")
		{
			Master.DelayedJoinedGame(DelayedJoinNotify[i]);
			LastKeyGrab = WorldInfo.TimeSeconds;

			DelayedJoinNotify.Remove(i, 1);
			--i;
		}
	}


	if (DelayedJoinNotify.Length <= 0)
	{
		Disable('Tick');
	}
	else if (WorldInfo.TimeSeconds - LastKeyGrab > 10.0)
	{
		for (i=0; i<DelayedJoinNotify.Length; ++i)
			if (DelayedJoinNotify[i] != none)
				Master.DelayedJoinedGame(DelayedJoinNotify[i], True);

		DelayedJoinNotify.Length = 0;
		Disable('Tick');
	}
}

function GetSeamlessTravelActorList(bool bToEntry, out array<Actor> ActorList)
{
	Super.GetSeamlessTravelActorList(bToEntry, ActorList);

	if (Master != none)
		Master.NotifySeamlessTravel(bToEntry, ActorList);
}

function GetServerDetails(out GameInfo.ServerResponseLine ServerState)
{
	local int i;

	Super.GetServerDetails(ServerState);

	i = ServerState.ServerInfo.Length;
	ServerState.ServerInfo.Length = i+1;

	ServerState.ServerInfo[i].Key = "TitanTeamFixVersion";
	ServerState.ServerInfo[i].Value = TTFVersion;
}

function Mutate(string MutateString, PlayerController Sender)
{
	local AIController AC;
	local int RawBotCount;

	if (Master == none || Sender == none)
	{
		Super.Mutate(MutateString, Sender);
		return;
	}


	bRemoveThisDebugCode = True;


	if (MutateString ~= "TTFVersion")
	{
		Sender.ClientMessage("Running TitanTeamFix version:"@TTFVersion);
	}
	else if (MutateString ~= "TTFDebug")
	{
		Sender.ClientMessage("Team 0 size:"@WorldInfo.Game.GameReplicationInfo.Teams[0].Size$", Team 1 size:"@WorldInfo.Game.GameReplicationInfo.Teams[1].Size);
	}
	else if (MutateString ~= "TTFPlayerCount")
	{
		foreach WorldInfo.AllControllers(Class'AIController', AC)
		{
			++RawBotCount;
		}

		Sender.ClientMessage("DesiredPlayerCount (i.e. MinPlayers):"@UTGame(WorldInfo.Game).DesiredPlayerCount$", NumPlayers:"@WorldInfo.Game.NumPlayers$", NumBots:"@WorldInfo.Game.NumBots);
		Sender.ClientMessage("RawBotCount:"@RawBotCount$", NumTravellingPlayers:"@WorldInfo.Game.NumTravellingPlayers);
	}
	else if (MutateString ~= "TTFBotDebug" && Sender.PlayerReplicationInfo != none && Sender.PlayerReplicationInfo.bAdmin)
	{
		bBotDebug = !bBotDebug;

		Sender.ClientMessage("bBotDebug is now '"$bBotDebug$"'");
	}
	else
	{
		Super.Mutate(MutateString, Sender);
	}
}


defaultproperties
{
	BalancerClass=Class'TTeamFixGeneric'
}