// ====================================================================
//  Class:  xVoting.xVotingHandler
//
//	xVotingHandler handles the server side of map voting, kick voting,
//  and match setup.
//
//  Written by Bruce Bickar
//  (c) 2003, Epic Games, Inc.  All Rights Reserved
// ====================================================================
class xVotingHandler extends VotingHandler config;

// work variables
var array<VotingReplicationInfo> MVRI; // used to communicated between players and server
var int           MapCount;      // number of maps
var bool          bLevelSwitchPending;
var bool          bMidGameVote;
var int           TimeLeft,ScoreBoardTime,ServerTravelTime;
var array<MapVoteScore> MapVoteCount;
var array<KickVoteScore> KickVoteCount;
var class<MapVoteHistory> MapVoteHistoryClass;
var array<MapVoteMapList> MapList;
var MapVoteHistory History;
var string        TextMessage;
var string        ServerTravelString;
var bool          bAutoDetectMode; // true if mapvote enabled but not configuration

// ---- INI Configuration setting variables ----
var() config array<MapVoteGameConfig> GameConfig;
var() config int    VoteTimeLimit;
var() config int    ScoreBoardDelay;
var() config bool   bAutoOpen;
var() config int    MidGameVotePercent;
var() config bool   bScoreMode;
var() config bool   bAccumulationMode;
var() config bool   bEliminationMode;
var() config int    MinMapCount;
var() config string MapVoteHistoryType;
var() config int    RepeatLimit;
var() config int    DefaultGameConfig;
var() config bool   bDefaultToCurrentGameType;
var() config bool   bMapVote;
var() config bool   bKickVote;
var() config bool   bMatchSetup;
var() config int    KickPercent;
var() config bool   bAnonymousKicking;
var() config string MapListLoaderType;
var() config array<AccumulationData> AccInfo; // used to save player's unused votes between maps when in Accumulation mode
var() config int    ServerNumber;
var() config int    CurrentGameConfig;

// MatchSetup
var MatchConfig MatchProfile;
var string GameConfigPage;
var string MapListConfigPage;

// Localization variables
var localized string lmsgInvalidPassword;
var localized string lmsgMatchSetupPermission;
var localized string lmsgKickVote;
var localized string lmsgAnonymousKickVote;
var localized string lmsgKickVoteAdmin;
var localized string lmsgMapWon;
var localized string lmsgMidGameVote;
var localized string lmsgSpectatorsCantVote;
var localized string lmsgMapVotedFor;
var localized string lmsgMapVotedForWithCount;
var localized string PropsDisplayText[17];
var localized string PropDescription[17];
var localized string lmsgAdminMapChange;
var localized string lmsgGameConfigColumnTitle[6];

const MAPVOTEALLOWED = True;
const KICKVOTEALLOWED = True;
const MATCHSETUPALLOWED = False;

static function bool IsEnabled()
{
	return ( Default.bMapVote || Default.bKickVote || Default.bMatchSetup );
}

