//==============================================================================
//  WebAdmin handler for activities related to the game currently in
//  progress on the server
//
//  Written by Michael Comeau
//  Revised by Ron Prestenback
//  © 2003,2004 Epic Games, Inc. All Rights Reserved
//==============================================================================

class xWebQueryCurrent extends xWebQueryHandler
	config;

var config string CurrentIndexPage;		// This is the page with the Menu
var config string CurrentPlayersPage;
var config string CurrentGamePage;
var config string CurrentConsolePage;
var config string CurrentConsoleLogPage;
var config string CurrentConsoleSendPage;
var config string CurrentMutatorsPage;
var config string CurrentBotsPage;
var config string CurrentRestartPage;
//if _RO_
var config string CurrentResetPage;
//end _RO_
var config string DefaultSendText;
var config string StatTable;
var config string StatTableRow;

// Custom Skin Support
var config string PlayerListHeader;
var config string PlayerListLinkedHeader;
var config string PlayerListMinPlayers;
var config string ConsoleRefreshTag;
var config string MutatorTablePage;
var config string MutatorGroupTitle;
var config string MutatorGroupMember;


// Localization
// Sections & Titles
var localized string BadGameType;
var localized string CurrentLinks[6];
var localized string NoBotsTitle;

// Labels
var localized string KickButtonText[3];
var localized string NoPlayersConnected;
var localized string SelectedMutators;
var localized string PickMutators;
var localized string GameTypeUnsupported;
var localized string NoBots;
var localized string Added;
var localized string Removed;
var localized string BotStatus;
var localized string SingleBotStatus;
var localized string ConsoleUserlist;

// Help messages
var localized string NoteGamePage;
var localized string NotePlayersPage;
var localized string NoteConsolePage;
var localized string NoteMutatorsPage;
var localized string NoteBotsPage;

var StringArray	SpeciesNames;
var array<StringArray>  BotList;		// Sorted bot list by species

function bool Query(WebRequest Request, WebResponse Response)
{
	if (!CanPerform(NeededPrivs))
		return false;

	switch (Mid(Request.URI, 1))
	{
		case DefaultPage:
			QueryCurrentFrame(Request, Response);
			return true;

		case CurrentIndexPage:
			QueryCurrentMenu(Request, Response);
			return true;

		case CurrentPlayersPage:
			if (!MapIsChanging())
				QueryCurrentPlayers(Request, Response);
			return true;

		case CurrentGamePage:
			if (!MapIsChanging())
				QueryCurrentGame(Request, Response);
			return true;

		case CurrentConsolePage:
			if (!MapIsChanging())
				QueryCurrentConsole(Request, Response);
			return true;

		case CurrentConsoleLogPage:
			if (!MapIsChanging())
				QueryCurrentConsoleLog(Request, Response);
			return true;

		case CurrentConsoleSendPage:
			QueryCurrentConsoleSend(Request, Response);
			return true;

		case CurrentMutatorsPage:
			if (!MapIsChanging())
				QueryCurrentMutators(Request, Response);
			return true;

		case CurrentBotsPage:
			if (!MapIsChanging())
				QueryCurrentBots(Request, Response);
			return true;

		case CurrentRestartPage:
			if (!MapIsChanging())
				QueryRestartPage(Request, Response);
			return true;
	}
	return false;
}

//*****************************************************************************
function QueryCurrentFrame(WebRequest Request, WebResponse Response)
{
local String Page;

	// if no page specified, use the default
	Page = Request.GetVariable("Page", CurrentGamePage);

	Response.Subst("IndexURI", 	CurrentIndexPage$"?Page="$Page);
	Response.Subst("MainURI", 	Page);

	ShowFrame(Response, DefaultPage);
}

function QueryCurrentMenu(WebRequest Request, WebResponse Response)
{
	local String Page;

	Page = Request.GetVariable("Page", CurrentGamePage);

	// set background colors
	Response.Subst("DefaultBG", DefaultBG);	// for unused tabs

	Response.Subst("PlayersBG", DefaultBG);
	Response.Subst("GameBG", 	DefaultBG);
	Response.Subst("ConsoleBG",	DefaultBG);
	Response.Subst("MutatorsBG",DefaultBG);
	Response.Subst("RestartBG", DefaultBG);

	switch(Page)
	{
		case CurrentPlayersPage:
			Response.Subst("PlayersBG",	HighlightedBG);
			break;
		case CurrentGamePage:
			Response.Subst("GameBG", 	HighlightedBG);
			break;
		case CurrentConsolePage:
			Response.Subst("ConsoleBG",	HighlightedBG);
			break;
		case CurrentMutatorsPage:
			Response.Subst("MutatorsBG",HighlightedBG);
			break;
		case CurrentRestartPage:
			Response.Subst("RestartBG", HighlightedBG);
			break;
	}

	// Set URIs
	Response.Subst("PlayersURI", 	DefaultPage$"?Page="$CurrentPlayersPage);
	Response.Subst("GameURI",		DefaultPage$"?Page="$CurrentGamePage);
	Response.Subst("ConsoleURI", 	DefaultPage$"?Page="$CurrentConsolePage);
	Response.Subst("MutatorsURI", 	DefaultPage$"?Page="$CurrentMutatorsPage);
	Response.Subst("RestartURI", 	DefaultPage$"?Page="$CurrentRestartPage);

	// Set link text
	Response.Subst("GameLink", 		CurrentLinks[0]);
	Response.Subst("PlayerLink", 	CurrentLinks[1]);
	Response.Subst("ConsoleLink",	CurrentLinks[2]);
	Response.Subst("MutatorLink",	CurrentLinks[3]);
	Response.Subst("RestartLink",	CurrentLinks[4]);

	ShowPage(Response, CurrentIndexPage);
}

