//==============================================================================
//	Created on: 09/04/2003
//	MaplistManager adds multiple maplist support to UT2004.  To ensure backwards compatibility,
//  it does not change the way the maplist system current works - instead, it enhances this
//  system by serving as the intermediary between the interface and the maplist used by the
//  game.
//  The only contact that MaplistManager has with the standard maplists are when
//    a) Creating the default list for a new gametype - it uses the value of the Maps array for
//       that gametype's maplist
//    b) When the user decides to "Use" or apply the maplist.  The maps in the custom maplist are
//       copied to the gametype's maplist class.
//
//	Written by Ron Prestenback
//	© 2003, Epic Games, Inc.  All Rights Reserved
//==============================================================================
class MaplistManager extends MaplistManagerBase
	DependsOn(CacheManager)
	DependsOn(MaplistRecord)
	notplaceable;

struct GameRecordGroup
{
	var() string GameType;
	var() string ActiveMaplist;
};

struct MaplistRecordGroup
{
	var string GameType;
	var int    Active;
	var int    LastActive;  // used for cancelling changes

	var array<MaplistRecord.MapItem> AllMaps;
	var array<MaplistRecord> Records;
};

var protected config array<GameRecordGroup>  Games;
var protected array<string> MaplistRecordNames;

var array<CacheManager.GameRecord> 	CachedGames;
var protected array<MaplistRecordGroup> Groups;
var() localized string DefaultListName;
var() localized string InvalidGameType;
var() localized string ReallyInvalidGameType;
var() localized string DefaultListExists;
var   bool bDirty;	// Indicates that we need to save


event PreBeginPlay()
{
	local int i, idx;

	Super.PreBeginPlay();

	// Get list of gametypes
	class'CacheManager'.static.GetGameTypeList(CachedGames);


	// Create records for all record names stored in the ini.
	InitializeMaplistRecords();

	// Initialize all groups
	for (i = 0; i < CachedGames.Length; i++)
	{
		idx = AddGroup(CachedGames[i].ClassName);

		// If this group does not have any records, create a default maplist
		if ( Groups[idx].Records.Length <= 0 )
			CreateDefaultList(i);
	}

	// Set the active list for all groups according to the value in the ini
	InitializeActiveLists();

	// Save if any config values were changed.
	if ( bDirty )
		Save();
}

event Destroyed()
{
	// Make sure we are not destroyed while changes pending
	if ( bDirty )
		Save();

	Super.Destroyed();
}

protected function CreateDefaultList(int i)
{
	local string ListName;
	local array<string> Arr;

	if ( !ValidCacheGameIndex(i) )
		return;

	ListName = DefaultListName @ CachedGames[i].GameAcronym;
	if ( GetDefaultMaps(CachedGames[i].MaplistClassName, Arr)/* && Arr.Length > 0*/ )
		AddList(CachedGames[i].ClassName, ListName, Arr);
}

// returns whether Maplist class was loaded successfully
function bool GetDefaultMaps( string MaplistClassName, out array<string> Maps )
{
	local class<Maplist> List;

	if ( MaplistClassName == "" )
		return false;

	List = class<Maplist>(DynamicLoadObject( MaplistClassName, class'Class', True ));
	if ( List == None )
		return false;

	Maps = List.static.StaticGetMaps();
	return true;
}

protected function InitializeMaplistRecords()
{
	local int i, cnt;
	local MaplistRecord Rec;

	MaplistRecordNames = GetPerObjectNames("System", "MaplistRecord");
	cnt = MaplistRecordNames.Length;

	// First, flush any existing records from all groups (this should never actually happen)
	for ( i = 0; i < Groups.Length; i++ )
		if ( Groups[i].Records.Length > 0 )
			Groups[i].Records.Remove(0, Groups[i].Records.Length);

	for ( i = 0; i < cnt && i < MaplistRecordNames.Length; i++ )
	{
		if ( MaplistRecordNames[i] == "" )
			continue;

		Rec = CreateRecord(MaplistRecordNames[i]);

		// GameType referenced by this MaplistRecord no longer exists, or this name references a non-existent record
		if ( !ValidGameType(Rec.GetGameType()) )
		{
			// Remove the object from the .ini
			Rec.ClearConfig();
			continue;
		}

		AddMaplistRecord(Rec);
	}
}

