Legacy:LadderGameRules

From Unreal Wiki, The Unreal Engine Documentation Site
UT2003 :: Object >> Actor >> Info >> GameRules >> LadderGameRules (Ladder1.46)
//-----------------------------------------------------------
// Ladder.LadderGameRules
//
// Class responsible for maintenance of all profiles
//
// Handles all interface between game & profiles
// Handles all interaction from webadmin interface & adminmenu interface
//-----------------------------------------------------------

class LadderGameRules extends GameRules
	config;

const LOGNAME	= 'LadderGameRules';

// Handles all ServerActors with PlayInfo
var ConfigMaster				SAManager;

// Server was shut down before game was finished
var config bool					bDirtyStart;

// Profiles
struct Profile
{
	var string 					ProfileName;	// Name of Profile
	var bool 					bActive;		// This profile is currently in use
	var bool					bReload;		// This profile was the last profile to be used
};

// Profile Storage
var protected config array<Profile> 	Profiles;
var protected config int				NumRemainingMatches;

// Profile Usage Variables
var array<ProfileConfigSet> 	LadderProfiles;
var ProfileConfigSet 			CurrentProfile;			// Currently Active Profile
var 		StringArray 		AllLadderProfiles;		// Only used for sorting in switch profiles by name
var 		StringArray			ProfileMaps;			// Holds maplist for switching server to new profile
var 		string 				ProfileMutators;		// Holds mutator string for switch server to new profile
var			array<string>		ProfileActors;
var 		bool				bWaitingToSwitch;		// Waiting for end of match to switch server to new profile
var			int					Index;					// Index of currently active profile

var string 						LadderProfileClass;
var string						DefaultProfileName;
var class<ProfileConfigSet>		ProfileClass;

struct CommandLineParam { var string ParamName; var string Value; };
var array<CommandLineParam> CommandLineOptions;

// Localization
var localized string ShutdownWhileTemporary;
var localized string ShutdownWhilePermanent;
var localized string ShutdownWhileWaiting;
var localized string ShutdownRemainingMatches;
var localized string ShutdownResumingTemporary;
var localized string ShutdownResumingPermanent;
var localized string ShutdownTrackingCorrupted;

var localized string EndOfGame;
var localized string EndofGameIntercepting;
var localized string EndofGameNoRemainingMatches;
var localized string OverridingURL;

// Error Messages
var localized string BadProfileClass;
var localized string CannotRemoveActive;
var localized string PreviousProfileRemoved;

// Warning Messages
var localized string EmptyMaplist;
var localized string InvalidMaplist;
var localized string CancellingProfile;
var localized string BadIndex;

// Turns out there is a bug with the 2166 (and probably earlier) version
// of the MatchEnded state of DeathMatch:
// Once Level.TimeSeconds > EndTime + RestartWait, MatchEnded.Timer() begins
// calling RestartGame().  This results in RestartGame (and thus GameRules.HandleRestartGame())
// getting called multiple times.
var bool						bRestartCalled;

//function bool AddTextParam(string ParamName, optional string DefaultValue, optional bool bAddIfNotExists)
//{
//	local int i;
//	local TextParam TmpT;

//	for (
//}

function bool AddBoolParam(string ParamName, optional bool DefaultValue, optional bool bAddIfNotExists);
function bool AddSelectParam(string ParamName, array<string> VarOptions, optional bool bAddIfNotExists);

event Tick(float DeltaTime)
{
	if (Level != None && Level.NextURL != "")
	{
		bDirtyStart = False;
		SaveConfig();
		Disable('Tick');
	}
}

event PreBeginPlay()
{
	ProfileClass = class<ProfileConfigSet>(DynamicLoadObject(LadderProfileClass, class'Class'));
	if (ProfileClass == None)
	{
		log(BadProfileClass@"'"$LadderProfileClass$"'",LOGNAME);
		Destroy();
		return;
	}

// Initialize String Arrays
	AllLadderProfiles = new(None) class'SortedStringArray';
	ProfileMaps = new(None) class'StringArray';
	InitializeProfileArray();

	Super.PreBeginPlay();
}