function QueryCurrentPlayers(WebRequest Request, WebResponse Response)
{
	local string Sort, PlayerListSubst, TempStr, TempTag, TempData;
	local string TableHeaders, GameType, Reverse, ColorNames[2], Last;
	local StringArray	PlayerList;
	local Controller P, NextP;
	local int i, Cols, mlength;
	local string IP, ID;
	local bool bCanKick, bCanBan, bCanKickBots;

	Response.Subst("Section", CurrentLinks[1]);
	Response.Subst("PostAction", CurrentPlayersPage);
	ColorNames[0] = class'TeamInfo'.default.ColorNames[0];
	ColorNames[1] = class'TeamInfo'.default.ColorNames[1];
	MLength = int(Eval(Len(ColorNames[0]) > Len(ColorNames[1]), string(Len(ColorNames[0])), string(Len(ColorNames[1]))));

	if (CanPerform("Xp|Kp|Kb|Ko"))
	{
		PlayerList = new(None) class'SortedStringArray';

		Sort = Request.GetVariable("Sort", "Name");
		Last = Request.GetVariable("Last");
		Response.Subst("Sort", Sort);
		Cols = 0;

		bCanKick = CanPerform("Kp");
		bCanBan = CanPerform("Kb");
		bCanKickBots = CanPerform("Ko|Mb");
		if (Last == Sort && Request.GetVariable("ReverseSort") == "")
		{
			PlayerList.ToggleSort();
			Reverse = "?ReverseSort=True";
		}

		else Reverse = "";

		// Count the number of Columns allowed
		if (bCanKick || bCanBan || bCanKickBots)
		{
		// Use 'do-while' to avoid access-none when destroying Controllers within the loop
			P = Level.ControllerList;
			if (P != None)
			{
				do {
					NextP = P.NextController;
					if(		PlayerController(P) != None
						&&	P.PlayerReplicationInfo != None
						&&	NetConnection(PlayerController(P).Player) != None)
					{
						if ( bCanBan && Request.GetVariable("Ban" $ string(P.PlayerReplicationInfo.PlayerID)) != "" )
							Level.Game.AccessControl.KickBanPlayer(PlayerController(P));

						//if _RO_
						else if ( bCanBan && Request.GetVariable("Session" $ string(P.PlayerReplicationInfo.PlayerID)) != "" )
							Level.Game.AccessControl.BanPlayer(PlayerController(P), true);
						//end _RO_

						else if ( bCanKick && Request.GetVariable("Kick" $ string(P.PlayerReplicationInfo.PlayerID)) != "" )
							Level.Game.AccessControl.KickPlayer(PlayerController(P));
					}

					else if ( PlayerController(P) == None && bCanKickBots && P.PlayerReplicationInfo != None &&
						  	  Request.GetVariable("Kick" $ string(P.PlayerReplicationInfo.PlayerID)) != "")
					{	// Kick Bots
						P.Destroy();
					}
					P = NextP;
				} until (P == None);
			}

			if (bCanKick || bCanKickBots) Cols += 1;
			if (bCanBan) Cols += 2;

            Response.Subst("KickButton", SubmitButton("Kick", KickButtonText[Cols-1]));

			// Build of valid TableHeaders
			TableHeaders = "";
			if (bCanKick || bCanKickBots)
			{
				Response.Subst("HeadTitle", "Kick");
				TableHeaders $= WebInclude(PlayerListHeader);
			}

			if (bCanBan)
			{
			    //if _RO_
				Response.Subst("HeadTitle", "Session");
				TableHeaders $= WebInclude(PlayerListHeader);
				//end _RO_

				Response.Subst("HeadTitle", "Ban");
				TableHeaders $= WebInclude(PlayerListHeader);
			}

			if (Sort ~= "Name") Response.Subst("ReverseSort", Reverse);
			else Response.Subst("ReverseSort", "");
			Response.Subst("HeadTitle", "Name");
			TableHeaders $= WebInclude(PlayerListLinkedHeader);

			if (Level.Game.GameReplicationInfo.bTeamGame)
			{
				if (Sort ~= "Team")	Response.Subst("ReverseSort", Reverse);
				else Response.Subst("ReverseSort", "");
				Response.Subst("HeadTitle", "Team");
				TableHeaders $= WebInclude(PlayerListLinkedHeader);
			}

			if (Sort ~= "Ping")	Response.Subst("ReverseSort", Reverse);
			else Response.Subst("ReverseSort", "");
			Response.Subst("HeadTitle", "Ping");
			TableHeaders $= WebInclude(PlayerListLinkedHeader);

			if (Sort ~= "Score") Response.Subst("ReverseSort", Reverse);
			else Response.Subst("ReverseSort", "");
			Response.Subst("HeadTitle", "Score");
			TableHeaders $= WebInclude(PlayerListLinkedHeader);

            //if _RO_
			Response.Subst("HeadTitle", "Team Kills");
			TableHeaders $= WebInclude(PlayerListHeader);
			//end _RO_

			Response.Subst("HeadTitle", "IP");
			TableHeaders $= WebInclude(PlayerListHeader);

			// evo ---
			if (Level.Game.AccessControl.bBanbyID)
			{
				Response.Subst("HeadTitle", "Global ID");
				TableHeaders $= WebInclude(PlayerListHeader);
			}
			// --- evo

			Response.Subst("TableHeaders", TableHeaders);
		}

		if (CanPerform("Ms"))
		{
			GameType = Level.GetItemName(SetGamePI(GameType));
			if (GamePI != None && GamePI.Settings[GamePI.FindIndex(GameType$".MinPlayers")].SecLevel <= CurAdmin.MaxSecLevel())
			{
				if ((Request.GetVariable("SetMinPlayers", "") != "") && UnrealMPGameInfo(Level.Game) != None)
				{
					UnrealMPGameInfo(Level.Game).MinPlayers = Min(Max(int(Request.GetVariable("MinPlayers", String(0))), 0), 32);
					Level.Game.SaveConfig();
				}

				Response.Subst("MinPlayers", string(UnrealMPGameInfo(Level.Game).MinPlayers));
				Response.Subst("MinPlayerPart", WebInclude(PlayerListMinPlayers));
			}

			else
			{
				Response.Subst("MinPlayers", "");
				Response.Subst("MinPlayersPart", "");
			}
		}

		for (P=Level.ControllerList; P!=None; P=P.NextController)
		{
			TempData = "";
			if (!P.bDeleteMe && P.bIsPlayer && P.PlayerReplicationInfo != None)
			{
				Response.Subst("Content", CheckBox("Kick" $ string(P.PlayerReplicationInfo.PlayerID), False));
				if (CanPerform("Kp"))
					TempData $= WebInclude(CellCenter);

				if (CanPerform("Kb"))
				{
				    //if _RO_
					if ( PlayerController(P) != None )
					{
						Response.Subst("Content", Checkbox("Session" $ string(P.PlayerReplicationInfo.PlayerID), False));
						TempData $= WebInclude(CellCenter);
						Response.Subst("Content", Checkbox("Ban" $ string(P.PlayerReplicationInfo.PlayerID), False));
						TempData $= WebInclude(CellCenter);
					}
					else
					{
					    Response.Subst("Content", "");
					    TempData $= WebInclude(CellCenter)$WebInclude(CellCenter);
					}
					//else
					//if ( PlayerController(P) != None )
					//	Response.Subst("Content", Checkbox("Ban" $ string(P.PlayerReplicationInfo.PlayerID), False));
					//else Response.Subst("Content", "");
					//TempData $= WebInclude(CellCenter);
					//end _RO_
				}

				TempStr = "";
				if (DeathMatch(Level.Game) != None && DeathMatch(Level.Game).bTournament && P.PlayerReplicationInfo.bReadyToPlay)
					TempStr = " (Ready) ";

				else if (P.PlayerReplicationInfo.bIsSpectator)
					TempStr = " (Spectator) ";

				else if (PlayerController(P) == None)
					TempStr = " (Bot) ";

				if( PlayerController(P) != None )
				{
					IP = PlayerController(P).GetPlayerNetworkAddress();
					IP = HtmlEncode(" " $ Left(IP, InStr(IP, ":")));
					// evo ---
					ID = HtmlEncode(" " $ Eval(Level.Game.AccessControl.bBanbyID, PlayerController(P).GetPlayerIDHash(), " "));
					// --- evo
				}

				else
				{
					IP = HtmlEncode("  ");
					ID = HtmlEncode("  ");
				}

				Response.Subst("Content", HtmlEncode(P.PlayerReplicationInfo.PlayerName $ TempStr));
				TempData $= WebInclude(NowrapLeft);

				if (Level.Game.bTeamGame)
				{
					if (P.PlayerReplicationInfo.Team != None && P.PlayerReplicationInfo.Team.TeamIndex < 4)
						Response.Subst("Content", "<span style='background-color: "$class'TeamInfo'.default.ColorNames[P.PlayerReplicationInfo.Team.TeamIndex]$"'>"$HtmlEncode("  ")$"</span>"$HtmlEncode(P.PlayerReplicationInfo.Team.GetHumanReadableName()));

					else if (P.PlayerReplicationInfo.bIsSpectator)
						Response.Subst("Content", HtmlEncode("  "));

					TempData $= WebInclude(NowrapCenter);
				}

				Response.Subst("Content", string(P.PlayerReplicationInfo.Ping*4));
				TempData $= WebInclude(CellCenter);

				Response.Subst("Content", string(int(P.PlayerReplicationInfo.Score)));
				TempData $= WebInclude(CellCenter);

                //if _RO_
				Response.Subst("Content", string(P.PlayerReplicationInfo.FFKills));
				TempData $= WebInclude(CellCenter);
				//end _RO_

				Response.Subst("Content", IP);
				TempData $= WebInclude(CellCenter);

				if (Level.Game.AccessControl.bBanbyID)
				{
					Response.Subst("Content", ID);
					TempData $= WebInclude(CellCenter);
				}

				switch (Sort)
				{
					case "Name":
						TempTag = P.PlayerReplicationInfo.PlayerName; break;
					case "Team":	// Ordered by Team, then subordered by last selected sort method
						TempTag = PadRight(class'TeamInfo'.default.ColorNames[P.PlayerReplicationInfo.Team.TeamIndex],MLength,"0");
						switch (Last)
						{
							case "Name":
								TempTag $= P.PlayerReplicationInfo.PlayerName; break;
							case "Ping":
								TempTag $= PadLeft(string(P.PlayerReplicationInfo.Ping*4), 5, "0"); break;
							default:
								TempTag $= PadLeft(string(int(P.PlayerReplicationInfo.Score)), 4, "0"); break;
						}
						break;
					case "Ping":
						TempTag = PadLeft(string(P.PlayerReplicationInfo.Ping*4), 5, "0"); break;
					default:
						TempTag = PadLeft(string(int(P.PlayerReplicationInfo.Score)), 4, "0"); break;
				}

				Response.Subst("RowContent", TempData);
				PlayerList.Add( WebInclude(RowLeft), TempTag);
			}
		}

		PlayerListSubst = "";
		if (PlayerList.Count() > 0)
		{
			for ( i=0; i<PlayerList.Count(); i++)
			{
				if (Sort ~= "Score")
					PlayerListSubst = PlayerList.GetItem(i) $ PlayerListSubst;

				else PlayerListSubst $= PlayerList.GetItem(i);
			}
		}

		else
		{
			Response.Subst("SpanContent", NoPlayersConnected);
			Response.Subst("SpanLength", "6");
			Response.Subst("RowContent", WebInclude(CellColSpan));
			PlayerListSubst = WebInclude(RowCenter);
		}

		Response.Subst("PlayerList", PlayerListSubst);
		Response.Subst("MinPlayers", string(UnrealMPGameInfo(Level.Game).MinPlayers));

		Response.Subst("PageHelp", NotePlayersPage);
		MapTitle(Response);
		ShowPage(Response, CurrentPlayersPage);
	}
	else
		AccessDenied(Response);
}