protected function InitializeActiveLists()
{
	local int i, RecordIndex, idx;

	for ( i = Games.Length - 1; i >= 0; i-- )
	{
		if ( Games[i].GameType == "" )
		{
			RemoveGame(i);
			continue;
		}

		idx = GetGameIndex(Games[i].GameType);
		if ( idx == -1 )
			continue;

		RecordIndex = GetRecordIndex(idx, Games[i].ActiveMaplist);
		if ( RecordIndex < 0 || RecordIndex >= Groups[idx].Records.Length )
			RecordIndex = 0;

		SetActiveList(idx, RecordIndex);
		Groups[idx].LastActive = Groups[idx].Active;
	}
}

protected function int AddMaplistRecord(MaplistRecord Rec)
{
	local int i, j;

	if ( Rec == None || Rec.GetGameType() == "" )
		return -1;

	i = AddGroup(Rec.GetGameType());
	j = Groups[i].Records.Length;
	Rec.SetCacheMaps(Groups[i].AllMaps);
	Groups[i].Records[j] = Rec;

	return j;
}

// Add a group safely - built in protection against duplicates
protected function int AddGroup(string GameType)
{
	local int i;

	if ( GameType == "" )
		return -1;

	i = GetGameIndex(GameType);
	if ( i == -1 )
	{
		i = Groups.Length;
		Groups.Length = i + 1;
		Groups[i].GameType = GameType;

		GenerateGroupMaplist( i );
	}

	AddGameType(GameType);

	return i;
}

// Add a gametype safely - built in protection against duplicates
protected function int AddGameType(coerce string NewGameType)
{
	local int i;

	i = GetStoredGameIndex(NewGameType);
	if ( i == -1 )
	{
		i = Games.Length;
		Games.Length = i + 1;
		Games[i].GameType = NewGameType;
		bDirty = True;
	}
	return i;
}

protected function GenerateGroupMaplist( int GroupIndex )
{
	local int i, j;
	local array<CacheManager.MapRecord> Records;
	local MaplistRecord.MapItem  Item;
	local GameInfo.KeyValuePair Option;
	local string OptionName, OptionValueString;
	local array<string> Options;

	if ( !ValidGameIndex(GroupIndex) )
		return;

	i = GetCacheGameIndex(Groups[GroupIndex].GameType);
	if ( i == -1 )
		return;

	class'CacheManager'.static.GetMapList(Records, CachedGames[i].MapPrefix);
	for ( i = 0; i < Records.Length; i++ )
	{
		class'MaplistRecord'.static.CreateMapItem(Records[i].MapName, Item);
		if ( Records[i].ExtraInfo != "" )
		{
			if ( Divide(Records[i].ExtraInfo, "=", OptionName, OptionValueString) )
			{
				if ( OptionName == "LinkSetups" )
					OptionName = "LinkSetup";

				Split(OptionValueString, ";", Options);
				for ( j = 0; j < Options.Length; j++ )
				{
					Option.Key = OptionName;
					Option.Value = Options[j];

					Item.Options[Item.Options.Length] = Option;
				}
			}
		}

		Groups[GroupIndex].AllMaps[Groups[GroupIndex].AllMaps.Length] = Item;
	}

}

protected function bool RemoveGame(int i)
{
	if ( i < 0 || i >= Games.Length )
		return false;

	Games.Remove(i, 1);
	bDirty = True;
	return true;
}

protected function int RemoveRecord(int GameIndex, int RecordIndex)
{
	if ( !ValidRecordIndex(GameIndex, RecordIndex) )
		return -1;

	Groups[GameIndex].Records[RecordIndex].ClearConfig();
	Groups[GameIndex].Records.Remove(RecordIndex, 1);
	if ( !ValidRecordIndex(GameIndex, RecordIndex) )
		RecordIndex = 0;

	return RecordIndex;
}

protected function int GetStoredGameIndex(coerce string GameType)
{
	local int i;

	if ( GameType == "" )
		return -1;

	for ( i = 0; i < Games.Length; i++ )
	{
		if ( Games[i].GameType ~= GameType )
			return i;
	}

	return -1;
}

protected function Save()
{
	if ( bDirty )
	{
		SaveConfig();
		bDirty = False;
	}
}

protected function MaplistRecord CreateRecord(string RecordName)
{
	if ( RecordName == "" )
		return None;

	return new(None, Repl(RecordName, " ", Chr(27))) class'MaplistRecord';
}

protected function bool IsNewGameType(string GameClassName)
{
	return GetStoredGameIndex(GameClassName) == -1;
}