event PostBeginPlay()
{
	local int i, j, Matches;

	i = 0 - 1;
	j = 0 - 1;

	if (Level.Game.BaseMutator != None && ConfigMaster(Level.Game.BaseMutator.NextMutator) != None)
		SAManager = ConfigMaster(Level.Game.BaseMutator.NextMutator);
	Super.PostBeginPlay();

	if (bDirtyStart)
	{
		i = FindPreviousProfile();
		j = FindActiveProfile();
		Matches = NumRemainingMatches;
		if (i >= 0) // Found a previous profile
		{
			if (j >= 0)  // Found an active profile
			{
				if ( Matches > 0 )  // Temporary profile was active
				{
					log(ShutdownWhileTemporary@"'"$Profiles[j].ProfileName$"'.",LOGNAME);
					log(ShutdownRemainingMatches$":"@NumRemainingMatches$"."@ShutdownResumingTemporary,LOGNAME);
					ApplyProfile( j, Matches );
				}

				else
				{
					log(ShutdownWhileTemporary@"'"$Profiles[j].ProfileName$"'.",LOGNAME);
					log(ShutdownRemainingMatches$":"@NumRemainingMatches$"."@ShutdownResumingPermanent@"'"$Profiles[i].ProfileName$"'.",LOGNAME);
					ApplyProfile( i, 0);
				}

			}
			else
			{
				log(ShutdownWhileWaiting,LOGNAME);	// Shutdown while waiting to apply temporary profile - cancel
				if ( Matches > 0 )
				{
					ResetTrackingValues();
					ApplyProfile(i,Matches);
				}
				else
				{
					ResetTrackingValues();
					ApplyProfile(i,0);
				}
			}
		}
		else if (j >= 0) // no reload, but found active profile
		{
			log(ShutdownWhilePermanent@"'"$Profiles[j].ProfileName$"'."@ShutdownResumingPermanent@"'"$Profiles[j].ProfileName$"'.",LOGNAME);
			ApplyProfile( j, 0 );
		}

		else if (Matches > 0)	// no reload, no active
		{
			NumRemainingMatches = 0;
			SaveConfig();
		}
	}
	else
	{
		bDirtyStart = True;
		SaveConfig();
	}
}

// Game has ended - even if we want to handle restart, cannot simply
// return True, or GameRulesModifiers later in the list do not recieve
// HandleRestartGame() calls
function bool HandleRestartGame()
{
	local int i;
	local bool bHandleRestart;	// True if we are handling restart

	if (bDirtyStart)
	{
		bDirtyStart = False;
		SaveConfig();
	}

	if (!Level.Game.bChangeLevels || Level.Game.bAlreadyChanged)
		return Super.HandleRestartGame();

	if (bRestartCalled)
		return Super.HandleRestartGame();

	else bRestartCalled = True;

	if ( bWaitingToSwitch )
	{
		i = FindPreviousProfile();
		if (i >= 0)
		{
			log(EndOfGame,LOGNAME);
			log(EndofGameIntercepting@"'"$Profiles[i].ProfileName$"'.",LOGNAME);
			ApplyProfile(i,NumRemainingMatches);
			bHandleRestart = True;
		}
	}

	else if ( NumRemainingMatches > 0)
	{
		if (NumRemainingMatches == 1)
		{
		// Time to switch server to previous profile
			i = FindPreviousProfile();
			if (i >= 0)
			{
				log(EndOfGame,LOGNAME);
				log(EndOfGameNoRemainingMatches,LOGNAME);
				log(EndOfGameIntercepting@"'"$Profiles[i].ProfileName$"'.",LOGNAME);
				ApplyProfile(i,0);
				bHandleRestart = true;
			}
			else
			{
				log(EndOfGame,LOGNAME);
				log(PreviousProfileRemoved,LOGNAME);
			}
		}

		if ( !bHandleRestart )
		{
			NumRemainingMatches--;
			SaveConfig();
		}
	}

	// Returns true if either one is true
	return (Super.HandleRestartGame() || bHandleRestart);
}

//#########################################
// Profile Management
//