//================================================================================================
//                                    Startup/Event Code
//================================================================================================
function PostBeginPlay()
{
	local int i;

	Super.PostBeginPlay();

	// disable voting in single player mode
    if( Level.NetMode == NM_StandAlone )
		return;

// if _RO_
//	if ( Level.IsDemoBuild() )
//	{
//		bMapVote = False;
//		bKickVote = False;
//	}

	bMatchSetup = bMatchSetup && MATCHSETUPALLOWED;

	if(bKickVote)
		log("Kick Voting Enabled",'MapVote');
	else
		log("Kick Voting Disabled",'MapVote');

	if(bMapVote)
	{
		log("Map Voting Enabled",'MapVote');
		// check current game settings
		if( GameConfig.Length > 0 )
		{
			if( !(string(Level.Game.Class) ~= GameConfig[CurrentGameConfig].GameClass) )
			{
				CurrentGameConfig = 0;
				// find matching game type in game config
				for( i=0; i<GameConfig.Length; i++)
				{
					if(GameConfig[i].GameClass ~= string(Level.Game.Class))
					{
						CurrentGameConfig = i;
						break;
					}
				}
			}
		}
		else
			CurrentGameConfig = 0;
		LoadMapList();
	}
	else
		log("Map Voting Disabled",'MapVote');

	if(bMatchSetup)
	{
		log("MatchSetup Enabled",'MapVote');

		MatchProfile = CreateMatchProfile();
		MatchProfile.Init(Level);
		MatchProfile.LoadCurrentSettings();
	}
	else
		log("MatchSetup Disabled",'MapVote');
}
//------------------------------------------------------------------------------------------------
function PlayerJoin(PlayerController Player)
{
	// disable voting in single player mode
    if( Level.NetMode == NM_StandAlone )
		return;

	if(!Player.IsA('XPlayer')) // no bots
		return;

	if(bMapVote || bKickVote || bMatchSetup)
	{
		Log("___New Player Joined - " $ Player.PlayerReplicationInfo.PlayerName $ ", " $ Player.GetPlayerNetworkAddress(),'MapVote');
		AddMapVoteReplicationInfo(Player);
	}
}
//------------------------------------------------------------------------------------------------
function PlayerExit(Controller Exiting)
{
	local int i,x,ExitingPlayerIndex;

	// disable voting in single player mode
    if( Level.NetMode == NM_StandAlone )
		return;

    ExitingPlayerIndex = -1;

	log("____PlayerExit", 'MapVoteDebug');

	if(bMapVote || bKickVote || bMatchSetup)
	{
		// find the MVRI belonging to the exiting player
		for(i=0;i < MVRI.Length;i++)
		{
		    // remove players vote from vote count
			if( MVRI[i] != none && (MVRI[i].PlayerOwner == none || MVRI[i].PlayerOwner == Exiting) )
			{
				log("exiting player MVRI found " $ i,'MapVoteDebug');
				ExitingPlayerIndex = i;
				if( bMapVote && MVRI[ExitingPlayerIndex].MapVote > -1 && MVRI[ExitingPlayerIndex].GameVote > -1 )
				{
					for( x=0; x<MapVoteCount.Length; x++ )
					{
						if( MVRI[ExitingPlayerIndex].MapVote == MapVoteCount[x].MapIndex &&
							MVRI[ExitingPlayerIndex].GameVote == MapVoteCount[x].GameConfigIndex)
						{
							MapVoteCount[x].VoteCount -= MVRI[ExitingPlayerIndex].VoteCount;
							UpdateVoteCount(MapVoteCount[x].MapIndex, MapVoteCount[x].GameConfigIndex, MapVoteCount[x].VoteCount);
							break;
						}
					}
				}

				if( bKickVote )
				{
					// clear votes for exiting player
					UpdateKickVoteCount( MVRI[ExitingPlayerIndex].PlayerID, 0 );

					// decrease votecount for player that the exiting player voted against
					if( MVRI[ExitingPlayerIndex].KickVote > -1 && MVRI[MVRI[ExitingPlayerIndex].KickVote] != none )
						UpdateKickVoteCount( MVRI[MVRI[ExitingPlayerIndex].KickVote].PlayerID, -1);
				}
			}

			if( bKickVote && ExitingPlayerIndex > -1 && MVRI[i] != none && MVRI[i].KickVote == ExitingPlayerIndex )
				MVRI[i].KickVote = -1;

			if( MVRI[i] != none && (MVRI[i].PlayerOwner == none || MVRI[i].PlayerOwner == Exiting) )
			{
				log("___Destroying VRI...",'MapVoteDebug');
				MVRI[i].Destroy();
				MVRI[i] = none;
				if( bKickVote )
					TallyKickVotes();
				if( bMapVote )
					TallyVotes(false);
			}
		}
	}
}
//------------------------------------------------------------------------------------------------
function AddMapVoteReplicationInfo(PlayerController Player)
{
	local VotingReplicationInfo M;

	log("___Spawning VotingReplicationInfo",'MapVoteDebug');
	M = Spawn(class'VotingReplicationInfo',Player,,Player.Location);
	if(M == None)
	{
		Log("___Failed to spawn VotingReplicationInfo",'MapVote');
		return;
	}

	M.PlayerID = Player.PlayerReplicationInfo.PlayerID;
	MVRI[MVRI.Length] = M;
}
//================================================================================================
//                                    Map Voting
//================================================================================================
function LoadMapList()
{
	local int i,EnabledMapCount;
	local class<MapListLoader> MapListLoaderClass;
	local MapListLoader Loader;

	MapList.Length = 0; // clear
	MapCount = 0;

	MapVoteHistoryClass = class<MapVoteHistory>(DynamicLoadObject(MapVoteHistoryType, class'Class'));
	History = new(None,"MapVoteHistory"$string(ServerNumber)) MapVoteHistoryClass;
	if(History == None) // Failed to spawn MapVoteHistory
		History = new(None,"MapVoteHistory"$string(ServerNumber)) class'MapVoteHistory_INI';

	log("GameTypes:",'MapVote');

	if(GameConfig.Length == 0)
	{
		bAutoDetectMode = true;
		// default to ONLY current game type and maps
		GameConfig.Length = 1;
		GameConfig[0].GameClass = string(Level.Game.Class);
		GameConfig[0].Prefix = Level.Game.MapPrefix;
		GameConfig[0].Acronym = Level.Game.Acronym;
		GameConfig[0].GameName = Level.Game.GameName;
		GameConfig[0].Mutators="";
		GameConfig[0].Options="";
//		GameConfig.Length = 10;
//		// UT2003 game types
//		GameConfig[0].GameClass="XGame.xDeathMatch";GameConfig[0].Prefix="DM";GameConfig[0].Acronym="DM";GameConfig[0].GameName="DeathMatch";GameConfig[0].Mutators="";GameConfig[0].Options="";
//		GameConfig[1].GameClass="XGame.xTeamGame";GameConfig[1].Prefix="DM";GameConfig[1].Acronym="TDM";GameConfig[1].GameName="Team DeathMatch";GameConfig[1].Mutators="";GameConfig[1].Options="";
//		GameConfig[2].GameClass="XGame.xDoubleDom";GameConfig[2].Prefix="DOM";GameConfig[2].Acronym="DOM";GameConfig[2].GameName="Double Domination";GameConfig[2].Mutators="";GameConfig[2].Options="";
//		GameConfig[3].GameClass="XGame.xCTFGame";GameConfig[3].Prefix="CTF";GameConfig[3].Acronym="CTF";GameConfig[3].GameName="Capture the Flag";GameConfig[3].Mutators="";GameConfig[3].Options="";
//		GameConfig[4].GameClass="XGame.xBombingRun";GameConfig[4].Prefix="BR";GameConfig[4].Acronym="BR";GameConfig[4].GameName="Bombing Run";GameConfig[4].Mutators="";GameConfig[4].Options="";
//        // bonus pack game types
//		GameConfig[5].GameClass="BonusPack.xMutantGame";GameConfig[5].Prefix="DM";GameConfig[5].Acronym="MUT";GameConfig[5].GameName="Mutant";GameConfig[5].Mutators="";GameConfig[5].Options="";
//		GameConfig[6].GameClass="BonusPack.xLastManStandingGame";GameConfig[6].Prefix="DM";GameConfig[6].Acronym="LMS";GameConfig[6].GameName="Last Man Standing";GameConfig[6].Mutators="";GameConfig[6].Options="";
//		GameConfig[7].GameClass="SkaarjPack.Invasion";GameConfig[7].Prefix="DM";GameConfig[7].Acronym="INV";GameConfig[7].GameName="Invasion";GameConfig[7].Mutators="";GameConfig[7].Options="";
//		// UT2004 game types
//		GameConfig[8].GameClass="Onslaught.ONSOnslaughtGame";GameConfig[8].Prefix="ONS";GameConfig[8].Acronym="ONS";GameConfig[8].GameName="Onslaught";GameConfig[8].Mutators="";GameConfig[8].Options="";
//		GameConfig[9].GameClass="UT2k4Assault.ASGameInfo";GameConfig[9].Prefix="AS";GameConfig[9].Acronym="AS";GameConfig[9].GameName="Assault";GameConfig[9].Mutators="";GameConfig[9].Options="";
	}
	MapCount = 0;

	for(i=0;i < GameConfig.Length;i++)
		if(GameConfig[i].GameClass != "")
			log(GameConfig[i].GameName,'MapVote');

	log("MapListLoaderType = " $ MapListLoaderType,'MapVote');

	MapListLoaderClass = class<MapListLoader>(DynamicLoadObject(MapListLoaderType, class'Class'));
	Loader = spawn(MapListLoaderClass);
	if(Loader == None) // Failed to spawn MapListLoader
		Loader = spawn(class'DefaultMapListLoader'); // default
	Loader.LoadMapList(self);

	log(MapCount $ " maps loaded.",'MapVote');

	History.Save();

	if(bEliminationMode)
	{
		// Count the Remaining Enabled maps
		EnabledMapCount = 0;
		for(i=0;i<MapCount;i++)
		{
			if(MapList[i].bEnabled)
				EnabledMapCount++;
		}
		if(EnabledMapCount < MinMapCount || EnabledMapCount == 0)
		{
			log("Elimination Mode Reset/Reload.",'MapVote');
			RepeatLimit = 0;
			MapList.Length = 0;
			MapCount = 0;
			SaveConfig();
			Loader.LoadMapList(self);
		}
	}
	Loader.Destroy();
}
//------------------------------------------------------------------------------------------------
function AddMap(string MapName, string Mutators, string GameOptions) // called from the MapListLoader
{
	local MapHistoryInfo MapInfo;
	local bool bUpdate;
	local int i;

	for(i=0; i < MapList.Length; i++)  // dont add duplicate map names
		if(MapName ~= MapList[i].MapName)
			return;

	MapInfo = History.GetMapHistory(MapName);

	MapList.Length = MapCount + 1;
	MapList[MapCount].MapName = MapName;
	MapList[MapCount].PlayCount = MapInfo.P;
	MapList[MapCount].Sequence = MapInfo.S;
	if(MapInfo.S <= RepeatLimit && MapInfo.S != 0)
		MapList[MapCount].bEnabled = false; // dont allow players to vote for this one
	else
		MapList[MapCount].bEnabled = true;
	MapCount++;

	if(Mutators != "" && Mutators != MapInfo.U)
	{
		MapInfo.U = Mutators;
		bUpdate = True;
	}

	if(GameOptions != "" && GameOptions != MapInfo.G)
	{
		MapInfo.G = GameOptions;
		bUpdate = True;
	}

	if(MapInfo.M == "") // if map not found in MapVoteHistory then add it
	{
		MapInfo.M = MapName;
		bUpdate = True;
	}

	if(bUpdate)
		History.AddMap(MapInfo);
}
//------------------------------------------------------------------------------------------------
function int GetMVRIIndex(PlayerController Player)
{
	local int i;

	for(i=0;i < MVRI.Length;i++)
		if(MVRI[i] != None && MVRI[i].PlayerOwner == Player)
			return i;
	return -1;
}
//------------------------------------------------------------------------------------------------
function SubmitMapVote(int MapIndex, int GameIndex, Actor Voter)
{
	local int Index, VoteCount, PrevMapVote, PrevGameVote;
	local MapHistoryInfo MapInfo;

	if(bLevelSwitchPending)
		return;

	Index = GetMVRIIndex(PlayerController(Voter));

	// check for invalid vote from unpatch players
	if( !IsValidVote(MapIndex, GameIndex) )
		return;

    //if _RO_
	if(PlayerController(Voter).PlayerReplicationInfo.bAdmin || PlayerController(Voter).PlayerReplicationInfo.bSilentAdmin)  // Administrator Vote
	//else
	//if(PlayerController(Voter).PlayerReplicationInfo.bAdmin)
	//end _RO_
	{
		TextMessage = lmsgAdminMapChange;
		TextMessage = Repl(TextMessage, "%mapname%", MapList[MapIndex].MapName $ "(" $ GameConfig[GameIndex].Acronym $ ")");
		Level.Game.Broadcast(self,TextMessage);

		log("Admin has forced map switch to " $ MapList[MapIndex].MapName $ "(" $ GameConfig[GameIndex].Acronym $ ")",'MapVote');

		CloseAllVoteWindows();

		bLevelSwitchPending = true;

		MapInfo = History.PlayMap(MapList[MapIndex].MapName);

		ServerTravelString = SetupGameMap(MapList[MapIndex], GameIndex, MapInfo);
		log("ServerTravelString = " $ ServerTravelString ,'MapVoteDebug');

		Level.ServerTravel(ServerTravelString, false);    // change the map

		settimer(1,true);
		return;
	}

	if( PlayerController(Voter).PlayerReplicationInfo.bOnlySpectator )
	{
		// Spectators cant vote
		PlayerController(Voter).ClientMessage(lmsgSpectatorsCantVote);
		return;
	}

	// check for invalid map, invalid gametype, player isnt revoting same as previous vote, and map choosen isnt disabled
	if(MapIndex < 0 ||
		MapIndex >= MapCount ||
		GameIndex >= GameConfig.Length ||
		(MVRI[Index].GameVote == GameIndex && MVRI[Index].MapVote == MapIndex) ||
		!MapList[MapIndex].bEnabled)
		return;

	log("___" $ Index $ " - " $ PlayerController(Voter).PlayerReplicationInfo.PlayerName $ " voted for " $ MapList[MapIndex].MapName $ "(" $ GameConfig[GameIndex].Acronym $ ")",'MapVote');

	PrevMapVote = MVRI[Index].MapVote;
	PrevGameVote = MVRI[Index].GameVote;
	MVRI[Index].MapVote = MapIndex;
	MVRI[Index].GameVote = GameIndex;

	if(bAccumulationMode)
	{
		if(bScoreMode)
		{
			VoteCount = GetAccVote(PlayerController(Voter)) + int(GetPlayerScore(PlayerController(Voter)));
			TextMessage = lmsgMapVotedForWithCount;
			TextMessage = repl(TextMessage, "%playername%", PlayerController(Voter).PlayerReplicationInfo.PlayerName );
			TextMessage = repl(TextMessage, "%votecount%", string(VoteCount) );
			TextMessage = repl(TextMessage, "%mapname%", MapList[MapIndex].MapName $ "(" $ GameConfig[GameIndex].Acronym $ ")" );
			Level.Game.Broadcast(self,TextMessage);
		}
		else
		{
			VoteCount = GetAccVote(PlayerController(Voter)) + 1;
			TextMessage = lmsgMapVotedForWithCount;
			TextMessage = repl(TextMessage, "%playername%", PlayerController(Voter).PlayerReplicationInfo.PlayerName );
			TextMessage = repl(TextMessage, "%votecount%", string(VoteCount) );
			TextMessage = repl(TextMessage, "%mapname%", MapList[MapIndex].MapName $ "(" $ GameConfig[GameIndex].Acronym $ ")" );
			Level.Game.Broadcast(self,TextMessage);
		}
	}
	else
	{
		if(bScoreMode)
		{
			VoteCount = int(GetPlayerScore(PlayerController(Voter)));
			TextMessage = lmsgMapVotedForWithCount;
			TextMessage = repl(TextMessage, "%playername%", PlayerController(Voter).PlayerReplicationInfo.PlayerName );
			TextMessage = repl(TextMessage, "%votecount%", string(VoteCount) );
			TextMessage = repl(TextMessage, "%mapname%", MapList[MapIndex].MapName $ "(" $ GameConfig[GameIndex].Acronym $ ")" );
			Level.Game.Broadcast(self,TextMessage);
		}
		else
		{
			VoteCount =  1;
			TextMessage = lmsgMapVotedFor;
			TextMessage = repl(TextMessage, "%playername%", PlayerController(Voter).PlayerReplicationInfo.PlayerName );
			TextMessage = repl(TextMessage, "%mapname%", MapList[MapIndex].MapName $ "(" $ GameConfig[GameIndex].Acronym $ ")" );
			Level.Game.Broadcast(self,TextMessage);
		}
	}
	UpdateVoteCount(MapIndex, GameIndex, VoteCount);
	if( PrevMapVote > -1 && PrevGameVote > -1 )
		UpdateVoteCount(PrevMapVote, PrevGameVote, -MVRI[Index].VoteCount); // undo previous vote
	MVRI[Index].VoteCount = VoteCount;
	TallyVotes(false);
}
//------------------------------------------------------------------------------------------------
function bool IsValidVote(int MapIndex, int GameIndex)
{
	local int i;
	local array<string> PrefixList;

	// check if the maps prefix is one listed for the gametype
	Split(GameConfig[GameIndex].Prefix, ",", PrefixList);

	for( i=0; i<PreFixList.Length; i++)
		if( left(MapList[MapIndex].MapName, len(PrefixList[i])) ~= PrefixList[i] )
			return true;

	return false;
}
//------------------------------------------------------------------------------------------------
function UpdateVoteCount(int MapIndex, int GameIndex, int VoteCount)
{
	local int x,i;
	local bool bFound;
	local MapVoteScore MVCData;

	// search for matching record
	for( x=0; x<MapVoteCount.Length; x++ )
	{
		if( MapVoteCount[x].GameConfigIndex == GameIndex &&
		    MapVoteCount[x].MapIndex == MapIndex)
		{
			MapVoteCount[x].VoteCount += VoteCount;
			MVCData = MapVoteCount[x];
			if(MapVoteCount[x].VoteCount <= 0)
				MapVoteCount.Remove( x, 1);
			bFound = true;
			break;
		}
	}

	if( !bFound && VoteCount > 0) // add new if not found
	{
		x = MapVoteCount.Length;
		MapVoteCount.Insert(x,1);
		MapVoteCount[x].GameConfigIndex = GameIndex;
		MapVoteCount[x].MapIndex = MapIndex;
		MapVoteCount[x].VoteCount = VoteCount;
		MVCData = MapVoteCount[x];
	}

	// send update to all players
	for( i=0; i<MVRI.Length; i++ )
	{
		if( MVRI[i] != none && MVRI[i].PlayerOwner != none )
			MVRI[i].ReceiveMapVoteCount(MVCData, False);
	}
}
//------------------------------------------------------------------------------------------------
function TallyVotes(bool bForceMapSwitch)
{
	local int        index,x,y,topmap,r,mapidx,gameidx;
	local array<int> VoteCount;
	local array<int> Ranking;
	local int        PlayersThatVoted;
	local int        TieCount;
	local string     CurrentMap;
	local int        Votes;
	local MapHistoryInfo MapInfo;

	if(bLevelSwitchPending)
		return;

	PlayersThatVoted = 0;
	VoteCount.Length = GameConfig.Length * MapCount;
	// note: VoteCount array is a 2 dimension array VoteCount[GameConfigIndex, MapIndex]
	//       Maps ->
	//       0 1 2 3 4 5 6 7 8
	// G     - - - - - - - - -
	// a  0 |0 0 0 0 0 0 0 2 0
	// m  1 |0 0 0 2 0 0 0 0 0
	// e  2 |0 6 0 0 0 5 0 0 0
	// s  3 |0 0 0 3 0 0 0 0 0

	for(x=0;x < MVRI.Length;x++) // for each player
	{
		if(MVRI[x] != none && MVRI[x].MapVote > -1 && MVRI[x].GameVote > -1) // if this player has voted
		{
			PlayersThatVoted++;

			if(bScoreMode)
			{
				if(bAccumulationMode)
					Votes = GetAccVote(MVRI[x].PlayerOwner) + int(GetPlayerScore(MVRI[x].PlayerOwner));
				else
					Votes = int(GetPlayerScore(MVRI[x].PlayerOwner));
			}
			else
			{  // Not Score Mode == Majority (one vote per player)
				if(bAccumulationMode)
					Votes = GetAccVote(MVRI[x].PlayerOwner) + 1;
				else
					Votes = 1;
			}
			VoteCount[MVRI[x].GameVote * MapCount + MVRI[x].MapVote] = VoteCount[MVRI[x].GameVote * MapCount + MVRI[x].MapVote] + Votes;

			if(!bScoreMode)
			{
				// If more then half the players voted for the same map as this player then force a winner
				if(Level.Game.NumPlayers > 2 && float(VoteCount[MVRI[x].GameVote * MapCount + MVRI[x].MapVote]) / float(Level.Game.NumPlayers) > 0.5 && Level.Game.bGameEnded)
					bForceMapSwitch = true;
			}
		}
	}
	log("___Voted - " $ PlayersThatVoted,'MapVoteDebug');

	if(Level.Game.NumPlayers > 2 && !Level.Game.bGameEnded && !bMidGameVote && (float(PlayersThatVoted) / float(Level.Game.NumPlayers)) * 100 >= MidGameVotePercent) // Mid game vote initiated
	{
		Level.Game.Broadcast(self,lmsgMidGameVote);
		bMidGameVote = true;
		// Start voting count-down timer
		TimeLeft = VoteTimeLimit;
		ScoreBoardTime = 1;
		settimer(1,true);
	}

	index = 0;
	for(x=0;x < VoteCount.Length;x++) // for each map
	{
		if(VoteCount[x] > 0)
		{
			Ranking.Insert(index,1);
			Ranking[index++] = x; // copy all vote indexes to the ranking list if someone has voted for it.
		}
	}

	if(PlayersThatVoted > 1)
	{
		// bubble sort ranking list by vote count
		for(x=0; x<index-1; x++)
		{
			for(y=x+1; y<index; y++)
			{
				if(VoteCount[Ranking[x]] < VoteCount[Ranking[y]])
				{
				topmap = Ranking[x];
				Ranking[x] = Ranking[y];
				Ranking[y] = topmap;
				}
			}
		}
	}
	else
	{
		if(PlayersThatVoted == 0)
		{
			GetDefaultMap(mapidx, gameidx);
			topmap = gameidx * MapCount + mapidx;
		}
		else
			topmap = Ranking[0];  // only one player voted
	}

	//Check for a tie
	if(PlayersThatVoted > 1) // need more than one player vote for a tie
	{
		if(index > 1 && VoteCount[Ranking[0]] == VoteCount[Ranking[1]] && VoteCount[Ranking[0]] != 0)
		{
			TieCount = 1;
			for(x=1; x<index; x++)
			{
				if(VoteCount[Ranking[0]] == VoteCount[Ranking[x]])
				TieCount++;
			}
			//reminder ---> int Rand( int Max ); Returns a random number from 0 to Max-1.
			topmap = Ranking[Rand(TieCount)];

			// Don't allow same map to be choosen
			CurrentMap = GetURLMap();

			r = 0;
			while(MapList[topmap - (topmap/MapCount) * MapCount].MapName ~= CurrentMap)
			{
				topmap = Ranking[Rand(TieCount)];
				if(r++>100)
					break;  // just incase
			}
		}
		else
		{
			topmap = Ranking[0];
		}
	}

	// if everyone has voted go ahead and change map
	if(bForceMapSwitch || (Level.Game.NumPlayers == PlayersThatVoted && Level.Game.NumPlayers > 0) )
	{
		if(MapList[topmap - topmap/MapCount * MapCount].MapName == "")
			return;

		TextMessage = lmsgMapWon;
		TextMessage = repl(TextMessage,"%mapname%",MapList[topmap - topmap/MapCount * MapCount].MapName $ "(" $ GameConfig[topmap/MapCount].Acronym $ ")");
		Level.Game.Broadcast(self,TextMessage);

		CloseAllVoteWindows();

		MapInfo = History.PlayMap(MapList[topmap - topmap/MapCount * MapCount].MapName);

		ServerTravelString = SetupGameMap(MapList[topmap - topmap/MapCount * MapCount], topmap/MapCount, MapInfo);
		log("ServerTravelString = " $ ServerTravelString ,'MapVoteDebug');

		History.Save();

		if(bEliminationMode)
			RepeatLimit++;

		if(bAccumulationMode)
			SaveAccVotes(topmap - topmap/MapCount * MapCount, topmap/MapCount);

		//if(bEliminationMode || bAccumulationMode)
		CurrentGameConfig = topmap/MapCount;
		if( !bAutoDetectMode )
			SaveConfig();

		bLevelSwitchPending = true;
		settimer(Level.TimeDilation,true);  // timer() will monitor the server-travel and detect a failure

		Level.ServerTravel(ServerTravelString, false);    // change the map
	}
}
//------------------------------------------------------------------------------------------------
event timer()
{
	local int mapidx,gameidx,i;
	local MapHistoryInfo MapInfo;

	if(bLevelSwitchPending)
	{
		if( Level.NextURL == "" )
		{
			if(Level.NextSwitchCountdown < 0)  // if negative then level switch failed
			{
				Log("___Map change Failed, bad or missing map file.",'MapVote');
				GetDefaultMap(mapidx, gameidx);
				MapInfo = History.PlayMap(MapList[mapidx].MapName);
				ServerTravelString = SetupGameMap(MapList[mapidx], gameidx, MapInfo);
				log("ServerTravelString = " $ ServerTravelString ,'MapVoteDebug');
				History.Save();
				Level.ServerTravel(ServerTravelString, false);    // change the map
			}
		}
		return;
	}

	if(ScoreBoardTime > -1)
	{
		if(ScoreBoardTime == 0)
			OpenAllVoteWindows();
		ScoreBoardTime--;
		return;
	}
	TimeLeft--;

	if(TimeLeft == 60 || TimeLeft == 30 || TimeLeft == 20 || TimeLeft == 10)  // play announcer count down voice
	{
		//log("___CountDown " $ TimeLeft,'MapVoteDebug');
		//BroadcastLocalizedMessage(class'MapVoteCountDownMsg', TimeLeft);
		for( i=0; i<MVRI.Length; i++)
			if(MVRI[i] != none && MVRI[i].PlayerOwner != none )
				MVRI[i].PlayCountDown(TimeLeft);
	}

	//if(TimeLeft < 11 && TimeLeft > 0 )  // play announcer voice Count Down
	//	BroadcastLocalizedMessage(class'VotingTimerMessage', TimeLeft);

	if(TimeLeft == 0)  // force level switch if time limit is up
		TallyVotes(true);   // if no-one has voted a random map will be choosen
}
//------------------------------------------------------------------------------------------------
function CloseAllVoteWindows()
{
	local int i;

	for(i=0; i < MVRI.Length;i++)
	{
		if(MVRI[i] != none)
		{
			//log("___Closing window " $ i,'MapVoteDebug');
			MVRI[i].CloseWindow();
		}
	}
}
//------------------------------------------------------------------------------------------------
function OpenAllVoteWindows()
{
	local int i;

	for(i=0; i < MVRI.Length;i++)
	{
		if(MVRI[i] != none)
		{
			//log("Opening window " $ i,'MapVoteDebug');
			MVRI[i].OpenWindow();
		}
	}
}
//------------------------------------------------------------------------------------------------
function string SetupGameMap(MapVoteMapList MapInfo, int GameIndex, MapHistoryInfo MapHistoryInfo)
{
	local string ReturnString;
	local string MutatorString;
	local string OptionString;
	local array<string> MapsInRotation;
	local int i;

	// Add Per-GameType Mutators
	if(GameConfig[GameIndex].Mutators != "")
		MutatorString = MutatorString $ GameConfig[GameIndex].Mutators;

	// Add Per-Map Mutators
	if(MapHistoryInfo.U != "")
		MutatorString = MutatorString $ "," $ MapHistoryInfo.U;

	// Add Per-GameType Game Options
	if(GameConfig[GameIndex].Options != "")
		OptionString = OptionString $ Repl(Repl(GameConfig[GameIndex].Options,",","?")," ","");

	// Add Per-Map Game Options
	if(MapHistoryInfo.G != "")
		OptionString = OptionString $ "?" $ MapHistoryInfo.G;

	//if _RO_
	// Remove the .rom off of the map name, if it exists
	if ( Right(MapInfo.MapName, 4) == ".rom" )
	    ReturnString = Left(MapInfo.MapName, Len(MapInfo.MapName) - 4);
	else
	    ReturnString = MapInfo.MapName;

	MapsInRotation = Level.Game.MaplistHandler.GetCurrentMapRotation();
	for ( i = 0; i < MapsInRotation.Length; i++ )
	{
	    if ( InStr(MapsInRotation[i], ReturnString) != -1 )
	    {
            ReturnString = MapsInRotation[i];
	        break;
        }
    }
	//else
	//ReturnString = MapInfo.MapName; //$ ".ut2";
	//end _RO_

    ReturnString = ReturnString $ "?Game=" $ GameConfig[GameIndex].GameClass;

	if(MutatorString != "")
		ReturnString = ReturnString $ "?Mutator=" $ MutatorString;

	if(OptionString != "")
		ReturnString = ReturnString $ "?" $ OptionString;

	return ReturnString;
}
//------------------------------------------------------------------------------------------------
function bool HandleRestartGame()
{
	local int i;
	// Called by GameInfo.RestartGame at End Of Game
	// Return False to prevent traveling to next map
    log("____HandleRestartGame", 'MapVoteDebug');

	// disable voting in single player mode
    if( Level.NetMode == NM_StandAlone )
		return true;

	if( bMatchSetup ) // check if any match setup in progress
	{
		for( i=0; i<MVRI.Length; i++)
			if( MVRI[i] != none && MVRI[i].bMatchSetupPermitted )
				return false; // don't contine to next map
	}

	if(bMapVote && bAutoOpen)
	{
		//check if the game is an assault mod for UT2k3Assault
		if(string(Level.Game.Class) ~= "RoARAssault.xAssault")
			if(int(Level.Game.GameReplicationInfo.GetPropertyText("Part")) != 2)
				return true;

		// Start voting count-down timer
		TimeLeft = VoteTimeLimit;
		ScoreBoardTime = ScoreBoardDelay;
		settimer(1,true);
  		return false;
	}
	return true;
}
//------------------------------------------------------------------------------------------------
function MapVoteMapList GetMapList(int p_MapIndex)
{
	return MapList[p_MapIndex];
}
//------------------------------------------------------------------------------------------------
function MapVoteGameConfigLite GetGameConfig(int p_GameConfigIndex)
{
	local MapVoteGameConfigLite GameConfigItem;

	GameConfigItem.GameClass = GameConfig[p_GameConfigIndex].GameClass;
	GameConfigItem.Prefix = GameConfig[p_GameConfigIndex].Prefix;
	GameConfigItem.GameName = GameConfig[p_GameConfigIndex].GameName;

	return GameConfigItem;
}
//------------------------------------------------------------------------------------------------
function float GetPlayerScore(PlayerController Player)
{
	local float PlayerScore;

	if( !Level.Game.bGameEnded )
		PlayerScore = 1;
	else
		PlayerScore = Player.PlayerReplicationInfo.Score;

	if(PlayerScore < 1)
		PlayerScore = 1;

	return PlayerScore;
}
//------------------------------------------------------------------------------------------------
function int GetAccVote(PlayerController Player)
{
	local int x,PlayerAccVotes;
	local string PlayerName;

	PlayerName = Player.PlayerReplicationInfo.PlayerName;

	if(PlayerName == "")
		return(0);

	if(AccInfo.Length > 0)
	{
		// Find the players name in the saved accumulated votes
		for(x=0;x<AccInfo.Length;x++)
		{
			if(AccInfo[x].Name == PlayerName)
			{
				PlayerAccVotes = AccInfo[x].VoteCount;
				break;
			}
		}
	}
	else
		PlayerAccVotes = 0;
	return(PlayerAccVotes);
}
//------------------------------------------------------------------------------------------------
function SaveAccVotes(int WinningMapIndex, int WinningGameIndex)
{
	local Controller C;
	local PlayerController P;
	local int x, Index;
	local bool bFound;

	if(AccInfo.Length > 0)
	{
		for(x=0;x<AccInfo.Length;x++)
		{
			if(AccInfo[x].Name != "")
			{
				bFound = false;
				for(C=Level.ControllerList;C!=None;C=C.NextController)
				{
					P = PlayerController(C);
					if(C.bIsPlayer && P != None && AccInfo[x].Name == P.PlayerReplicationInfo.PlayerName)
					{
						Index = GetMVRIIndex(P);
						if(MVRI[Index] != None && MVRI[Index].MapVote != WinningMapIndex && MVRI[Index].GameVote != WinningGameIndex)
						{
							bFound = true;
							if(bScoreMode)
								AccInfo[x].VoteCount = AccInfo[x].VoteCount + int(GetPlayerScore(P));
							else
								AccInfo[x].VoteCount++;
						}
						break;
					}
				}
				if(!bFound)  // If this player is not here anymore remove or voted for winning map then remove
				{
					AccInfo[x].Name = "";
					AccInfo[x].VoteCount = 0;
				}
			}
		}

		// Remove blank entries
		for(x=AccInfo.Length-1;x>=0;x--)
		{
			if(AccInfo[x].Name == "")
			{
				//log("Removeing " $ AccInfo[x].Name);
				AccInfo.Remove(x,1);
			}
		}
	}

	// Add players who have not voted
	for(C=Level.ControllerList;C!=None;C=C.NextController)
	{
		P = PlayerController(C);
		if(C.bIsPlayer && P != None)
		{
			bFound = false;
			if(AccInfo.Length > 0)
			{
				for(x=0;x<AccInfo.Length;x++)
				{
					if(AccInfo[x].Name == P.PlayerReplicationInfo.PlayerName)
					{
						bFound = true;
						break;
					}
				}
			}
			Index = GetMVRIIndex(P);
			if(!bFound && MVRI[Index].MapVote != WinningMapIndex && MVRI[Index].GameVote != WinningGameIndex)
			{
				// Not found, so add it
				AccInfo.Insert(AccInfo.Length,1);
				AccInfo[AccInfo.Length - 1].Name = P.PlayerReplicationInfo.PlayerName;
				if(bScoreMode)
					AccInfo[AccInfo.Length - 1].VoteCount = int(GetPlayerScore(P));
				else
					AccInfo[AccInfo.Length - 1].VoteCount = 1;
			}
		}
	}
}
//------------------------------------------------------------------------------------------------
function GetDefaultMap(out int mapidx, out int gameidx)
{
	local int i,x,y,r,p,GCIdx;
	local array<string> PrefixList;
	local bool bLoop;

	if(MapCount <= 0)
		return;

	// set the default gametype
	if(bDefaultToCurrentGameType)
		GCIdx = CurrentGameConfig;
	else
		GCIdx = DefaultGameConfig;

	// Parse Prefix list for default game type
	PrefixList.Length = 0;
	p = Split(GameConfig[GCIdx].Prefix, ",", PrefixList);
	if(PrefixList.Length == 0)
	{
		gameidx = GCIdx;
		mapidx = 0;
		return;
	}

	// choose a map at random, check if it is enabled and the prefix is in the prefix list
	r=0;
	bLoop = True;
	while(bLoop)
	{
		i = Rand(MapCount);
		if( MapList[i].bEnabled )
		{
			for(x=0; x < PrefixList.Length; x++)
			{
				if( left(MapList[i].MapName, len(PrefixList[x])) ~= PrefixList[x] )
				{
					bLoop = false;
					break;
				}
			}
		}

		if(bLoop && r++ > 100)
		{
			// give up after 100 unsuccessful attempts.
			// find the first map that matches up to default gametype
            for(i=0;i<=MapCount;i++)
			{
				if( MapList[i].bEnabled )
				{
					for(x=0; x < PrefixList.Length; x++)
					{
						if( left(MapList[i].MapName, len(PrefixList[x])) ~= PrefixList[x] )
						{
							// ding ding ding, found one
							bLoop = false;
							break;
						}
					}
				}
			}

			if(bLoop) // still didnt find any, then find the first enabled map and find its gameconfig
			{
				for(i=0;i<=MapCount;i++)
				{
					if( MapList[i].bEnabled )
					{
						// find prefix in GameConfigs
						for(y=0;y<GameConfig.Length;y++)
						{
							// Parse Prefix list for game type
							PrefixList.Length = 0;
							p = Split(GameConfig[y].Prefix, ",", PrefixList);
							if(PrefixList.Length > 0)
							{
								for(x=0; x < PrefixList.Length; x++)
								{
									if( left(MapList[i].MapName, len(PrefixList[x])) ~= PrefixList[x] )
									{
										// ding ding ding, found one
										GCIdx = y;
										bLoop = false;
										break;
									}
								}
							}
							if(!bLoop)
								break;
						}
						break;
					}
				}
			}
			break;
		}
	}
	gameidx = GCIdx;
	mapidx = i;
	log("Default Map Choosen = " $ MapList[mapidx].MapName $ "(" $ GameConfig[gameidx].Acronym $ ")",'MapVoteDebug');
}
//================================================================================================
//                                    Kick Voting
//================================================================================================
function SubmitKickVote(int PlayerID, Actor Voter)
{
	local int VoterID, VictumID, i, PreviousVote;
	local bool bFound;
	local string PlayerName;

	log("SubmitKickVote " $ PlayerID, 'MapVoteDebug');

	if(bLevelSwitchPending || !bKickVote)
		return;

	VoterID = GetMVRIIndex(PlayerController(Voter));

	// Find Player
	bFound = false;
	for(i=0;i < MVRI.Length;i++)
	{
		if(MVRI[i] != none && MVRI[i].PlayerOwner.PlayerReplicationInfo.PlayerID == PlayerID)
		{
			bFound = true;
			VictumID = i;
			PlayerName = MVRI[i].PlayerOwner.PlayerReplicationInfo.PlayerName;
			break;
		}
	}
	if(!bFound)
		return;

	if( MVRI[VoterID].KickVote == VictumID ) // if vote is for same player stop
		return;

    //if _RO_
    if( PlayerController(Voter).PlayerReplicationInfo.bAdmin || PlayerController(Voter).PlayerReplicationInfo.bSilentAdmin )  // Administrator Vote
    //else
	//if( PlayerController(Voter).PlayerReplicationInfo.bAdmin )  // Administrator Vote
    //end _RO_
	{
		log("___Admin " $ PlayerController(Voter).PlayerReplicationInfo.PlayerName $ " kicked " $ PlayerName,'MapVote');
		KickPlayer(VictumID);
		return;
	}

	if( PlayerController(Voter).PlayerReplicationInfo.bOnlySpectator )
	{
		// Spectators cant vote
		PlayerController(Voter).ClientMessage(lmsgSpectatorsCantVote);
		return;
	}

	// cant kick admin
	//if _RO_
	if( MVRI[VictumID].PlayerOwner.PlayerReplicationInfo.bAdmin || MVRI[VictumID].PlayerOwner.PlayerReplicationInfo.bSilentAdmin ||
        NetConnection(MVRI[VictumID].PlayerOwner.Player) == None)
	//else
	//if(MVRI[VictumID].PlayerOwner.PlayerReplicationInfo.bAdmin || NetConnection(MVRI[VictumID].PlayerOwner.Player) == None)
	//end _RO_
	{
		TextMessage = lmsgKickVoteAdmin;
		TextMessage = repl(TextMessage,"%playername%",PlayerController(Voter).PlayerReplicationInfo.PlayerName);
		Level.Game.Broadcast(self,TextMessage);
		return;
	}

	log("___" $ PlayerController(Voter).PlayerReplicationInfo.PlayerName $ " placed a kick vote against " $ PlayerName,'MapVote');
	if(bAnonymousKicking)
	{
		TextMessage = lmsgAnonymousKickVote;
		TextMessage = repl(TextMessage,"%playername%",PlayerName);
		Level.Game.Broadcast(self,TextMessage);
	}
	else
	{
		TextMessage = lmsgKickVote;
		TextMessage = repl(TextMessage,"%playername1%",PlayerController(Voter).PlayerReplicationInfo.PlayerName);
		TextMessage = repl(TextMessage,"%playername2%",PlayerName);
		Level.Game.Broadcast(self,TextMessage);
	}
	PreviousVote = MVRI[VoterID].KickVote;
	MVRI[VoterID].KickVote = VictumID;

  	UpdateKickVoteCount(MVRI[VictumID].PlayerID, 1);
	if( PreviousVote > -1 )
		UpdateKickVoteCount(MVRI[PreviousVote].PlayerID, -1); // undo previous vote

	TallyKickVotes();
}
//------------------------------------------------------------------------------------------------
function UpdateKickVoteCount(int PlayerID, int VoteCountDelta)
{
	local int x,i;
	local bool bFound;

	if( PlayerID < 0 )
		return;

	// search for matching record
	for( x=0; x<KickVoteCount.Length; x++ )
	{
		if( KickVoteCount[x].PlayerID == PlayerID)
		{
			if( VoteCountDelta == 0 )
				KickVoteCount[x].KickVoteCount = 0;
			else
				KickVoteCount[x].KickVoteCount += VoteCountDelta;

			if( KickVoteCount[x].KickVoteCount < 0 )
			   KickVoteCount[x].KickVoteCount = 0;
			bFound = true;
			break;
		}
	}

	if( !bFound && VoteCountDelta > 0) // add new if not found
	{
		x = KickVoteCount.Length;
		KickVoteCount.Insert(x,1);
		KickVoteCount[x].PlayerID = PlayerID;
		KickVoteCount[x].KickVoteCount = 1;
	}

	// send update to all players
	for( i=0; i<MVRI.Length; i++ )
	{
		if( MVRI[i] != none && MVRI[i].PlayerOwner != none && x < KickVoteCount.Length )
			MVRI[i].ReceiveKickVoteCount(KickVoteCount[x], False);
	}
}
//------------------------------------------------------------------------------------------------
function TallyKickVotes()
{
	local int i,x,y,index,PlayersThatVoted,Lamer;
	local array<int> VoteCount;
	local array<int> Ranking;

	VoteCount.Length = MVRI.Length;

	// tally up the votes
	for(i=0;i < MVRI.Length;i++)
	{
		if(MVRI[i] != None && MVRI[i].KickVote != -1) // if this player has voted
		{
			PlayersThatVoted++;
			VoteCount[MVRI[i].KickVote]++; // increment the votecount for this player
		}
	}

	index = 0;
	for(i=0;i < VoteCount.Length;i++) // for each player
	{
		if(VoteCount[i] > 0)
		{
			Ranking.Insert(index,1);
			Ranking[index++] = i;
		}
	}

	if(PlayersThatVoted > 1)
	{
		// bubble sort ranking list by vote count
		for(x=0; x<index-1; x++)
		{
			for(y=x+1; y<index; y++)
			{
				if(VoteCount[Ranking[x]] < VoteCount[Ranking[y]])
				{
				Lamer = Ranking[x];
				Ranking[x] = Ranking[y];
				Ranking[y] = Lamer;
				}
			}
		}
		Lamer = Ranking[0];
	}

	// if more than KickPercent of the players voted to kick this player then kick
	// TODO: For TESTING only , remove
	//if(Level.Game.NumPlayers > 2 && ((float(VoteCount[Lamer])/float(Level.Game.NumPlayers))*100 >= KickPercent))
	if(((float(VoteCount[Lamer])/float(Level.Game.NumPlayers))*100 >= KickPercent))
	{
		KickPlayer(Lamer);
		return;
	}
}
//------------------------------------------------------------------------------------------------
function KickPlayer(int PlayerIndex)
{
	local int i;

	if( MVRI[PlayerIndex] == none || MVRI[PlayerIndex].PlayerOwner == none )
		return;

	TextMessage = "%playername% has been kicked.";
	TextMessage = repl(TextMessage,"%playername%",MVRI[PlayerIndex].PlayerOwner.PlayerReplicationInfo.PlayerName);
	Level.Game.Broadcast(self,TextMessage);

	if(bKickVote)
	{
		// Reset votes
		for(i=0;i < MVRI.Length;i++)
		{
			if(MVRI[i] != None && MVRI[i].KickVote != -1)
				MVRI[i].KickVote = -1;
		}
	}

	//close his/her voting window if open
	if(MVRI[PlayerIndex] != None)
		MVRI[PlayerIndex].CloseWindow();

	log("___" $ MVRI[PlayerIndex].PlayerOwner.PlayerReplicationInfo.PlayerName $ " has been kicked.",'MapVote');
	Level.Game.AccessControl.BanPlayer(MVRI[PlayerIndex].PlayerOwner, True); // session type ban
}
//================================================================================================
//                                    MatchSetup
//================================================================================================
function bool MatchSetupLogin(string UserID, string Password, Actor Requestor, out int SecLevel)
{
	local xAdminUser AdminUser;

	if( bMatchSetup && PlayerController(Requestor) != none )
	{
		if( UserID ~= "Admin" && PlayerController(Requestor).PlayerReplicationInfo.bAdmin )
		{
			SecLevel = 255;
			return True; // this user is already logged in as an administrator
		}

		if( Level.Game.AccessControl.AdminLogin( PlayerController(Requestor), UserID, Password) )
		{
			// Xm = MatchSetup Priv
			if( Level.Game.AccessControl.CanPerform(PlayerController(Requestor), "Xm") )
			{
				Log(UserID $ " has logged in to MatchSetup.");
				AdminUser = Level.Game.AccessControl.GetUser(UserID);
				if( AdminUser != none )
					SecLevel = AdminUser.MaxSecLevel();
				else
					SecLevel = 0;
				// hack for default AccessControl setup
				if( SecLevel == 0 && PlayerController(Requestor).PlayerReplicationInfo.bAdmin )
					SecLevel = 255;

				Log("SecLevel = " $ SecLevel);
				return True;
			}
			else
			{
				log(UserID $ " doesnt have MatchSetup permissions.");
				PlayerController(Requestor).ClientMessage(lmsgMatchSetupPermission);
				Return False;
			}
		}
		else
		{
			Log(UserID $ " password was invalid.");
			PlayerController(Requestor).ClientMessage(lmsgInvalidPassword);
			return False;
		}
	}
}
//------------------------------------------------------------------------------------------------
function MatchSetupLogout(Actor Requestor)
{
	if( bMatchSetup && PlayerController(Requestor) != none )
		Level.Game.AccessControl.AdminLogout( PlayerController(Requestor) );
}
//================================================================================================
//                                    Configuration
//================================================================================================
static function FillPlayInfo(PlayInfo PlayInfo)
{
	// this sends configuration settings to ether the WebAdmin, Server Rules GUI,
	// or MatchSetup via the PlayInfo class.
	Super.FillPlayInfo(PlayInfo);

	PlayInfo.AddSetting(default.MapVoteGroup,"bMapVote",default.PropsDisplayText[0],0,1,"Check",,,True,False);
	PlayInfo.AddSetting(default.MapVoteGroup,"bAutoOpen",default.PropsDisplayText[1],0,1,"Check",,,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"ScoreBoardDelay",default.PropsDisplayText[2],0,1,"Text","3;0:60",,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"bScoreMode",default.PropsDisplayText[3],0,1,"Check",,,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"bAccumulationMode",default.PropsDisplayText[4],0,1,"Check",,,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"bEliminationMode",default.PropsDisplayText[5],0,1,"Check",,,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"MinMapCount",default.PropsDisplayText[6],0,1,"Text","4;1:9999",,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"RepeatLimit",default.PropsDisplayText[7],0,1,"Text","4;0:9999",,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"VoteTimeLimit",default.PropsDisplayText[8],0,1,"Text","3;10:300",,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"MidGameVotePercent",default.PropsDisplayText[9],0,1,"Text","3;1:100",,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"bDefaultToCurrentGameType",default.PropsDisplayText[10],0,1,"Check",,,True,True);
	PlayInfo.AddSetting(default.MapVoteGroup,"GameConfig",default.PropsDisplayText[15],0, 1,"Custom",";;"$default.GameConfigPage,,True,True);
	//PlayInfo.AddSetting(default.MapVoteGroup,"MapListLoaderType",default.PropsDisplayText[16],0, 1,"Custom",";;"$default.MapListConfigPage,,True,True);

	PlayInfo.AddSetting(default.KickVoteGroup,"bKickVote",default.PropsDisplayText[11],0,1,"Check",,,True,True);
	PlayInfo.AddSetting(default.KickVoteGroup,"KickPercent",default.PropsDisplayText[12],0,1,"Text","3;1:100",,True,True);
	PlayInfo.AddSetting(default.KickVoteGroup,"bAnonymousKicking",default.PropsDisplayText[13],0,1,"Check",,,True,True);

	PlayInfo.AddSetting(default.ServerGroup,"bMatchSetup",default.PropsDisplayText[14],0,1,"Check",,,True,True);

	class'DefaultMapListLoader'.static.FillPlayInfo(PlayInfo);
	PlayInfo.PopClass();
}