function QueryCurrentGame(WebRequest Request, WebResponse Response)
{
local StringArray	ExcludeMaps, IncludeMaps, MovedMaps;
local class<GameInfo> GameClass;
local string NewGameType, SwitchButtonName, GameState, NewMap;
local bool bMakeChanges;
local Controller C;
local xPlayer XP;
local TeamPlayerReplicationInfo PRI;
local int MultiKills, Sprees, GameIndex;

	if (CanPerform("Mt|Mm"))
	{
		if (Request.GetVariable("SwitchGameTypeAndMap", "") != "")
		{
			if (CanPerform("Mt"))
				ServerChangeMap(Request, Response, Request.GetVariable("MapSelect"), Request.GetVariable("GameTypeSelect"));

			else AccessDenied(Response);

			return;
		}

		else if (Request.GetVariable("SwitchMap", "") != "")
		{
			if (CanPerform("Mm|Mt"))
			{
				NewMap = Request.GetVariable("MapSelect");
				Level.ServerTravel(NewMap$"?game="$Level.Game.Class$"?mutator="$UsedMutators(), false);
				ShowMessage(Response, WaitTitle, Repl(MapChangingTo, "%MapName%", NewMap));
			}

			else AccessDenied(Response);

			return;
		}

		bMakeChanges = (Request.GetVariable("ApplySettings", "") != "");
		if (CanPerform("Mt") && (bMakeChanges || Request.GetVariable("SwitchGameType", "") != ""))
		{
			NewGameType = Request.GetVariable("GameTypeSelect");
			GameClass = class<GameInfo>(DynamicLoadObject(NewGameType, class'Class'));
		}
		else GameClass = None;

		if (GameClass == None)
		{
			GameClass = Level.Game.Class;
			NewGameType = String(GameClass);
		}

		GameIndex = Level.Game.MaplistHandler.GetGameIndex(NewGameType);
		ExcludeMaps = ReloadExcludeMaps(NewGameType);
		IncludeMaps = ReloadIncludeMaps(ExcludeMaps, GameIndex, Level.Game.MaplistHandler.GetActiveList(GameIndex));

		GameState = "";
		// Show game status if admin has necessary privs
		if (CanPerform("Ma"))
		{
			if (Level.Game.NumPlayers > 0)
			{
				for (C = Level.ControllerList; C != None; C = C.NextController)
				{
					MultiKills = 0;
					Sprees = 0;
					PRI = None;
					XP = xPlayer(C);
					if (XP != None && !XP.bDeleteMe)
					{
						if (TeamPlayerReplicationInfo(XP.PlayerReplicationInfo) != None)
							PRI = TeamPlayerReplicationInfo(XP.PlayerReplicationInfo);

						if (PRI != None)
						{
							Response.Subst("PlayerName", HtmlEncode(PRI.PlayerName));
							Response.Subst("Kills", string(PRI.Kills));
							//if _RO_
							Response.Subst("FFKills", string(PRI.FFKills));
							//end _RO_
							Response.Subst("Deaths", string(PRI.Deaths));
							Response.Subst("Suicides",string(PRI.Suicides));
							//if _RO_
							/*
							//end _RO_
							for (i = 0; i < 7; i++)
								MultiKills += PRI.MultiKills[i];
							Response.Subst("MultiKills", string(MultiKills));
							for (i = 0; i < 6; i++)
								Sprees += PRI.Spree[i];
							Response.Subst("Sprees", string(Sprees));
							//if _RO_
							*/
							//end _RO_
							GameState $= WebInclude(StatTableRow);
						}
					}
				}
			}
            else
                //if _RO_
                GameState = "<tr><td colspan=\"5\" align=\"center\">"@NoPlayersConnected@"</td></tr>";
                //else
                //GameState = "<tr><td colspan=\"6\" align=\"center\">"@NoPlayersConnected@"</td></tr>";
                //end _RO_

			Response.Subst("StatRows", GameState);
			Response.Subst("GameState", WebInclude(StatTable));
		}

		if (GameClass == Level.Game.Class)
		{
			SwitchButtonName="SwitchMap";
			MovedMaps = New(None) Class'SortedStringArray';
			MovedMaps.CopyFromId(IncludeMaps, IncludeMaps.FindTagId(Left(string(Level), InStr(string(Level), "."))));
		}
		else SwitchButtonName="SwitchGameTypeAndMap";

		if (CanPerform("Mt"))
		{
			Response.Subst("Content", Select("GameTypeSelect", GenerateGameTypeOptions(NewGameType)));
			Response.Subst("GameTypeButton", SubmitButton("SwitchGameType", SwitchText));
		}
		else Response.Subst("Content", Level.Game.Default.GameName);

		Response.Subst("GameTypeSelect", WebInclude(CellLeft));
		Response.Subst("Content", Select("MapSelect", GenerateMapListSelect(IncludeMaps, MovedMaps)));
		Response.Subst("MapSelect", WebInclude(CellLeft));
		Response.Subst("MapButton", SubmitButton(SwitchButtonName, SwitchText));
		Response.Subst("PostAction", CurrentGamePage);

		Response.Subst("Section", CurrentLinks[0]);
		Response.Subst("PageHelp", NoteGamePage);
		MapTitle(Response);
		ShowPage(Response, CurrentGamePage);
	}
	else AccessDenied(Response);
}