// Applies selected profile immediately
function ApplyProfile(coerce int NewProfileIndex, int NumberOfMatches)
{
	local int idx,i,j;
	local ProfileConfigSet TempPCS;
	local array<string> TempArr;
	local string GameType,ShortName,FirstMap,Tmp,TmpV;

	idx = ActivateProfile(NewProfileIndex, NumberOfMatches > 0);

	if (idx < 0)
	{
		Warn(CancellingProfile@"'"$Profiles[NewProfileIndex].ProfileName$"'"@"("$BadIndex$")");
		if (ActivateProfile(Index) < 0)
			ResetTrackingValues();

		return;
	}

	TempPCS = LadderProfiles[idx];
	class'Ladder.CommandLineParams'.default.bRejectPlayInfo = True;
	TempPCS.StartEdit();
	GameType = string(TempPCS.GetGameClass());

// Set the maplist
	ProfileMaps.Reset();
	TempArr = TempPCS.GetUsedMaps();
	if (TempArr.Length == 0)
	{
		Warn(CancellingProfile@"'"$Profiles[idx].ProfileName$"'"@"("$EmptyMaplist$")");
		if (Index == idx) DeactivateProfile(Index);

		else if (ActivateProfile(Index, False) < 0)
			ResetTrackingValues();

		TempPCS.EndEdit(False);
		return;
	}

	NumRemainingMatches = NumberOfMatches;
	for (i=0;i<TempArr.Length;i++)
		ProfileMaps.Add(string(i),TempArr[i]);

	FirstMap = ProfileMaps.getTag(0);
	UpdateDefaultMaps(GameType,ProfileMaps);
//log("TempPCS:"$TempPCS@"ConfigManager:"$SAManager,'ApplyProfile');
// Set the mutators
	ProfileMutators = "";
	ProfileMutators = UpdateMutatorString(TempPCS);

// Set the server actors
	ProfileActors = TempPCS.GetUsedServerActors();
	TempArr = SAManager.GetAllManagedActors();
	for (i = 0; i < TempArr.Length; i++)
	{
		Tmp = Left(TempArr[i],InStr(TempArr[i],","));
		for (j = 0; j < ProfileActors.Length; j++)
		{
			if (Tmp ~= ProfileActors[j])
				break;

		}

		if (j < ProfileActors.Length)
			EnableManagedActor(Tmp);

		else DisableManagedActor(Tmp);
	}

// Set the PlayInfo
	for (i=0;i<TempPCS.Count();i++)
	{
		Tmp = TempPCS.GetProfileParamName(i);
		TmpV = TempPCS.GetProfileParam(i);
		ShortName = GetItemName(Tmp);

	// If this parameter was specified at the URL,
	// make sure to replace it, otherwise the URL value will
	// override the profile value
		if (HasURLOption(ShortName))
		{
			log(OverridingURL@ShortName$".",LOGNAME);
			UpdateURL(ShortName, TmpV, false);
		}

		else if (!TempPCS.SetNamedParam(Tmp, TmpV))
		{
			for (j = 0; j < CommandLineOptions.Length; j++)
			{
				if (Tmp ~= CommandLineOptions[j].ParamName && TmpV != "")
					UpdateURL(ShortName, TmpV, false);
			}
		}
	}

	TempPCS.SavePI();
	TempPCS.EndEdit(False);
	if (bDirtyStart)
	{
		bDirtyStart = False;
		SaveConfig();
	}

	Level.ServerTravel(FirstMap$"?Game="$GameType$"?Mutator="$ProfileMutators, False);
}

// Applies selected profile at the end of the current match
function bool WaitApplyProfile(coerce int NewProfileIndex, int NumberOfMatches)
{
	local int i;
	local int CurrentActive;

	CurrentActive = FindActiveProfile();
// Check to make sure we aren't attempting to switch to the currently active profile
	if ( NewProfileIndex == CurrentActive)
		return false;

// Clean up any other profiles that were supposed to be reloaded
	i = FindPreviousProfile();
	if (i >= 0) Profiles[i].bReload = False;
	if (CurrentActive >= 0) Profiles[CurrentActive].bActive = False;
	Profiles[NewProfileIndex].bReload = True;
	bWaitingToSwitch = True;
	NumRemainingMatches = NumberOfMatches;
	SaveConfig();
	return true;
}