// Never allow direct access to my records
protected function array<MaplistRecord> GetRecords(int GameIndex)
{
	if ( ValidGameIndex(GameIndex) )
		return Groups[GameIndex].Records;
}

protected function bool ValidRecordIndex(int GameIndex, int MapListIndex)
{
	return ValidGameIndex(GameIndex) && MaplistIndex >= 0 && MaplistIndex < Groups[GameIndex].Records.Length && Groups[GameIndex].Records[MapListIndex] != None;
}
/*
protected function bool ValidRecordNameIndex(int Index)
{
	return Index >= 0 && Index < MaplistRecordNames.Length;
}
*/
///////////////////////////////////////////////////////////////////
// Public Modifier Methods
//
//

// Called by maplist when the map changes
function MapChange(string NewMap)
{
	local int i, GameIndex, RecordIndex;

	GameIndex = AddGroup(string(Level.Game.Class));
	if ( ValidRecordIndex(GameIndex, Groups[GameIndex].Active) )
	{
		RecordIndex = Groups[GameIndex].Active;
		i = Groups[GameIndex].Records[RecordIndex].GetMapIndex(NewMap);

		// Uh oh!  The active maplist doesn't contain this map - should we ignore or attempt to find a maplist containing this map?
		if ( i == -1 )
		{
			// Attempt to find and activate a custom maplist containing this map
			RecordIndex = FindMaplistContaining(GameIndex, NewMap);
			if ( SetActiveList(GameIndex, RecordIndex) )
				i = Groups[GameIndex].Records[RecordIndex].GetMapIndex(NewMap);
		}
	}
	else
	{
		RecordIndex = FindMaplistContaining(GameIndex, NewMap);
		if ( SetActiveList(GameIndex, RecordIndex) )
			i = Groups[GameIndex].Records[RecordIndex].GetMapIndex(NewMap);
	}

	if ( i != -1 && ValidRecordIndex(GameIndex, RecordIndex) )
	{
		Groups[GameIndex].Records[RecordIndex].SetActiveMap(i);
		SaveMaplist(GameIndex, RecordIndex);
	}
}

// Add a new maplist
function int AddList(string GameType, string NewName, array<string> Maps)
{
	local int i;
	local MaplistRecord NewRecord;
	local string DesiredName;

	if ( !ValidGameType(GameType) )
		return -1;

	//check that we aren't using this name already
	// If so, generate a unique name
	DesiredName = NewName;
	while ( ValidName(NewName) )
		NewName = DesiredName $ string(i++);

	// Clamp the length of the name to a reasonable value
	if ( Len(NewName) > 512 )
		return -1;

	NewRecord = CreateRecord(NewName);
	if ( NewRecord == None )
		return -1;

	NewRecord.SetGameType(GameType);
	NewRecord.SetTitle(NewName);

	i = AddMaplistRecord(NewRecord);

	// Make sure new maplist always has maps
	if (Maps.Length == 0)
		NewRecord.Clear(true);
	else NewRecord.SetMapList(Maps);

	NewRecord.Save();
	if ( bDirty )
		Save();

	return i;
}
// Since the lists are PerObjectConfig, and the section of the ini is determined by the object's name,
// it is impossible to "rename" a list.  Instead, I must destroy this list, and create a new one with the
// desired name.
function int RenameList(int GameIndex, int RecordIndex, string NewName)
{
	local MaplistRecord OldRecord;
	local bool IsActive;

	if (!ValidRecordIndex(GameIndex, RecordIndex))
		return -1;

	if ( NewName == "" )
		return -1;

	IsActive = GetActiveList(GameIndex) == RecordIndex;
	OldRecord = Groups[GameIndex].Records[RecordIndex];
	RemoveRecord(GameIndex, RecordIndex);

	// Copy everything from the old list to the new list.
	RecordIndex = AddList(OldRecord.GetGameType(), NewName, OldRecord.GetAllMapURL());
	if ( ValidRecordIndex(GameIndex, RecordIndex) )
	{
		Groups[GameIndex].Records[RecordIndex].SetActiveMap(OldRecord.GetActiveMapIndex());

		if ( IsActive && !SetActiveList(GameIndex, RecordIndex) )
			RecordIndex = GetActiveList(GameIndex);
	}

	return RecordIndex;
}
function int RemoveList(int GameIndex, int RecordIndex)
{
	local int i, idx;

	if (!ValidRecordIndex(GameIndex, RecordIndex))
		return GetActiveList(GameIndex);

	idx = RemoveRecord(GameIndex, RecordIndex);

	// If this was the gametype's last record, regenerate a default maplist
	if ( Groups[GameIndex].Records.Length == 0 )
	{
		i = GetCacheGameIndex(Groups[GameIndex].GameType);
		CreateDefaultList(i);
	}

	// If this was the gametype's active list, reset active to the first list.
	if ( Groups[GameIndex].Active < 0 ||
	     Groups[GameIndex].Active == RecordIndex ||
		 Groups[GameIndex].Active >= Groups[GameIndex].Records.Length )
	{
		SetActiveList(GameIndex, 0);
		ApplyMaplist(GameIndex, 0);
	}

	// If we created a new list or had to change the active list
	if ( bDirty )
		Save();

	return idx;
}
// Save a maplist
function bool SaveMapList(int GameIndex, int RecordIndex)
{
	if (!ValidRecordIndex(GameIndex,RecordIndex))
		return false;

	Save();
	Groups[GameIndex].Records[RecordIndex].Save();
	return true;
}
function bool ClearList(int GameIndex, int RecordIndex)
{
	if (!ValidRecordIndex(GameIndex, RecordIndex))
		return false;

	Groups[GameIndex].Records[RecordIndex].Clear();
	return true;
}
function ResetList(int GameIndex, int RecordIndex)
{
	if (!ValidRecordIndex(GameIndex, RecordIndex))
		return;

	Groups[GameIndex].Records[RecordIndex].CancelChanges();
}