function QueryCurrentConsole(WebRequest Request, WebResponse Response)
{
local String SendStr, OutStr;

	if (CanPerform("Xc"))
	{
		SendStr = Request.GetVariable("SendText", "");
		if (SendStr != "" && !(Left(SendStr, 6) ~= "debug " || SendStr ~= "debug"))
		{
			if (Left(SendStr, 4) ~= "say ")
				Level.Game.Broadcast(Spectator, Mid(SendStr, 4), 'Say');
			else if (SendStr ~= "pause")
			{
				if (Level.Pauser == None)
					Level.Pauser = Spectator.PlayerReplicationInfo;
				else Level.Pauser = None;
			}
			else if (SendStr ~= "dump")
				Spectator.Dump();

			else if ((Left(SendStr, 4) ~= "get " || Left(SendStr,4) ~= "set ") &&
			(InStr(Caps(SendStr), "XADMINCONFIG") != -1 || !CanPerform("Ms")) )
			{
				if ( InStr(Caps(SendStr), "XADMINCONFIG") != -1 )
				{
					StatusError(Response, ConsoleUserlist);
					ShowMessage(Response, Error, "");
					log("User attempted to modify or enumerate admin account information illegally using the webadmin console.  User:"$Request.Username$".",'WebAdmin');
				}

				else if ( !CanPerform("Ms") )
					AccessDenied(Response);
			}
			else
			{
				OutStr = Level.ConsoleCommand(SendStr);
				if (OutStr != "")
					Spectator.AddMessage(None, OutStr, 'Console');
			}
		}

		Response.Subst("LogURI", CurrentConsoleLogPage);
		Response.Subst("SayURI", CurrentConsoleSendPage);
		ShowPage(Response, CurrentConsolePage);
	}
	else
		AccessDenied(Response);
}