// Add new profile
function ProfileConfigSet AddProfile(string PCSName, optional string GameType)
{
	local Profile TempP;
	local int i, NumProfiles;

	NumProfiles = Profiles.Length;
	for (i = 0; i < NumProfiles; i++)
		if (Profiles[i].ProfileName ~= PCSName)
			return None;

	TempP.ProfileName = PCSName;
	Profiles[Profiles.Length] = TempP;

	LoadProfile(TempP,GameType);
	SaveConfig();

	return LadderProfiles[NumProfiles];
}

// Copy profile - NewProfileName must be unique
function bool CopyProfile(int SourceProfileIndex, string NewProfileName)
{
	local ProfileConfigSet PCS;

	if (SourceProfileIndex < 0 || SourceProfileIndex >= Profiles.Length)
		return false;

	if (LadderProfiles[SourceProfileIndex] == None)
		return false;

	PCS = AddProfile(NewProfileName);
	if (PCS != None)
	{
		LadderProfiles[SourceProfileIndex].StartEdit();
		PCS.StartEdit();
		PCS.ReplaceWith(LadderProfiles[SourceProfileIndex]);
		PCS.EndEdit(True);
		LadderProfiles[SourceProfileIndex].EndEdit(False);
		return true;
	}
	return false;
}

// Remove profile
function bool RemoveProfile(int idx)
{
	local ProfileConfigSet LastPCS;
	local int LastIDX;

	if (idx < 0 || idx >= LadderProfiles.Length)
		return false;

	if (Profiles[idx].bActive)
	{
		log(CannotRemoveActive,LOGNAME);
		return false;
	}

	LastIDX = LadderProfiles.Length - 1;
	LastPCS = LadderProfiles[LastIDX];
	LastPCS.StartEdit();
	if (idx < LastIDX)
	{
		Profiles[idx] = Profiles[LastIDX];

		LadderProfiles[idx].StartEdit();
		LadderProfiles[idx].ReplaceWith(LastPCS);
		LadderProfiles[idx].EndEdit(True);

		AllLadderProfiles.Remove(AllLadderProfiles.FindItemId(string(idx)));
		AllLadderProfiles.Add(string(idx),Profiles[LastIDX].ProfileName);
	}

	LastPCS.Wipe();
	LastPCS.EndEdit(True);
	Profiles.Remove(LastIDX,1);
	LadderProfiles.Remove(LastIDX,1);
	AllLadderProfiles.Remove(AllLadderProfiles.FindItemId(string(LastIDX)));
	SaveConfig();
	return true;
}

// Returns index of currently active profile
function int FindActiveProfile()
{
	local int i;

	if (Index < -1)
	{
		for (i=0;i<Profiles.Length;i++)
			if (Profiles[i].bActive)
				Index = i;

		if (Index < -1) Index++;
	}

	return Index;
}

// Returns index of previously active profile
function int FindPreviousProfile()
{
	local int i;

	for (i=0;i<Profiles.Length;i++)
		if (Profiles[i].bReload)
			return i;

	return -1;
}

//#####################################################
// Profile Interface
//

// Returns an array of map names that in the maplist for this profile
function array<string> GetProfileMaps(optional string CurrentProfileItem)
{
	local int j;
	local ProfileConfigSet PCS;
	local array<string> TempMaps;

	if (CurrentProfileItem != "")
	{
		j = int(CurrentProfileItem);
		PCS = LadderProfiles[j];
	}

	if (PCS == None && CurrentProfile!=None)
		PCS = CurrentProfile;

	if (PCS != None)
		TempMaps = PCS.GetUsedMaps();

	return TempMaps;
}

// Returns an array of class names for mutators that this profile will use in the game
function array<string> GetProfileMutators(optional string CurrentProfileItem)
{
	local int j;
	local ProfileConfigSet PCS;
	local array<string> TempString;

	if (CurrentProfileItem != "")
	{
		j = int(CurrentProfileItem);
		PCS = LadderProfiles[j];
	}

	if (PCS == None && CurrentProfile != None)
		PCS = CurrentProfile;

	// Query profile for mutators to display
	if (PCS != none)
		TempString = PCS.GetUsedMutators();

	return TempString;
}