// Apply or cancel changes
function bool SaveGame(int GameIndex)
{
	local int i, Active;

	if (!ValidGameIndex(GameIndex))
		return false;

	Groups[GameIndex].LastActive = Groups[GameIndex].Active;

	i = AddGameType(Groups[GameIndex].GameType);

	Active = GetActiveList(GameIndex);
	if ( !ValidRecordIndex(GameIndex, Active) )
		SetActiveList(GameIndex, 0);

	for ( i = 0; i < Groups[GameIndex].Records.Length; i++ )
		SaveMaplist(GameIndex, i);

	Save();
	return True;
}
function ResetGame(int GameIndex)
{
	local int i;
	local int RecordIndex;

	if ( !ValidGameIndex(GameIndex) )
		return;

	// Reset all lists for this gametype.
	for ( i = 0; i < Groups[GameIndex].Records.Length; i++ )
		ResetList(GameIndex, i);

	i = GetStoredGameIndex(Groups[GameIndex].GameType);

	// Reset the gametype's active maplist
	RecordIndex = GetRecordIndex(GameIndex, Games[i].ActiveMaplist);
	if ( !ValidRecordIndex(GameIndex, RecordIndex) )
		RecordIndex = 0;

	SetActiveList(GameIndex, RecordIndex);
}

function bool ApplyMapList(int GameIndex, int RecordIndex)
{
	local class<MapList> ListClass;
	local int i;

	if (ValidRecordIndex(GameIndex, RecordIndex))
	{
		SetActiveList(GameIndex, RecordIndex);
		SaveGame(GameIndex);
		i = GetCacheGameIndex(Groups[GameIndex].GameType);
		if (i == -1)
		{
			Warn("Error applying maplist:"@Groups[GameIndex].GameType);
			return false;
		}

		ListClass = class<MapList>(DynamicLoadObject(CachedGames[i].MapListClassName,Class'Class'));
		if ( ListClass == None )
		{
			log("Invalid maplist class:"@CachedGames[i].MaplistClassName@"for gametype"@Cachedgames[i].ClassName);
			return false;
		}

		ListClass.static.SetMaplist( GetActiveMap(GameIndex,RecordIndex), GetMaplist(GameIndex,RecordIndex) );
 		return true;
	}
	else log("Invalid maplist index");

	return false;
}
function bool SetActiveList(int GameIndex, int NewActive)
{
	local int i;

	if (!ValidRecordIndex(GameIndex, NewActive))
		return false;

	i = AddGameType(Groups[GameIndex].GameType);

	Games[i].ActiveMaplist = Groups[GameIndex].Records[NewActive].GetTitle();
	Groups[GameIndex].Active = NewActive;
	bDirty = True;

	return true;
}


// Add a new map to the current maplist
function bool AddMap(int GameIndex, int RecordIndex, string MapName)
{
	if (ValidRecordIndex(GameIndex, RecordIndex))
		return Groups[GameIndex].Records[RecordIndex].AddMap(MapName);

	return false;
}
function bool InsertMap(int GameIndex, int RecordIndex, string MapName, int ListIndex)
{
	if ( ValidRecordIndex(GameIndex, RecordIndex) )
		return Groups[GameIndex].Records[RecordIndex].InsertMap(MapName, ListIndex);

	return false;
}