function QueryCurrentConsoleLog(WebRequest Request, WebResponse Response)
{
local String LogSubst, LogStr;
local int i;


	if (CanPerform("Xc"))
	{
		Response.Subst("Section", CurrentLinks[2]);
		Response.Subst("SubTitle", Level.Game.GameReplicationInfo.GameName$" in "$Level.Title);

		i = Spectator.LastMessage();
		LogStr = HtmlEncode(Spectator.NextMessage(i));
		while (LogStr  != "")
		{
			LogSubst = LogSubst$"&gt; "$LogStr$"<br>";
			LogStr = HtmlEncode(Spectator.NextMessage(i));
		}

		Response.Subst("RefreshMeta", ConsoleRefreshTag $ CurrentConsoleLogPage $ "#END\">");
		Response.Subst("LogText", LogSubst);
		Response.Subst("PageHelp", NoteConsolePage);
		MapTitle(Response);
		ShowPage(Response, CurrentConsoleLogPage);
	}
	else
		AccessDenied(Response);
}

function QueryCurrentConsoleSend(WebRequest Request, WebResponse Response)
{
	if (CanPerform("Xc"))
	{
		Response.Subst("DefaultSendText", DefaultSendText);
		Response.Subst("PostAction", CurrentConsolePage);
		ShowPage(Response, CurrentConsoleSendPage);
	}
	else
		AccessDenied(Response);
}