function array<string> GetProfileServerActors(optional string CurrentProfileItem)
{
	local int j;
	local ProfileConfigSet PCS;
	local array<string> Temp;

	if (CurrentProfileItem != "")
	{
		j = int(CurrentProfileItem);
		PCS = LadderProfiles[j];
	}

	if (PCS == None && CurrentProfile != None)
		PCS = CurrentProfile;

	if (PCS != None)
		Temp = PCS.GetUsedServerActors();

	return Temp;
}

//#####################################################
// Protected helper functions
//

// Clears LadderProfiles array and loads each stored (Profiles array)
// profile to the LadderProfiles array
protected function InitializeProfileArray()
{
	local int i;

	LadderProfiles.Length = 0;

// Load all profiles immediately
	for (i = 0; i < Profiles.Length; i++)
		LoadProfile(Profiles[i]);
}

// Adds a profile to the LadderProfiles array & initializes the profile
protected function LoadProfile(Profile NewProfile, optional string GameType)
{
	local int i;

	i = LadderProfiles.Length;
	LadderProfiles.Length = LadderProfiles.Length + 1;

	LadderProfiles[i] = new(None,"Profile"$string(i)) ProfileClass;

	if (LadderProfiles[i].GetGameClass() != None)
	{
		LadderProfiles[i].StartEdit();
		LadderProfiles[i].Wipe();
		LadderProfiles[i].EndEdit(True);
	}

	if (GameType == "") LadderProfiles[i].Init(Level);
	else LadderProfiles[i].Init(Level,GameType);

	AllLadderProfiles.Add(string(i), NewProfile.ProfileName);
}

protected function int ActivateProfile(coerce int ProfileId, optional bool bStoreLast)
{
	local int i,j;

	if (ProfileId < 0 || ProfileId >= Profiles.Length)
		return -1;

	i = FindActiveProfile();

	if (i == ProfileId) return ProfileId;
	j = 0 - 1;

	if (i >= 0) j = DeactivateProfile(i);
	if (j >= 0)
	{
		if (bStoreLast) Profiles[j].bReload = True;
		else Profiles[j].bReload = False;
	}

	Profiles[ProfileId].bActive = True;
	Profiles[ProfileId].bReload = False;
	SaveConfig();
	return ProfileId;
}

protected function int DeactivateProfile(coerce int j)
{
	if (j < 0 || j > Profiles.Length)
		return -1;

	Profiles[j].bActive = False;
	Index = default.Index;
	return j;
}

// Copied from UTServerAdmin - Applies the profile's maplist to the server's maplist
function UpdateDefaultMaps(String GameType, StringArray Maps)
{
	local class<GameInfo> GameClass;
	local MapList List;
	local int i;

	if (Maps == None)
	{
		Warn(InvalidMaplist);
		return;
	}
	GameClass = class<GameInfo>(DynamicLoadObject(GameType, class'Class'));
	if (GameClass != None && GameClass.Default.MapListType != "")
	{
		List = Level.Game.GetMapList(GameClass.Default.MapListType);
		if (List != None)
		{
			List.Maps.Length = 0;
			for (i=0; i<Maps.Count(); i++)
				List.Maps[i] = Maps.GetTag(i);

			List.MapNum = 0;
			List.SaveConfig();
			List.Destroy();
		}
	}
}

function EnableManagedActor(string ActorName)
{
	local int i;
	local string S;
	local class<AutoLoader> A;
	local array<string> Arr;

	if (SAManager == None) return;

	for (i = 0; i < SAManager.LoaderClasses.Length; i++)
	{
		A = SAManager.LoaderClasses[i].AutoLoaderClass;
		if (A == None) continue;

		Arr = A.static.GetManagedActors();
		while (Arr.Length > 0)
		{
			S = class'wUtils103.wArray'.static.ShiftS(Arr);
			if (ActorName ~= Left(S, InStr(S,",")))
			{
				A.static.EnableLoader(ActorName);
				return;
			}
		}
	}
}