function bool RemoveMap(int GameIndex, int RecordIndex, string MapName)
{
	if (ValidRecordIndex(GameIndex, RecordIndex))
		return Groups[GameIndex].Records[RecordIndex].RemoveMap(MapName);

	return false;
}
// Adapted from xWebAdmin.StringArray
function ShiftMap(int GameIndex, int RecordIndex, string MapName, int Count)
{
	local int i;
	local array<MaplistRecord.MapItem> Maps;

	if (!ValidRecordIndex(GameIndex, RecordIndex))
		return;

	if ( Count == 0 )
		return;

	i = Groups[GameIndex].Records[RecordIndex].GetMapIndex( MapName );
	if ( i == -1 )
		return;

	Maps = Groups[GameIndex].Records[RecordIndex].GetMaps();
	if (Count < 0)
	{
		// Move items toward 0
		if (i + Count < 0)
			Count = -i;
		Maps.Insert(i + Count, 1);
		Maps[i+Count] = Maps[i+1];
		Maps.Remove( i + 1, 1 );
	}
	else
	{
		if ((i + Count + 1) >= Maps.Length)
			Count = Maps.Length - i - 1;

		Maps.Insert(i + Count + 1, 1);
		Maps[i + Count + 1] = Maps[i];
		Maps.Remove(i, 1);
	}

	Groups[GameIndex].Records[RecordIndex].SetMapItemList( Maps );
}
function int SetActiveMap(int GameIndex, int RecordIndex, int MapIndex)
{
	if ( !ValidRecordIndex(GameIndex, RecordIndex) )
		return -1;

	return Groups[GameIndex].Records[RecordIndex].SetActiveMap(MapIndex);
}

///////////////////////////////////////////////////////////////////
// Public Accessor Methods
//
//
function int GetGameIndex(coerce string GameType)
{
	local int i;

	if ( GameType == "" )
		return -1;

	for(i = 0; i < Groups.Length; i++)
		if (Groups[i].GameType ~= GameType)
			return i;

	return -1;
}
function int GetCacheGameIndex(string GameType)
{
	local int i;

	for (i = 0; i < CachedGames.Length; i++)
		if (CachedGames[i].ClassName ~= GameType)
			return i;

	return -1;
}


function array<string> GetMapListNames(int GameIndex)
{
	local int i, idx;
	local array<MaplistRecord> Records;
	local array<string> Arr;

	if (ValidGameIndex(GameIndex))
	{
		Records = GetRecords(GameIndex);
		if ( Records.Length == 0 )
		{
			idx = GetCacheGameIndex(Groups[GameIndex].GameType);
			CreateDefaultList(idx);
			Records = GetRecords(GameIndex);
		}
		Arr.Length = Records.Length;

		for (i = 0; i < Records.Length; i++)
			Arr[i] = Records[i].GetTitle();
	}

	return Arr;
}

function array<string> GetCurrentMapRotation()
{
	local int GameIndex, RecordIndex, CurrentMap, i;
	local array<string> Ar;

	if ( Level.Game == None )
		return Ar;

	GameIndex = GetGameIndex(Level.Game.Class);
	RecordIndex = GetActiveList(GameIndex);
	CurrentMap = GetActiveMap(GameIndex, RecordIndex);

	Ar = GetMaplist(GameIndex, RecordIndex);
	while (i < CurrentMap)
		Ar[Ar.Length] = Ar[i++];

	if ( i > 0 && Ar.Length > 0 )
		Ar.Remove(0, i);

	return Ar;
}

function string GetActiveMapName( int GameIndex, int RecordIndex )
{
	if ( ValidRecordIndex(GameIndex, RecordIndex) )
		return Groups[GameIndex].Records[RecordIndex].GetActiveMapName();
}

function string GetMapTitle( int GameIndex, int RecordIndex, int MapIndex )
{
	if ( ValidRecordIndex(GameIndex, RecordIndex) )
		return Groups[GameIndex].Records[RecordIndex].GetMapName(MapIndex);
}

function string GetMapURL( int GameIndex, int RecordIndex, int MapIndex )
{
	if ( ValidRecordIndex(GameIndex, RecordIndex) )
		return Groups[GameIndex].Records[RecordIndex].GetMapURL(MapIndex);
}