function QueryCurrentMutators(WebRequest Request, WebResponse Response)
{
local int i, j, k, z;
local string selectedmutes, lastgroup, nextgroup, thisgroup, Checked, tmp;
local StringArray	GroupedMutators, SoloMutators;

	if (CanPerform("Mu"))
	{
		SoloMutators = new(None) class'SortedStringArray';
		GroupedMutators = new(None) class'StringArray';

		if (Request.GetVariable("SetMutes", "") != "")
		{
			AIncMutators.Reset();
			lastgroup = "";
			for (i = 0; i<AExcMutators.Count(); i++)
			{
				j = int(AExcMutators.GetItem(i));
				if (j < 0) continue;

				thisgroup = AllMutators[j].GroupName;
				if (Request.GetVariable(AExcMutators.GetTag(i), "") != "" || Request.GetVariable(thisgroup) == AllMutators[j].ClassName)
					AIncMutators.Add(AExcMutators.GetItem(i), AllMutators[j].FriendlyName);
			}
		}

		// Make a list sorted by friendly name
		for (i = 0; i<AExcMutators.Count(); i++)
		{
			j = int(AExcMutators.GetItem(i));
			if (j < 0) continue;

			SoloMutators.Add(string(j), AllMutators[j].FriendlyName);
		}

		// First, Display Selected Mutators, 1 per line
		selectedmutes = "";
		for (i = 0; i<AIncMutators.Count(); i++)
		{
			tmp = "";
			j = int(AIncMutators.GetItem(i));
			if (j < 0) continue;

			Response.Subst("Content", HtmlEncode(AllMutators[j].FriendlyName));
			tmp = WebInclude(CellLeft);
			Response.Subst("Content", HtmlEncode(AllMutators[j].Description));
			Response.Subst("RowContent", tmp $ WebInclude(CellLeft));
			selectedmutes $= WebInclude(RowLeft);
		}

		if (selectedmutes != "")
		{
			Response.Subst("TableTitle", SelectedMutators);
			Response.Subst("TableRows", selectedmutes);
			Response.Subst("SelectedTable", WebInclude(MutatorTablePage));
		}

		CreateFullMutatorList(SoloMutators, GroupedMutators);
		lastgroup = ""; selectedmutes = "";

		// First, display all grouped mutators, sorted by friendly name
		for (i = 0; i<GroupedMutators.Count(); i++)
		{
			j = int(GroupedMutators.GetItem(i));
			if (j < 0) continue;

		// If mod author has forgotten to specify a group name for this mutator,
		// then add unique group name, to avoid all mutes without group names from
		// being considered in the same group
			if (AllMutators[j].GroupName=="")
				thisgroup = "Z" $ string(z++);

			else thisgroup = AllMutators[j].GroupName;

			if ( (i + 1) == GroupedMutators.Count())
				nextgroup = "";

			else
			{
				k = int(GroupedMutators.GetItem(i + 1));
				if (k < 0) continue;

				if (AllMutators[k].GroupName=="")
					nextgroup = "Z" $ string(z);

				else nextgroup = AllMutators[k].GroupName;
			}

			Response.Subst("GroupName", thisgroup);
			Response.Subst("MutatorClass", AllMutators[j].ClassName);
			Response.Subst("MutatorName", AllMutators[j].FriendlyName);
			Response.Subst("MutatorDesc", AllMutators[j].Description);

			if (lastgroup != thisgroup && thisgroup == nextgroup) // and the next mut is in the same group as this one
			{
                Response.Subst("Checked", "checked");
				selectedmutes $= WebInclude(MutatorGroupTitle);
			}

			Checked = Eval(AIncMutators.FindItemId(string(j)) >= 0, " checked", "");
			Response.Subst("Checked", Checked);

			selectedmutes $= WebInclude(MutatorGroupMember);
			lastgroup = thisgroup;
		}

		for (i = 0; i < SoloMutators.Count(); i++)
		{
			tmp = "";	// Some sort of bug in WebInclude...must empty this var each time I use it
			j = int(SoloMutators.GetItem(i));

			Response.Subst("Content", CheckBox(AllMutators[j].ClassName, AIncMutators.FindItemId(string(j)) >= 0) $ "&nbsp;" $ AllMutators[j].FriendlyName);
			tmp = WebInclude(NowrapLeft);
			Response.Subst("Content", AllMutators[j].Description);
			Response.Subst("RowContent", tmp $ WebInclude(CellLeft));
			selectedmutes $= WebInclude(RowLeft);
		}

		Response.Subst("TableTitle", PickMutators);
		Response.Subst("TableRows", selectedmutes);
		Response.Subst("ChooseTable", WebInclude(MutatorTablePage));

		MapTitle(Response);
		Response.Subst("Section", CurrentLinks[3]);
		Response.Subst("PageHelp", NoteMutatorsPage);
		Response.Subst("PostAction", CurrentMutatorsPage);
		ShowPage(Response, CurrentMutatorsPage);
	}

	else AccessDenied(Response);
}