function DisableManagedActor(string ActorName)
{
	local int i;
	local string S;
	local class<AutoLoader> A;
	local array<string> Arr;

	if (SAManager == None) return;

	for (i = 0; i < SAManager.LoaderClasses.Length; i++)
	{
		A = SAManager.LoaderClasses[i].AutoLoaderClass;
		if (A == None) continue;

		Arr = A.static.GetManagedActors();
		while (Arr.Length > 0)
		{
			S = class'wUtils103.wArray'.static.ShiftS(Arr);
			if (ActorName ~= Left(S, InStr(S,",")))
			{
				A.static.DisableLoader(ActorName);
				return;
			}
		}
	}
}

// Replaces the ?mutator= command line parameter with this profile's configured mutators
function string UpdateMutatorString(ProfileConfigSet PCS)
{
	local array<string> Mutators;
	local string MutatorString;

	if (PCS == None)
		return "";

	Mutators = PCS.GetUsedMutators();
	MutatorString = class'wUtils103.wArray'.static.Join(Mutators,",",True);
	return MutatorString;
}

// Copied from UTServerAdmin and modified
final function bool HasURLOption(string ParamName)
{
	local string Param, Value;
	local int i;

	Param = ParamName;
	while (true)
	{
		i = Instr(Param, ".");
		if (i < 0)
			break;

		Param = Mid(Param, i+1);
	}

	Value = Level.GetUrlOption(Param);
	return Value != "";
}

protected function ResetTrackingValues()
{
	local int i;
	for (i=0;i<Profiles.Length;i++)
	{
		Profiles[i].bActive = False;
		Profiles[i].bReload = False;
	}

	NumRemainingMatches = 0;
	SaveConfig();
}

function int GetRemainingMatches()
{
	return NumRemainingMatches;
}

defaultproperties
{
	Index=-2
    LadderProfileClass="Ladder.ProfileConfigSet"
    ShutdownWhileTemporary="Server was shutdown with active temporary profile"
    ShutdownWhilePermanent="Server was shutdown with active profile"
    ShutdownWhileWaiting="Server was shutdown while waiting to apply a profile. Now applying new profile."
    ShutdownRemainingMatches="Number of matches remaining for this profile"
    ShutdownResumingTemporary="Reactivating temporary profile with remaining number of matches."
    ShutdownResumingPermanent="Reactivating standard profile"
    ShutdownTrackingCorrupted="Profile tracking was corrupted.  Resetting profile tracking values."
    EndOfGame="END OF GAME DETECTED."
    EndofGameIntercepting="Intercepting map change & activating profile"
    EndofGameNoRemainingMatches="Profile match limit reached. Reactivating previous profile."
    OverridingURL="Overriding command line value for parameter"
    BadProfileClass="Bad Profile Class"
    CannotRemoveActive="Removal of active profile not allowed! Please switch server to a different profile, then try again."
    PreviousProfileRemoved="Attempted to return server to previous profile, but no previous profile was found.  Leaving server set to current profile."
    BadIndex="Invalid profile index"
    CancellingProfile="Cancelling activation of profile"
    EmptyMaplist="Profile maplist is empty"
    InvalidMaplist="Attempting to load profile with invalid maplist!"
	CommandLineOptions(0)=(ParamName="CommandLineParams.AdminUserName",Value="")
	CommandLineOptions(1)=(ParamName="CommandLineParams.AdminPassword",Value="")
	CommandLineOptions(2)=(ParamName="CommandLineParams.GameRules",Value="")
	CommandLineOptions(3)=(ParamName="CommandLineParams.DemoRec",Value="")
	CommandLineOptions(4)=(ParamName="CommandLineParams.bAutoNumBots",Value="")
	CommandLineOptions(5)=(ParamName="CommandLineParams.QuickStart",Value="")
	CommandLineOptions(6)=(ParamName="CommandLineParams.RedTeamAI",Value="UnrealGame.TeamAI")
	CommandLineOptions(7)=(ParamName="CommandLineParams.BlueTeamAI",Value="UnrealGame.TeamAI")
	CommandLineOptions(8)=(ParamName="CommandLineParams.RedTeamSymbol",Value="")
	CommandLineOptions(9)=(ParamName="CommandLineParams.BlueTeamSymbol",Value="")
}