//if _RO_
function SetMapURL( int GameIndex, int RecordIndex, int MapIndex, string NewMapURL )
{
	if ( ValidRecordIndex(GameIndex, RecordIndex) )
	{
        Groups[GameIndex].Records[RecordIndex].SetMapURL(MapIndex, NewMapURL);
    	bDirty = True;
    }
}
//end _RO_

function array<string> GetMapList(int GameIndex, int RecordIndex)
{
	local array<string> Maps;

	if (ValidRecordIndex(GameIndex, RecordIndex))
		Maps = Groups[GameIndex].Records[RecordIndex].GetAllMapURL();

	return Maps;
}

function bool GetAvailableMaps( int GameIndex, out array<MaplistRecord.MapItem> Ar )
{
	if ( !ValidGameIndex(GameIndex) )
		return False;

	Ar = Groups[GameIndex].AllMaps;
	return True;
}

function array<string> GetCacheMapList(string Acronym)
{
	local int i, j;
	local array<CacheManager.MapRecord> TempRecords;
	local array<string> Arr;

	class'CacheManager'.static.GetMapList(TempRecords, Acronym);

	Arr.Length = TempRecords.Length;
	for (i = TempRecords.Length - 1; i >= 0; i--)
		Arr[j++] = TempRecords[i].MapName;

	return Arr;
}
function int FindMaplistContaining(int GameIndex, string Mapname)
{
	local int i, idx;

	if ( ValidGameIndex(GameIndex) )
	{
		for ( i = 0; i < Groups[GameIndex].Records.Length; i++ )
		{
			idx = Groups[GameIndex].Records[i].GetMapIndex(MapName);
			if ( idx != -1 )
				return i;
		}
	}

	return -1;
}
function int GetMapIndex(int GameIndex, int RecordIndex, string MapName)
{
	if ( ValidRecordIndex(GameIndex, RecordIndex) )
		return Groups[GameIndex].Records[RecordIndex].GetMapIndex(MapName);

	return -1;
}
function int GetRecordIndex(int GameIndex, string MapListName)
{
	local int i;
	local array<MaplistRecord> Records;


	if ( ValidGameIndex(GameIndex) )
	{
		if ( MaplistName == "" )
			return GetActiveList(GameIndex);

		Records = GetRecords(GameIndex);
		for ( i = 0; i < Records.Length; i++ )
			if ( Records[i] != None && Records[i].GetTitle() ~= MaplistName )
				return i;
	}

	return -1;
}
function string GetMapListTitle(int GameIndex, int RecordIndex)
{
	if (ValidRecordIndex(GameIndex, RecordIndex))
		return Groups[GameIndex].Records[RecordIndex].GetTitle();

	return "";
}

function int GetActiveList(int GameIndex)
{
	if (ValidGameIndex(GameIndex))
		return Groups[GameIndex].Active;

	return -1;
}
function int GetActiveMap(int GameIndex, int RecordIndex)
{
	if ( !ValidRecordIndex(GameIndex,RecordIndex) )
		return -1;

	return Groups[GameIndex].Records[RecordIndex].GetActiveMapIndex();
}

function bool ValidCacheGameIndex(int i)
{
	return i >= 0 && i < CachedGames.Length;
}

function bool ValidGameIndex(int i)
{
	return i >= 0 && i < Groups.Length;
}

function bool ValidGameType(string GameType)
{
	local int i;

	for (i = 0; i < CachedGames.Length; i++)
	{
		if (GameType ~= CachedGames[i].ClassName)
			return true;
	}

	return false;
}
function bool ValidName(string S)
{
	local int i, j;

	if ( S == "" )
		return false;

	for ( i = 0; i < Groups.Length; i++ )
	{
		for ( j = 0; j < Groups[i].Records.Length; j++ )
			if ( Groups[i].Records[j] != None && Groups[i].Records[j].GetTitle() ~= S )
				return true;
	}

	return false;
}

function bool MaplistDirty(int GameIndex, int RecordIndex)
{
	if ( ValidRecordIndex(GameIndex, RecordIndex) )
		return Groups[GameIndex].Records[RecordIndex].IsDirty() || bDirty;

	return bDirty;
}

DefaultProperties
{
	DefaultListName="Default"
	InvalidGameType="could not be loaded.  Normally, this means an .u file has been deleted, but the .int file has not."
	DefaultListExists="Gametype already has a default list!"
	ReallyInvalidGameType="The requested gametype '%gametype%' could not be loaded."
}