function QueryCurrentBots(WebRequest Request, WebResponse Response)
{
local array<xUtil.PlayerRecord> PlayerRecords;
local string OutStr, BotName, LeftTable, RightTable;
local int i, j, BotCount, maxbots, cnt, Col1Count;
local xBot	B;
local bool oldstate, newstate, bInMatch;
local DeathMatch	DM;

	if (!CanPerform("Mb"))
	{
		AccessDenied(Response);
		return;
	}

	DM = DeathMatch(Level.Game);
	if (DM == None)
	{
		ShowMessage(Response, BadGameType, Repl(GameTypeUnsupported, "%GameType%", string(Level.Game.Class)));
		return;
	}

	// Disable any type of Bots controls when stats are on
	if (DM.bEnableStatLogging && DM.NumBots == 0)
	{
		ShowMessage(Response, NoBotsTitle, NoBots);
		return;
	}

	// Make a sorted list of all species and group bots
	if (SpeciesNames == None)
	{
		class'xUtil'.static.GetPlayerList(PlayerRecords);
		SpeciesNames = new(None) class'SortedStringArray';
		for (i = 0; i<PlayerRecords.Length; i++)
			SpeciesNames.Add(PlayerRecords[i].Species.default.SpeciesName, PlayerRecords[i].Species.default.SpeciesName, true);

		BotList.Length = SpeciesNames.Count();	// Preset Bot list size

		for (i = 0; i<PlayerRecords.Length; i++)
		{
			j = SpeciesNames.FindTagId(PlayerRecords[i].Species.default.SpeciesName);
			if (j >= 0 && BotList[j] == None)
				BotList[j] = new(None) class'SortedStringArray';

			// Add the player record to the BotList
			BotList[j].Add(PlayerRecords[i].DefaultName, PlayerRecords[i].DefaultName);
		}
	}

	bInMatch = Level.Game.IsInState('MatchInProgress');

	if (Request.GetVariable("addbotnum", "") != "")
	{
		BotCount = int(Request.GetVariable("addnum", "0"));
		if (Request.GetVariable("BotAction", "") ~= "Add")
		{
			maxbots = 32-(DM.NumPlayers + DM.NumBots);

			BotCount = Clamp(BotCount, 0, maxbots);
			for (i=0;i<BotCount; i++)
				DM.ForceAddBot();

			// Save the change
			if (BotCount == 0)
				StatusError(Response, "0" @ Repl(BotStatus, "%Action%", Added));

			else if (BotCount == 1)
				StatusOk(Response, "1" @ Repl(SingleBotStatus, "%Action%", Added));

			else
				StatusOk(Response, BotCount @ Repl(BotStatus, "%Action%", Added));
		}

		else if (Request.GetVariable("BotAction", "") ~= "Remove")
		{
			BotCount = Clamp(BotCount, 0, DM.NumBots);

			DM.MinPlayers = DM.NumPlayers + DM.NumBots - BotCount;
			if (BotCount == 0)
				StatusError(Response, "0" @ Repl(BotStatus, "%Action%", Removed));

			else if (BotCount == 1)
				StatusOk(Response, "1" @ Repl(SingleBotStatus, "%Action%", Removed));

			else
				StatusOk(Response, BotCount@Repl(BotStatus, "%Action%", Removed));
		}
	}
	else if (Request.GetVariable("selectbots", "") != "" && bInMatch)
	{
		// Read as many bot infos as available
		for (i = 0; i<SpeciesNames.Count(); i++)
		{
			for (j = 0; j<BotList[i].Count(); j++)
			{
				oldstate = Request.GetVariable("BotX"$i$"."$j, "") != "";
				newstate = Request.GetVariable("Bot"$i$"."$j, "") != "";
				BotName = BotList[i].GetItem(j);
				if (oldstate != newstate)
				{
					if (oldstate)	// remove the bot
					{
						B = FindPlayingBot(BotName);
						if (B != None)
						{
							DM.MinPlayers = DM.NumPlayers + DM.NumBots - 1;
							B.Destroy();
						}
					}
					else
					{
						DM.MinPlayers = DM.NumPlayers + DM.NumBots;
						DM.AddNamedBot(BotName);
					}
				}
			}
		}
	}

	// Build our BotList
	if (SpeciesNames != None)
	{
		BotCount = 0;
		for ( i = 0; i < SpeciesNames.Count(); i++ )
			BotCount += BotList[i].Count();

		for (i = 0; i<SpeciesNames.Count(); i++)
		{
			OutStr = "";
			Response.Subst("SpeciesName", SpeciesNames.GetItem(i));
			OutStr = WebInclude(CurrentBotsPage$"_species");
			for (j = 0; j<BotList[i].Count(); j++)
			{
				Response.Subst("BotChecked", "");
				Response.Subst("BotIndex", String(i)$"."$String(j));
				Response.Subst("BotName", BotList[i].GetItem(j));
				B = FindPlayingBot(BotList[i].GetItem(j));

				Response.Subst("DisabledBots", "");
				if (!bInMatch)
					Response.Subst("DisabledBots", " DISABLED");
				// The following are set only if bot is currently in the game
				if (B != None)
				{
					Response.Subst("BotColor", GetTeamColor(B.PlayerReplicationInfo.Team));
					Response.Subst("BotTeamName", GetTeamName(B.PlayerReplicationInfo.Team));
					OutStr $= WebInclude(CurrentBotsPage$"_row_sel");
				}

				else OutStr $= WebInclude(CurrentBotsPage$"_row");

			}

			Response.Subst("BotList", OutStr);

			// Attempt to balance the bot lists
			if ( cnt + BotList[i].Count() < BotCount / 2 )
			{
				// Fill the left list until it has more than half the total number of bots
				Col1Count = cnt + BotList[i].Count();
				LeftTable $= WebInclude(CurrentBotsPage $ "_species_group");
			}
			else
			{
				if ( cnt - Col1Count > Col1Count )
				{
					// Account for any species that contained a large number of bots by adding smaller
					// species to the first column
					Col1Count += Botlist[i].Count();
					LeftTable $= WebInclude(CurrentBotsPage $ "_species_group");
				}
				else RightTable $= WebInclude(CurrentBotsPage $ "_species_group");
			}
			cnt += BotList[i].Count();
		}
	}

	Response.Subst("LeftBotTable", LeftTable);
	Response.Subst("RightBotTable", RightTable);

	// If not in match, make sure that the bots selection button is disabled
	Response.Subst("DisabledBots", "");
	if (!bInMatch)
		Response.Subst("DisabledBots", " DISABLED");
	MapTitle(Response);
	Response.Subst("PageHelp", NoteBotsPage);
	Response.Subst("Section", CurrentLinks[4]);
	ShowPage(Response, CurrentBotsPage);
}