static event bool AcceptPlayInfoProperty(string PropertyName)
{
// if _RO_
//	if ( class'LevelInfo'.static.IsDemoBuild() )
//		return false;

	switch ( PropertyName )
	{
	case "bMapVote":
	case "bAutoOpen":
	case "ScoreBoardDelay":
	case "bScoreMode":
	case "bAccumulationMode":
	case "bEliminationMode":
	case "MinMapCount":
	case "RepeatLimit":
	case "VoteTimeLimit":
	case "MidGameVotePercent":
	case "bDefaultToCurrentGameType":
	case "GameConfig":
	case "MapListLoaderType":
		 return MAPVOTEALLOWED;

	case "bKickVote":
	case "KickPercent":
	case "bAnonymousKicking":
		return KICKVOTEALLOWED;

	case "bMatchSetup":
		return MATCHSETUPALLOWED;
	}

	return Super.AcceptPlayInfoProperty(PropertyName);
}

//------------------------------------------------------------------------------------------------
static event string GetDescriptionText(string PropName)
{
	switch (PropName)
	{
		case "bMapVote":					return default.PropDescription[0];
		case "bAutoOpen":					return default.PropDescription[1];
		case "ScoreBoardDelay":				return default.PropDescription[2];
		case "bScoreMode":					return default.PropDescription[3];
		case "bAccumulationMode":			return default.PropDescription[4];
		case "bEliminationMode":			return default.PropDescription[5];
		case "MinMapCount":					return default.PropDescription[6];
		case "RepeatLimit":					return default.PropDescription[7];
		case "VoteTimeLimit":				return default.PropDescription[8];
		case "MidGameVotePercent":			return default.PropDescription[9];
		case "bDefaultToCurrentGameType":	return default.PropDescription[10];
		case "bKickVote":					return default.PropDescription[11];
		case "KickPercent":					return default.PropDescription[12];
		case "bAnonymousKicking":			return default.PropDescription[13];
		case "bMatchSetup":			        return default.PropDescription[14];
		case "GameConfig":                  return default.PropDescription[15];
		case "MapListLoaderType":           return default.PropDescription[16];
	}
	return "";
}
//------------------------------------------------------------------------------------------------
function string GetConfigArrayData(string ConfigArrayName, int RowIndex, int ColumnIndex)
{
	switch( Caps(ConfigArrayName) )
	{
		case "GAMECONFIG":
			if( RowIndex > GameConfig.Length-1 || ColumnIndex > 5 )
				return "";

			switch( ColumnIndex )
			{
	        	case 0:
					return "GAMETYPE;50;" $ GameConfig[RowIndex].GameClass;
				case 1:
					return "TEXT;50;" $ GameConfig[RowIndex].Prefix;
				case 2:
					return "TEXT;20;" $ GameConfig[RowIndex].Acronym;
				case 3:
					return "TEXT;50;" $ GameConfig[RowIndex].GameName;
				case 4:
					return "MUTATORS;255;" $ GameConfig[RowIndex].Mutators;
				case 5:
					return "TEXT;255;" $ GameConfig[RowIndex].Options;
				default:
					return "";
			}
			break;

		case "MAPLIST":
			// TODO: *
			break;
	}
}
//------------------------------------------------------------------------------------------------
function string GetConfigArrayColumnTitle(string ConfigArrayName, int ColumnIndex)
{
	switch( Caps(ConfigArrayName) )
	{
		case "GAMECONFIG":
			if( ColumnIndex > 5 || ColumnIndex < 0 )
				return "";
   			return lmsgGameConfigColumnTitle[ColumnIndex];

		case "MAPLIST":
			// TODO: *
			break;
	}
}
//------------------------------------------------------------------------------------------------
function DeleteConfigArrayItem(string ConfigArrayName, int RowIndex)
{
	switch( Caps(ConfigArrayName) )
	{
		case "GAMECONFIG":
			if( RowIndex < 0 || RowIndex > GameConfig.Length-1 )
				return;
			GameConfig.Remove(RowIndex,1);
   			return;

		case "MAPLIST":
			// TODO: *
			break;
	}
}
//------------------------------------------------------------------------------------------------
function int AddConfigArrayItem(string ConfigArrayName)
{
	switch( Caps(ConfigArrayName) )
	{
		case "GAMECONFIG":
			GameConfig.Insert(GameConfig.Length,1);
			GameConfig[GameConfig.Length-1].GameClass = "XGame.xDeathMatch";
			GameConfig[GameConfig.Length-1].Prefix = "";
			GameConfig[GameConfig.Length-1].Acronym = "";
			GameConfig[GameConfig.Length-1].GameName = "new";
			GameConfig[GameConfig.Length-1].Mutators = "";
			GameConfig[GameConfig.Length-1].Options = "";
   			return GameConfig.Length-1;

		case "MAPLIST":
			// TODO: *
			break;
	}
}
//------------------------------------------------------------------------------------------------
function UpdateConfigArrayItem(string ConfigArrayName, int RowIndex, int ColumnIndex, string NewValue)
{
	switch( Caps(ConfigArrayName) )
	{
		case "GAMECONFIG":
			if( RowIndex < 0 || RowIndex > GameConfig.Length-1 || ColumnIndex > 5 )
				return;

			switch( ColumnIndex )
			{
	        	case 0:
					GameConfig[RowIndex].GameClass = NewValue;
					break;
				case 1:
					GameConfig[RowIndex].Prefix = NewValue;
					break;
				case 2:
					GameConfig[RowIndex].Acronym = NewValue;
					break;
				case 3:
					GameConfig[RowIndex].GameName = NewValue;
					break;
				case 4:
					GameConfig[RowIndex].Mutators = NewValue;
					break;
				case 5:
					GameConfig[RowIndex].Options = NewValue;
					break;
			}
   			return;

		case "MAPLIST":
			// TODO: *
			break;
	}
}
//------------------------------------------------------------------------------------------------
function int GetConfigArrayItemCount(string ConfigArrayName)
{
	switch( Caps(ConfigArrayName) )
	{
		case "GAMECONFIG":
   			return GameConfig.Length;

		case "MAPLIST":
			// TODO: *
			break;
	}
}
//------------------------------------------------------------------------------------------------
function ReloadAll( optional bool bParam )
{
	// TODO: ReloadAll
	ReloadMatchConfig(bParam,bParam);
}
//------------------------------------------------------------------------------------------------
function PropagateValue( VotingReplicationInfo Sender, string Type, string SettingName, string NewValue )
{
	local int i;

	// BroadCast change to all other MatchSetup users.
	for( i=0; i<MVRI.Length; i++)
	{
		if(MVRI[i].bMatchSetupPermitted && MVRI[i] != Sender)
		{
			MVRI[i].SendClientResponse( Type, MVRI[i].UpdateID, SettingName $ Chr(27) $ NewValue );
			MVRI[i].bMatchSetupAccepted = false;
		}
	}
}
//------------------------------------------------------------------------------------------------
function ReloadMatchConfig( bool bRefreshMaps, bool bRefreshMuts, optional PlayerController Caller )
{
	local int i;

	for ( i = 0; i < MVRI.Length; i++ )
	{
		// TODO - optimize to determine which settings need to be sent and only send those
		if ( MVRI[i] != None && MVRI[i].bMatchSetupPermitted )
		{
			// If we want a full refresh, have the client request the full refresh, so
			// that the client's current lists will be cleared first
			if ( bRefreshMaps && bRefreshMuts )
				MVRI[i].SendClientResponse(MVRI[i].LoginID,"1");
			else MVRI[i].RequestMatchSettings(bRefreshMaps, bRefreshMuts);
		}
	}
}
//------------------------------------------------------------------------------------------------
function MatchConfig CreateMatchProfile()
{
	return new(None, "MatchConfig" $ Chr(27) $ Level.Game.Class $ Chr(27) $ ServerNumber) class'MatchConfig';
}
//------------------------------------------------------------------------------------------------
function GetServerDetails( out GameInfo.ServerResponseLine ServerState )
{
	local int i;
	i = ServerState.ServerInfo.Length;

	ServerState.ServerInfo.Length = i+1;
	ServerState.ServerInfo[i].Key = "MapVoting";
	ServerState.ServerInfo[i++].Value = Locs(bMapVote);

	ServerState.ServerInfo.Length = i+1;
	ServerState.ServerInfo[i].Key = "KickVoting";
	ServerState.ServerInfo[i++].Value = Locs(bKickVote);
}
//------------------------------------------------------------------------------------------------
defaultproperties
{
	VoteTimeLimit=70
	ScoreBoardDelay=5
	bAutoOpen=True
	MidGameVotePercent=50
	MinMapCount=2
	MapVoteHistoryType="xVoting.MapVoteHistory_INI"
	RepeatLimit=4
	bScoreMode=False
	bAccumulationMode=False
	bEliminationMode=False
	DefaultGameConfig=0
	bDefaultToCurrentGameType=True
	bMapVote=False
	bKickVote=False
	bMatchSetup=False
	KickPercent=51
	bAnonymousKicking=True
	MapListLoaderType="xVoting.DefaultMapListLoader"
	ServerNumber=1

	PropsDisplayText(0)="Enable Map Voting"
    PropsDisplayText(1)="Auto Open GUI"
    PropsDisplayText(2)="ScoreBoard Delay"
    PropsDisplayText(3)="Score Mode"
    PropsDisplayText(4)="Accumulation Mode"
    PropsDisplayText(5)="Elimination Mode"
    PropsDisplayText(6)="Minimum Maps"
    PropsDisplayText(7)="Repeat Limit"
    PropsDisplayText(8)="Voting Time Limit"
    PropsDisplayText(9)="Mid-Game Vote Percent"
    PropsDisplayText(10)="Default Current GameType"
    PropsDisplayText(11)="Enable Kick Voting"
    PropsDisplayText(12)="Kick Vote Percent"
    PropsDisplayText(13)="Anonymous Kick Voting"
    PropsDisplayText(14)="Allow Match Setup"
    PropsDisplayText(15)="Game Configuration"
    PropsDisplayText(16)="Map List Configuration"

	PropDescription(0)="If enabled players can vote for maps."
    PropDescription(1)="If enabled the Map voting interface will automatically open at the end of each game."
    PropDescription(2)="Sets the number of seconds to delay after the end of each game before opening the voting interface."
    PropDescription(3)="If enabled, each player gets his or her score worth of votes."
    PropDescription(4)="If enabled, each player will accumulate votes each game until they win."
    PropDescription(5)="If enabled, available maps are disabled as they are played until there are X maps left."
    PropDescription(6)="The number of enabled maps that remain in the map list (in Elimination mode) before the map list is reset."
    PropDescription(7)="Number of previously played maps that should not be votable."
    PropDescription(8)="Limits how much time (in seconds) to allow for voting."
    PropDescription(9)="Percentage of players that must vote to trigger a Mid-Game vote."
    PropDescription(10)="If enabled, and there are no players on the server then the server will stay on the current game type."
    PropDescription(11)="If enable players can vote to kick other players."
    PropDescription(12)="The percentage of players that must vote against an individual player to have them kicked from the server."
    PropDescription(13)="If enabled players can place Kick votes without anyone knowing who placed the vote."
	PropDescription(14)="Enables match setup on the server - valid admin username & password is required in order to use this feature"
    PropDescription(15)="Opens the map voting game configuration screen"
    PropDescription(16)="Opens the map voting list configuration screen"

    GameConfigPage="xVoting.MapVoteGameConfigPage"
    MapListConfigPage="xVoting.MapVoteMapListConfigPage"

	lmsgAdminMapChange="Admin has forced map switch to %mapname%"
	lmsgMapVotedForWithCount="%playername% has placed %votecount% votes for %mapname%"
	lmsgMapVotedFor="%playername% has voted for %mapname%"
	lmsgMapWon="%mapname% has won !"
	lmsgMidGameVote="Mid-Game Map Voting has been initiated !!!!"
	lmsgSpectatorsCantVote="Sorry, Spectators can not vote."
	lmsgInvalidPassword="The password entered is invalid !"
	lmsgMatchSetupPermission="Sorry, you do not have permission to use Match Setup !"
	lmsgKickVote="%playername1% placed a kick vote against %playername2%"
	lmsgAnonymousKickVote="A kick vote has been placed against %playername%"
	lmsgKickVoteAdmin="%playername% attempted to submit a kick vote against the server administrator !"
	lmsgGameConfigColumnTitle[0]="GameType"
	lmsgGameConfigColumnTitle[1]="MapPrefixes"
	lmsgGameConfigColumnTitle[2]="Abbreviation"
	lmsgGameConfigColumnTitle[3]="Name"
	lmsgGameConfigColumnTitle[4]="Mutators"
	lmsgGameConfigColumnTitle[5]="Options"
}