function xBot FindPlayingBot(string BotName) // Returns -1 on failure, or index for team/color
{
local Controller C;
local xBot B;

	for (C = Level.ControllerList; C != None; C = C.NextController)
	{
		B = xBot(C);
		if (B != None)
			if (B.PlayerReplicationInfo.PlayerName == BotName)
				return B;
	}
	return None;
}

function string GetTeamColor(TeamInfo Team)
{
	if (Team == None)
		return "";

	if (Team.TeamIndex < 4)
		return Team.ColorNames[Team.TeamIndex];

	return "#CCCCCC";
}

function string GetTeamName(TeamInfo Team)
{
	if (Team == None)
		return "";

	return Team.GetHumanReadableName();
}

defaultproperties
{
	 NeededPrivs="X|K|M|Xs|Xc|Xp|Xi|Kp|Kb|Ko|Mb|Mt|Mm|Mu|Ma"
     CurrentIndexPage="current_menu"
     CurrentPlayersPage="current_players"
     CurrentGamePage="current_game"
     CurrentConsolePage="current_console"
     CurrentConsoleLogPage="current_console_log"
     CurrentConsoleSendPage="current_console_send"
     CurrentMutatorsPage="current_mutators"
     CurrentBotsPage="current_bots"
     CurrentRestartPage="current_restart"
     PlayerListHeader="current_players_list_head"
     PlayerListLinkedHeader="current_players_list_head_link"
     PlayerListMinPlayers="current_players_minp"
     MutatorTablePage="current_mutators_table"
     MutatorGroupTitle="current_mutators_group"
     MutatorGroupMember="current_mutators_group_row"
     StatTable="current_game_stat_table"
     StatTableRow="current_game_stat_table_row"
     ConsoleRefreshTag="<meta http-equiv=\"refresh\" CONTENT=\"5; URL="
     DefaultSendText="say "

     BadGameType="Unsupported Game Type"
     NoBotsTitle="Bots unavailable"
     ConsoleUserlist="Getting or setting admin accounts and groups is not allowed through the webadmin console.  This action has been logged."
     CurrentLinks(0)="Current Game"
     CurrentLinks(1)="Player List"
     CurrentLinks(2)="Server Console"
     CurrentLinks(3)="Mutators"
     CurrentLinks(4)="Restart Map"

	 KickButtonText(0)="Kick"
	 KickButtonText(1)="Ban"
	 KickButtonText(2)="Kick/Ban"
	 NoPlayersConnected="** No Players Connected **"
	 SelectedMutators="Selected Mutators"
	 PickMutators="Select desired mutators"
	 GameTypeUnsupported="The Game Type '%GameType%' does not use standard bots."
	 NoBots="You cannot add bots while World Stats Logging is enabled."
	 SingleBotStatus="bot was %Action%"
	 BotStatus="bots were %Action%"
	 Added="added."
	 Removed="removed."

     NoteGamePage="You can view and select maps from other gametypes by using the combo box.  Selecting maps from other gametypes will automatically switch the server to that gametype."
     NotePlayersPage="In order to see the global ID for connected players, change the value for bBanbyID in the [Engine.AccessControl] section of your ini to 'True'.  Bots cannot be banned."
     NoteConsolePage="You may communicate with the players in the game by entering text at the text box and clicking 'Send'.  You can also enter console commands to control the server.  Game messages are shown in the log window, with the exception of team messages."
     NoteMutatorsPage="Select which mutators you want to be used when you hit the Restart Server Link"
     NoteBotsPage="You may only add bots once the game has started.  Adding bots has an adverse effect on the MinPlayers setting, and may cause this value to change.  If stats are enabled, you may only add bots if bots are already in the game."
     DefaultPage="currentframe"
     Title="Current"
}
