// Written by .:..: (2009)
Class ServerPerksMut extends Mutator
	Config(ServerPerks);

struct ChatIconType
{
	var() config string IconTexture,IconTag;
	var() config bool bCaseInsensitive;
};
var() globalconfig array<string> Perks,TraderInventory,WeaponCategories,CustomCharacters;
var() globalconfig int MinPerksLevel,MaxPerksLevel,RemotePort,MidGameSaveWaves,FTPKeepAliveSec;
var() globalconfig float RequirementScaling;
var() globalconfig string RemoteDatabaseURL,RemotePassword,RemoteFTPUser,RemoteFTPDir,ServerNewsURL,FTPUploadAllIgnoreDate;
var() globalconfig array<ChatIconType> SmileyTags;

var array<ClientPerkRepLink.FShopCategoryIndex> LoadedCategories;
var array<byte> LoadInvCategory;
var array< class<SRVeterancyTypes> > LoadPerks;
var array< class<Pickup> > LoadInventory;
var const int VersionNumber;
var array<PlayerController> PendingPlayers;
var array<StatsObject> ActiveStats;
var localized string ServerPerksGroup;
var transient InternetLink Link;
var KFGameType KFGT;
var int LastSavedWave,WaveCounter;
var SRGameRules RulesMod;
var transient array<name> AddedServerPackages;
var transient float NextCheckTimer;
var array<SRHUDKillingFloor.SmileyMessageType> SmileyMsgs;
var class<Object> SRScoreboardType,SRHudType,SRMenuType;
var class<PlayerController> SRControllerType;

var() globalconfig bool bUploadAllStats,bForceGivePerk,bNoSavingProgress,bUseRemoteDatabase,bUsePlayerNameAsID,bMessageAnyPlayerLevelUp,bNoPerkChanges
						,bUseLowestRequirements,bBWZEDTime,bUseEnhancedScoreboard,bOverrideUnusedCustomStats,bAllowAlwaysPerkChanges,bEnableWebAdmin
						,bForceCustomChars,bEnableChatIcons,bEnhancedShoulderView,bFixGrenadeExploit,bAdminEditStats,bUseFTPLink,bDebugDatabase;
var bool bEnabledEmoIcons;

function PostBeginPlay()
{
	local int i,j;
	local class<SRVeterancyTypes> V;
	local class<Pickup> P;
	local string S;
	local byte Cat;
	local class<PlayerRecordClass> PR;
	local Texture T;
	local KFLevelRules R;

	if( RulesMod==None )
		RulesMod = Spawn(Class'SRGameRules');

	KFGT = KFGameType(Level.Game);
	if( Level.Game.HUDType~=Class'KFGameType'.Default.HUDType || Level.Game.HUDType~=Class'KFStoryGameInfo'.Default.HUDType )
	{
		bEnabledEmoIcons = bEnableChatIcons;
		Level.Game.HUDType = string(SRHudType);
	}

	if( bUseEnhancedScoreboard && (Level.Game.ScoreBoardType~=Class'KFGameType'.Default.ScoreBoardType || Level.Game.ScoreBoardType~=Class'KFStoryGameInfo'.Default.ScoreBoardType) )
		Level.Game.ScoreBoardType = string(SRScoreboardType);

	// Use own playercontroller class for security reasons.
	if( Level.Game.PlayerControllerClass==Class'KFPlayerController' || Level.Game.PlayerControllerClass==Class'KFPlayerController_Story' )
	{
		Level.Game.PlayerControllerClass = SRControllerType;
		Level.Game.PlayerControllerClassName = string(SRControllerType);
	}
	DeathMatch(Level.Game).LoginMenuClass = string(SRMenuType);

	// Load perks.
	for( i=0; i<Perks.Length; i++ )
	{
		V = class<SRVeterancyTypes>(DynamicLoadObject(Perks[i],Class'Class'));
		if( V!=None )
		{
			LoadPerks[LoadPerks.Length] = V;
			ImplementPackage(V);
		}
	}
	
	// Setup categories
	LoadedCategories.Length = WeaponCategories.Length;
	for( i=0; i<WeaponCategories.Length; ++i )
	{
		S = WeaponCategories[i];
		j = InStr(S,":");
		if( j==-1 )
		{
			LoadedCategories[i].Name = S;
			LoadedCategories[i].PerkIndex = 255;
		}
		else
		{
			LoadedCategories[i].Name = Mid(S,j+1);
			LoadedCategories[i].PerkIndex = int(Left(S,j));
		}
	}
	if( LoadedCategories.Length==0 )
	{
		LoadedCategories.Length = 1;
		LoadedCategories[0].Name = "All";
		LoadedCategories[0].PerkIndex = 255;
	}

	// Init rules
	foreach AllActors(class'KFLevelRules',R)
		break;
	if( R==None && KFGT!=None )
	{
		R = KFGT.KFLRules;
		if( R==None )
		{
			R = Spawn(class'KFLevelRules');
			KFGT.KFLRules = R;
		}
	}
	if( R!=None )
	{
		// Empty all stock weapons first.
		R.MediItemForSale.Length = 0;
		R.SuppItemForSale.Length = 0;
		R.ShrpItemForSale.Length = 0;
		R.CommItemForSale.Length = 0;
		R.BersItemForSale.Length = 0;
		R.FireItemForSale.Length = 0;
		R.DemoItemForSale.Length = 0;
		R.NeutItemForSale.Length = 0;
	}

	// Load up trader inventory.
	for( i=0; i<TraderInventory.Length; i++ )
	{
		S = TraderInventory[i];
		j = InStr(S,":");
		if( j>0 )
		{
			Cat = Min(int(Left(S,j)),LoadedCategories.Length-1);
			S = Mid(S,j+1);
		}
		else Cat = 0;
		P = class<Pickup>(DynamicLoadObject(S,Class'Class'));
		if( P!=None )
		{
			// Inform bots.
			if( R!=None )
				R.MediItemForSale[R.MediItemForSale.Length] = P;

			LoadInventory[LoadInventory.Length] = P;
			LoadInvCategory[LoadInvCategory.Length] = Cat;
			if( P.Outer.Name!='KFMod' )
				ImplementPackage(P);
		}
	}

	// Load custom chars.
	for( i=0; i<CustomCharacters.Length; ++i )
	{
		// Separate group from actual skin.
		S = CustomCharacters[i];
		j = InStr(S,":");
		if( j>=0 )
			S = Mid(S,j+1);
		PR = class<PlayerRecordClass>(DynamicLoadObject(S$"Mod."$S,class'Class',true));
		if( PR!=None )
		{
			if( PR.Default.MeshName!="" ) // Add mesh package.
				ImplementPackage(DynamicLoadObject(PR.Default.MeshName,class'Mesh',true));
			if( PR.Default.BodySkinName!="" ) // Add skin package.
				ImplementPackage(DynamicLoadObject(PR.Default.BodySkinName,class'Material',true));
			ImplementPackage(PR);
		}
	}
	
	// Load chat icons
	if( bEnabledEmoIcons )
	{
		j = 0;
		for( i=0; i<SmileyTags.Length; ++i )
		{
			if( SmileyTags[i].IconTexture=="" || SmileyTags[i].IconTag=="" )
				continue;
			T = Texture(DynamicLoadObject(SmileyTags[i].IconTexture,class'Texture',true));
			if( T==None )
				continue;
			ImplementPackage(T);
			SmileyMsgs.Length = j+1;
			SmileyMsgs[j].SmileyTex = T;
			if( SmileyTags[i].bCaseInsensitive )
				SmileyMsgs[j].SmileyTag = Caps(SmileyTags[i].IconTag);
			else SmileyMsgs[j].SmileyTag = SmileyTags[i].IconTag;
			SmileyMsgs[j].bInCAPS = SmileyTags[i].bCaseInsensitive;
			++j;
		}
		bEnabledEmoIcons = (j!=0);
	}

	Log("Adding"@AddedServerPackages.Length@"additional serverpackages",Class.Outer.Name);
	for( i=0; i<AddedServerPackages.Length; i++ )
		AddToPackageMap(string(AddedServerPackages[i]));
	AddedServerPackages.Length = 0;
	
	if( bUseEnhancedScoreboard || (Level.Game.HUDType~=string(SRHudType)) )
		AddToPackageMap("CountryFlagsTex");

	if( bFixGrenadeExploit )
		Class'Frag'.Default.FireModeClass[0] = Class'FragFireFix';

	if( bUseRemoteDatabase )
	{
		Log("Using remote database:"@RemoteDatabaseURL$":"$RemotePort,Class.Outer.Name);
		RespawnNetworkLink();
	}
}

function Tick( float Delta )
{
	Disable('Tick');
	if( bEnableWebAdmin && Level.NetMode!=NM_StandAlone )
		InitWebServer();
}
function InitWebServer()
{
	local WebServer W;
	local int i;
	local UTServerAdmin U;
	
	// Must increase the limit size.
	class'WebConnection'.default.MaxValueLength = Max(class'WebConnection'.default.MaxValueLength,9999);
	class'WebConnection'.default.MaxLineLength = Max(class'WebConnection'.default.MaxLineLength,10100);

	foreach DynamicActors(class'WebServer',W)
	{
		for( i=0; i<10; ++i )
		{
			U = UTServerAdmin(W.ApplicationObjects[i]);
			if( U!=None )
			{
				for( i=0; i<U.QueryHandlers.Length; ++i )
					if( U.QueryHandlers[i].Class==Class'ServerPerkWebAdmin' )
						return; // Already implemented.
				U.QueryHandlers.Length = i+1;
				U.QueryHandlers[i] = New(U)Class'ServerPerkWebAdmin';
				U.QueryHandlers[i].Init();
				return;
			}
		}
		Log("ServerPerks WebAdmin interface failed to init: Missing UTServerAdmin application.",Class.Outer.Name);
		return;
	}
	Log("ServerPerks WebAdmin interface failed to init: Missing WebServer object.",Class.Outer.Name);
}

function RespawnNetworkLink()
{
	if( Link!=None )
		Link.Destroy();
	if( !bUseFTPLink )
	{
		Link = Spawn(Class'DatabaseUdpLink');
		DatabaseUdpLink(Link).Mut = Self;
	}
	else
	{
		Link = Spawn(Class'FTPTcpLink');
		FTPTcpLink(Link).Mut = Self;
	}
	Link.BeginEvent();
}

function Mutate(string MutateString, PlayerController Sender)
{
	if( MutateString~="SPHelp" )
		Sender.ClientMessage("ServerPerksMut mutate CMDs: EditStats, SetPerk, Debug, GetID");
	if( Sender.PlayerReplicationInfo.bAdmin || Sender.PlayerReplicationInfo.bSilentAdmin || Viewport(Sender.Player)!=None )
	{
		if( bAdminEditStats && MutateString~="EditStats" )
			Spawn(Class'AdminMenuHandle',Sender).MutatorOwner = Self;
		else if( MutateString~="GetID" )
			ListID(Sender);
		else if( Left(MutateString,7)~="SetPerk" )
			AdminChangePerk(Sender,Mid(MutateString,8));
	}
	if( NextCheckTimer<Level.TimeSeconds && MutateString~="Debug" ) // Allow developer to lookup bugs.
	{
		NextCheckTimer = Level.TimeSeconds+0.25;
		Sender.ClientMessage("Debug info: "$Sender.SteamStatsAndAchievements);
		if( ServerStStats(Sender.SteamStatsAndAchievements)!=None )
		{
			Sender.ClientMessage("Ready:"@ServerStStats(Sender.SteamStatsAndAchievements).bStatsReadyNow@ServerStStats(Sender.SteamStatsAndAchievements).bStatsChecking);
			Sender.ClientMessage("MyStatsObject:"@ServerStStats(Sender.SteamStatsAndAchievements).MyStatsObject);
			Sender.ClientMessage("Timer:"@Sender.SteamStatsAndAchievements.TimerCounter@Sender.SteamStatsAndAchievements.TimerRate);
		}
		Sender.ClientMessage("Perk info:");
		DebugMessageProgress(Sender);
	}
	if ( NextMutator != None )
		NextMutator.Mutate(MutateString, Sender);
}
final function AdminChangePerk( PlayerController Sender, string Cmd )
{
	local int i,ID;
	local Controller C;
	local class<SRVeterancyTypes> V;
	local bool bAll;
	
	i = InStr(Cmd," ");
	if( i==-1 )
	{
		Sender.ClientMessage("Usage: SetPerk <ID/All> <PerkName>");
		return;
	}
	if( Left(Cmd,i)~="All" )
		bAll = true;
	else ID = int(Left(Cmd,i));
	
	Cmd = Mid(Cmd,i+1);
	for( i=0; i<LoadPerks.Length; ++i )
		if( string(LoadPerks[i].Name)~=Cmd || LoadPerks[i].Default.VeterancyName~=Cmd )
		{
			V = LoadPerks[i];
			break;
		}
	if( V==None )
	{
		Sender.ClientMessage("Unknown perk name: "$Cmd);
		return;
	}
	
	for( C=Level.ControllerList; C!=None; C=C.nextController )
	{
		if( C.bIsPlayer && C.PlayerReplicationInfo!=None && (bAll || C.PlayerReplicationInfo.PlayerID==ID) && xPlayer(C)!=None && ServerStStats(xPlayer(C).SteamStatsAndAchievements)!=None )
		{
			ServerStStats(xPlayer(C).SteamStatsAndAchievements).ForcePerkChange(V);
			xPlayer(C).ClientMessage("An admin has forced to change your perk to "$V.Default.VeterancyName);
			if( !bAll )
			{
				Sender.ClientMessage("Changed "$C.PlayerReplicationInfo.PlayerName$" perk to: "$V);
				break;
			}
		}
	}
	if( bAll )
		Sender.ClientMessage("Changed everyones perk to: "$V);
}
final function ListID( PlayerController PC )
{
	local Controller C;
	
	PC.ClientMessage("Player IDs:");
	for( C=Level.ControllerList; C!=None; C=C.nextController )
	{
		if( C.bIsPlayer && C.PlayerReplicationInfo!=None && xPlayer(C)!=None && ServerStStats(xPlayer(C).SteamStatsAndAchievements)!=None )
			PC.ClientMessage("ID="$C.PlayerReplicationInfo.PlayerID$", Name="$C.PlayerReplicationInfo.PlayerName);
	}
}
final function DebugMessageProgress( PlayerController PC )
{
	local KFPlayerReplicationInfo PRI;
	local class<SRVeterancyTypes> VC;
	local int i,Req,Cur,NumR;
	local byte Lvl;
	local ClientPerkRepLink Rep;
	
	PRI = KFPlayerReplicationInfo(PC.PlayerReplicationInfo);
	if( PRI==None || Class<SRVeterancyTypes>(PRI.ClientVeteranSkill)==None )
		return;
	VC = Class<SRVeterancyTypes>(PRI.ClientVeteranSkill);
	Lvl = PRI.ClientVeteranSkillLevel+1;
	
	Rep = Class'SRGameRules'.Static.FindStatsFor(PC);
	if( Rep==None )
		return;

	PC.ClientMessage("Perk: "$VC.Default.VeterancyName$" NextLevel: "$Lvl);
	NumR = VC.Static.GetRequirementCount(Rep,Lvl);
	for( i=0; i<NumR; ++i )
	{
		Cur = VC.Static.GetPerkProgressInt(Rep,Req,Lvl,i);
		PC.ClientMessage("Requirement '"$i$"' GetPerkProgressInt: Current:"$Cur$" Final:"$Req);
		VC.Static.GetPerkProgress(Rep,Lvl,i,Cur,Req);
		PC.ClientMessage("GetPerkProgress: Current:"$Cur$" Final:"$Req);
	}
}
final function ImplementPackage( Object O )
{
	local int i;
	
	if( O==None )
		return;
	while( O.Outer!=None )
		O = O.Outer;
	if( O.Name=='KFMod' )
		return;
	for( i=(AddedServerPackages.Length-1); i>=0; --i )
		if( AddedServerPackages[i]==O.Name )
			return;
	AddedServerPackages[AddedServerPackages.Length] = O.Name;
}

function bool CheckReplacement(Actor Other, out byte bSuperRelevant)
{
	if( PlayerController(Other)!=None )
	{
		PendingPlayers[PendingPlayers.Length] = PlayerController(Other);
		SetTimer(0.1,false);
	}
	else if( Bot(Other)!=None && Bot(Other).PawnClass==Class'KFHumanPawn' )
	{
		Bot(Other).PawnClass = Class'SRHumanPawn';
		Bot(Other).PreviousPawnClass = Class'SRHumanPawn';
	}
	else if( ServerStStats(Other)!=None )
		SetServerPerks(ServerStStats(Other));
	else if( ClientPerkRepLink(Other)!=None )
		SetupRepLink(ClientPerkRepLink(Other));

	return true;
}
function SetServerPerks( ServerStStats Stat )
{
	local int i;

	Stat.MutatorOwner = Self;
	Stat.Rep.ServerWebSite = ServerNewsURL;
	Stat.Rep.MinimumLevel = MinPerksLevel+1;
	Stat.Rep.MaximumLevel = MaxPerksLevel+1;
	Stat.Rep.RequirementScaling = RequirementScaling;
	Stat.Rep.CachePerks.Length = LoadPerks.Length;
	for( i=0; i<LoadPerks.Length; i++ )
		Stat.Rep.CachePerks[i].PerkClass = LoadPerks[i];
}
function SetupRepLink( ClientPerkRepLink R )
{
	local int i;

	R.bMinimalRequirements = bUseLowestRequirements;
	R.bBWZEDTime = bBWZEDTime;
	R.bNoStandardChars = bForceCustomChars;

	R.ShopInventory.Length = LoadInventory.Length;
	for( i=0; i<LoadInventory.Length; ++i )
	{
		R.ShopInventory[i].PC = LoadInventory[i];
		R.ShopInventory[i].CatNum = LoadInvCategory[i];
	}
	R.ShopCategories = LoadedCategories;
	R.CustomChars = CustomCharacters;
	if( bEnabledEmoIcons )
		R.SmileyTags = SmileyMsgs;
}

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

	Super.GetServerDetails( ServerState );
	l = ServerState.ServerInfo.Length;
	ServerState.ServerInfo.Length = l+1;
	ServerState.ServerInfo[l].Key = "SP: Version";
	ServerState.ServerInfo[l].Value = string(VersionNumber);
	l++;
	ServerState.ServerInfo.Length = l+1;
	ServerState.ServerInfo[l].Key = "SP: Min perk level";
	ServerState.ServerInfo[l].Value = string(MinPerksLevel);
	l++;
	ServerState.ServerInfo.Length = l+1;
	ServerState.ServerInfo[l].Key = "SP: Max perk level";
	ServerState.ServerInfo[l].Value = string(MaxPerksLevel);
	l++;
	ServerState.ServerInfo.Length = l+1;
	ServerState.ServerInfo[l].Key = "SP: Num trader weapons";
	ServerState.ServerInfo[l].Value = string(LoadInventory.Length);
	l++;
	For( i=0; i<LoadPerks.Length; i++ )
	{
		ServerState.ServerInfo.Length = l+1;
		ServerState.ServerInfo[l].Key = "SP: Perk "$(i+1);
		ServerState.ServerInfo[l].Value = LoadPerks[i].Default.VeterancyName;
		l++;
	}
}
function Timer()
{
	local int i;
	
	for( i=(PendingPlayers.Length-1); i>=0; --i )
	{
		if( KFPCServ(PendingPlayers[i])!=None )
			KFPCServ(PendingPlayers[i]).bUseAdvBehindview = bEnhancedShoulderView;
		if( PendingPlayers[i]!=None && PendingPlayers[i].Player!=None && ServerStStats(PendingPlayers[i].SteamStatsAndAchievements)==None )
		{
			if( PendingPlayers[i].SteamStatsAndAchievements!=None )
				PendingPlayers[i].SteamStatsAndAchievements.Destroy();
			PendingPlayers[i].SteamStatsAndAchievements = Spawn(Class'ServerStStats',PendingPlayers[i]);
			PendingPlayers[i].PlayerReplicationInfo.SteamStatsAndAchievements = PendingPlayers[i].SteamStatsAndAchievements;
		}
	}
	PendingPlayers.Length = 0;
}

final function string GetPlayerID( PlayerController PC )
{
	if( bUsePlayerNameAsID )
		return PC.PlayerReplicationInfo.PlayerName;
	return PC.GetPlayerIDHash();
}
function StatsObject GetStatsForPlayer( PlayerController PC )
{
	local StatsObject S;
	local string SId;
	local int i;

	if( Level.Game.bGameEnded )
		return None;
	SId = GetPlayerID(PC);
	for( i=0; i<ActiveStats.Length; ++i )
		if( string(ActiveStats[i].Name)~=SId )
		{
			S = ActiveStats[i];
			break;
		}
	if( S==None )
	{
		S = new(None,SId) Class'StatsObject';
		S.bStatsChanged = false;
		ActiveStats[ActiveStats.Length] = S;
	}
	S.PlayerName = PC.PlayerReplicationInfo.PlayerName;
	S.PlayerIP = PC.GetPlayerNetworkAddress();
	return S;
}

function SaveStats()
{
	local int i;
	local ClientPerkRepLink CP;

	if( bNoSavingProgress )
		return;

	Log("*** Saving "$ActiveStats.Length$" stat objects ***",Class.Outer.Name);
	foreach DynamicActors(Class'ClientPerkRepLink',CP)
		if( CP.StatObject!=None && ServerStStats(CP.StatObject).MyStatsObject!=None )
			ServerStStats(CP.StatObject).MyStatsObject.SetCustomValues(CP.CustomLink);

	if( bUseRemoteDatabase )
	{
		SaveAllStats();
		return;
	}
	for( i=0; i<ActiveStats.Length; ++i )
		if( ActiveStats[i].bStatsChanged )
		{
			ActiveStats[i].bStatsChanged = false;
			ActiveStats[i].SaveConfig();
		}
}
function CheckWinOrLose()
{
	local bool bWin;
	local Controller P;
	local PlayerController Player;

	bWin = (KFGameReplicationInfo(Level.GRI)!=None && KFGameReplicationInfo(Level.GRI).EndGameType==2);
	for ( P = Level.ControllerList; P != none; P = P.nextController )
	{
		Player = PlayerController(P);

		if ( Player != none )
		{
			if ( ServerStStats(Player.SteamStatsAndAchievements)!=None )
				ServerStStats(Player.SteamStatsAndAchievements).WonLostGame(bWin);
		}
	}
}
function InitNextWave()
{
	if( ++WaveCounter>=MidGameSaveWaves )
	{
		WaveCounter = 0;
		SaveStats();
	}
}

Auto state EndGameTracker
{
Begin:
	if( bUploadAllStats && Level.NetMode==NM_StandAlone )
	{
		Sleep(1.f);
		FTPTcpLink(Link).FullUpload();
		while( true )
		{
			if( KFGT!=None )
				KFGT.WaveCountDown = 60;
			Sleep(1.f);
		}
	}
	while( !Level.Game.bGameEnded )
	{
		Sleep(1.f);
		if( MidGameSaveWaves>0 && KFGT!=None && KFGT.WaveNum!=LastSavedWave )
		{
			LastSavedWave = KFGT.WaveNum;
			InitNextWave();
		}
		if( Level.bLevelChange )
		{
			SaveStats();
			Stop;
		}
	}
	CheckWinOrLose();
	SaveStats();
}

static final function string GetSafeName( string S )
{
	S = Repl(S,"=","-");
	S = Repl(S,Chr(10),""); // LF
	S = Repl(S,Chr(13),""); // CR
	S = Repl(S,"\"","'"); // " -> '
	return S;
}

delegate SaveAllStats();
delegate RequestStats( ServerStStats Other );

static function FillPlayInfo(PlayInfo PlayInfo)
{
	Super.FillPlayInfo(PlayInfo);

	PlayInfo.AddSetting(default.ServerPerksGroup,"MinPerksLevel","Min Perk Level",1,0, "Text", "4;-1:254");
	PlayInfo.AddSetting(default.ServerPerksGroup,"MaxPerksLevel","Max Perk Level",1,0, "Text", "4;0:254");
	PlayInfo.AddSetting(default.ServerPerksGroup,"RequirementScaling","Req Scaling",1,0, "Text", "6;0.01:4.00");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bForceGivePerk","Force perks",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bNoSavingProgress","No saving",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bAllowAlwaysPerkChanges","Unlimited perk changes",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bNoPerkChanges","No perk changes",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bUseRemoteDatabase","Use remote database",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bUseFTPLink","Use FTP remote database",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"RemoteDatabaseURL","Remote database URL",1,1,"Text","64");
	PlayInfo.AddSetting(default.ServerPerksGroup,"RemotePort","Remote database port",1,0, "Text", "5;0:65535");
	PlayInfo.AddSetting(default.ServerPerksGroup,"RemotePassword","Remote database password",1,0, "Text", "64");
	PlayInfo.AddSetting(default.ServerPerksGroup,"RemoteFTPUser","Remote database user",1,0, "Text", "64");
	PlayInfo.AddSetting(default.ServerPerksGroup,"RemoteFTPDir","Remote database dir",1,0, "Text", "64");
	PlayInfo.AddSetting(default.ServerPerksGroup,"FTPKeepAliveSec","FTP Keep alive sec",1,0, "Text", "6;0:600");
	PlayInfo.AddSetting(default.ServerPerksGroup,"MidGameSaveWaves","MidGame Save Waves",1,0, "Text", "5;0:10");
	PlayInfo.AddSetting(default.ServerPerksGroup,"ServerNewsURL","Newspage URL",1,0, "Text", "64");

	PlayInfo.AddSetting(default.ServerPerksGroup,"bUsePlayerNameAsID","Use PlayerName as ID",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bMessageAnyPlayerLevelUp","Notify any levelup",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bUseLowestRequirements","Use lowest req",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bBWZEDTime","BW ZED-time",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bUseEnhancedScoreboard","Enhanced scoreboard",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bForceCustomChars","Force Custom Chars",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bEnableChatIcons","Enable chat icons",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bEnhancedShoulderView","Shoulder view",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bFixGrenadeExploit","No Grenade Exploit",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bAdminEditStats","Admin edit stats",1,0, "Check");
	PlayInfo.AddSetting(default.ServerPerksGroup,"bEnableWebAdmin","SP WebAdmin",1,0, "Check");
}

static event string GetDescriptionText(string PropName)
{
	switch (PropName)
	{
		case "MinPerksLevel":		return "Minimum perk level players can have.";
		case "MaxPerksLevel":		return "Maximum perk level players can have.";
		case "RequirementScaling":	return "Perk requirements scaling.";
		case "bForceGivePerk":		return "Force all players to get at least a random perk if they have none selected.";
		case "bNoSavingProgress":	return "Server shouldn't save perk progression.";
		case "bUseRemoteDatabase":	return "Instead of storing perk data locally on server, use remote data storeage server.";
		case "bUseFTPLink":			return "Use FTP transmission for remote database.";
		case "RemoteDatabaseURL":	return "URL of the remote database.";
		case "RemotePort":			return "Port of the remote database.";
		case "RemotePassword":		return "Password for server to access the remote database.";
		case "RemoteFTPUser":		return "User name for server to access the remote database (only for FTP mode).";
		case "MidGameSaveWaves":	return "Between how many waves should it mid-game save stats.";
		case "bUsePlayerNameAsID":	return "Use PlayerName's as ID instead of ID Hash.";
		case "bMessageAnyPlayerLevelUp": return "Broadcast a global message anytime someone gains a perk upgrade.";
		case "bUseLowestRequirements":	return "Use lowest form of requirements for perks.";
		case "bBWZEDTime":			return "Make screen go black and white during ZED-time.";
		case "bUseEnhancedScoreboard":	return "Should use serverperk's own scoreboard.";
		case "bAllowAlwaysPerkChanges":	return "Allow unlimited perk changes.";
		case "bForceCustomChars":	return "Force players to use specified custom characters.";
		case "bEnableChatIcons":	return "Should enable chat icons to replace specific tags in chat.";
		case "bEnhancedShoulderView": return "Should enable a more enhanced on shoulder behindview.";
		case "bFixGrenadeExploit":	return "Should fix unlimited grenades glitch/exploit.";
		case "bAdminEditStats":		return "Allow admins edit stats through an admin menu.";
		case "ServerNewsURL":		return "Server newspage menu URL for page contents.";
		case "RemoteFTPDir":		return "Remote FTP database data storage directory.";
		case "FTPKeepAliveSec":		return "Keep FTP database connection alive with this many sec interval.";
		case "bEnableWebAdmin":		return "Enable additional webserver interface.";
		case "bNoPerkChanges":	return "Never allow players change perk during match.";
	}
	return Super.GetDescriptionText(PropName);
}

defaultproperties
{
     Perks(0)="ServerPerksP.SRVetSupportSpec"
     Perks(1)="ServerPerksP.SRVetBerserker"
     Perks(2)="ServerPerksP.SRVetCommando"
     Perks(3)="ServerPerksP.SRVetFieldMedic"
     Perks(4)="ServerPerksP.SRVetFirebug"
     Perks(5)="ServerPerksP.SRVetSharpshooter"
     Perks(6)="ServerPerksP.SRVetDemolitions"
     TraderInventory(0)="5:KFMod.MP7MPickup"
     TraderInventory(1)="5:KFMod.BlowerThrowerPickup"
     TraderInventory(2)="5:KFMod.MP5MPickup"
     TraderInventory(3)="5:KFMod.M7A3MPickup"
     TraderInventory(4)="5:KFMod.KrissMPickup"
     TraderInventory(5)="2:KFMod.ShotgunPickup"
     TraderInventory(6)="2:KFMod.KSGPickup"
     TraderInventory(7)="2:KFMod.BoomStickPickup"
     TraderInventory(8)="2:KFMod.BenelliPickup"
     TraderInventory(9)="2:KFMod.AA12Pickup"
     TraderInventory(10)="2:KFMod.NailGunPickup"
     TraderInventory(11)="2:KFMod.SPShotGunPickup"
     TraderInventory(12)="1:KFMod.DualiesPickup"
     TraderInventory(13)="1:KFMod.MK23Pickup"
     TraderInventory(14)="1:KFMod.DualMK23Pickup"
     TraderInventory(15)="1:KFMod.Magnum44Pickup"
     TraderInventory(16)="1:KFMod.Dual44MagnumPickup"
     TraderInventory(17)="1:KFMod.DeaglePickup"
     TraderInventory(18)="1:KFMod.DualDeaglePickup"
     TraderInventory(19)="3:KFMod.WinchesterPickup"
     TraderInventory(20)="3:KFMod.CrossbowPickup"
     TraderInventory(21)="3:KFMod.M14EBRPickup"
     TraderInventory(22)="3:KFMod.M99Pickup"
     TraderInventory(23)="3:KFMod.SPSniperPickup"
     TraderInventory(24)="4:KFMod.BullpupPickup"
     TraderInventory(25)="4:KFMod.AK47Pickup"
     TraderInventory(26)="4:KFMod.MKb42Pickup"
     TraderInventory(27)="4:KFMod.M4Pickup"
     TraderInventory(28)="4:KFMod.SCARMK17Pickup"
     TraderInventory(29)="4:KFMod.FNFAL_ACOG_Pickup"
     TraderInventory(30)="4:KFMod.ThompsonPickup"
     TraderInventory(31)="4:KFMod.SPThompsonPickup"
     TraderInventory(32)="4:KFMod.ThompsonDrumPickup"
     TraderInventory(33)="0:KFMod.MachetePickup"
     TraderInventory(34)="0:KFMod.AxePickup"
     TraderInventory(35)="0:KFMod.ChainsawPickup"
     TraderInventory(36)="0:KFMod.KatanaPickup"
     TraderInventory(37)="0:KFMod.ClaymoreSwordPickup"
     TraderInventory(38)="0:KFMod.CrossbuzzsawPickup"
     TraderInventory(39)="0:KFMod.ScythePickup"
     TraderInventory(40)="0:KFMod.DwarfAxePickup"
     TraderInventory(41)="7:KFMod.FlameThrowerPickup"
     TraderInventory(42)="2:KFMod.TrenchgunPickup"
     TraderInventory(43)="1:KFMod.FlareRevolverPickup"
     TraderInventory(44)="1:KFMod.DualFlareRevolverPickup"
     TraderInventory(45)="4:KFMod.MAC10Pickup"
     TraderInventory(46)="7:KFMod.HuskGunPickup"
     TraderInventory(47)="6:KFMod.PipeBombPickup"
     TraderInventory(48)="6:KFMod.M79Pickup"
     TraderInventory(49)="6:KFMod.M32Pickup"
     TraderInventory(50)="4:KFMod.M4203Pickup"
     TraderInventory(51)="6:KFMod.SPGrenadePickup"
     TraderInventory(52)="6:KFMod.LAWPickup"
     TraderInventory(53)="6:KFMod.SealSquealPickup"
     TraderInventory(54)="6:KFMod.SeekerSixPickup"
     TraderInventory(55)="8:KFMod.ZEDGunPickup"
     TraderInventory(56)="8:KFMod.ZEDMKIIPickup"
     TraderInventory(57)="2:KFMod.GoldenAA12Pickup"
     TraderInventory(58)="4:KFMod.GoldenAK47Pickup"
     TraderInventory(59)="2:KFMod.GoldenBenelliPickup"
     TraderInventory(60)="0:KFMod.GoldenChainsawPickup"
     TraderInventory(61)="1:KFMod.GoldenDeaglePickup"
     TraderInventory(62)="1:KFMod.GoldenDualDeaglePickup"
     TraderInventory(63)="7:KFMod.GoldenFTPickup"
     TraderInventory(64)="0:KFMod.GoldenKatanaPickup"
     TraderInventory(65)="6:KFMod.GoldenM79Pickup"
     TraderInventory(66)="4:KFMod.CamoMP5MPickup"
     TraderInventory(67)="2:KFMod.CamoShotgunPickup"
     TraderInventory(68)="4:KFMod.CamoM4Pickup"
     TraderInventory(69)="6:KFMod.CamoM32Pickup"
     TraderInventory(70)="4:KFMod.NeonAK47Pickup"
     TraderInventory(71)="5:KFMod.NeonKrissMPickup"
     TraderInventory(72)="2:KFMod.NeonKSGPickup"
     TraderInventory(73)="4:KFMod.NeonSCARMK17Pickup"
     WeaponCategories(0)="4:Melee"
     WeaponCategories(1)="2:Pistol"
     WeaponCategories(2)="1:Shotgun"
     WeaponCategories(3)="2:Sniper"
     WeaponCategories(4)="3:Machine Gun"
     WeaponCategories(5)="0:Medic Gun"
     WeaponCategories(6)="6:Explosive"
     WeaponCategories(7)="5:Flame Thrower"
     WeaponCategories(8)="7:Misc"
     MaxPerksLevel=6
     RemotePort=5000
     RequirementScaling=1.000000
     RemoteDatabaseURL="192.168.1.33"
     RemotePassword="Pass"
     RemoteFTPUser="User"
     SmileyTags(0)=(iconTexture="ServerPerks.I_Mad",IconTag=">:(")
     SmileyTags(1)=(iconTexture="ServerPerks.I_Frown",IconTag=":(")
     SmileyTags(2)=(iconTexture="ServerPerks.I_GreenLickB",IconTag=":)")
     SmileyTags(3)=(iconTexture="ServerPerks.I_Tongue",IconTag=":P",bCaseInsensitive=True)
     SmileyTags(4)=(iconTexture="ServerPerks.I_GreenLick",IconTag=":d")
     SmileyTags(5)=(iconTexture="ServerPerks.I_BigGrin",IconTag=":D")
     SmileyTags(6)=(iconTexture="ServerPerks.I_Indiffe",IconTag=":|")
     SmileyTags(7)=(iconTexture="ServerPerks.I_Ohwell",IconTag=":/")
     SmileyTags(8)=(iconTexture="ServerPerks.I_RedFace",IconTag=":*")
     SmileyTags(9)=(iconTexture="ServerPerks.I_RedFace",IconTag=":-*")
     SmileyTags(10)=(iconTexture="ServerPerks.I_Ban",IconTag="Ban?",bCaseInsensitive=True)
     SmileyTags(11)=(iconTexture="ServerPerks.I_Cool",IconTag="B)")
     SmileyTags(12)=(iconTexture="ServerPerks.I_Hmm",IconTag="Hmm")
     SmileyTags(13)=(iconTexture="ServerPerks.I_Scream",IconTag="XD")
     SmileyTags(14)=(iconTexture="ServerPerks.I_Spam",IconTag="SPAM")
     VersionNumber=750
     ServerPerksGroup="Server Perks"
     SRScoreboardType=Class'ServerPerks.SRScoreBoard'
     SRHudType=Class'ServerPerks.SRHUDKillingFloor'
     SRMenuType=Class'ServerPerks.SRInvasionLoginMenu'
     SRControllerType=Class'ServerPerks.KFPCServ'
     bUseEnhancedScoreboard=True
     bEnableWebAdmin=True
     bEnableChatIcons=True
     bEnhancedShoulderView=True
     bFixGrenadeExploit=True
     bAdminEditStats=True
     GroupName="KF-Stats"
     FriendlyName="Server Veterancy Handler V7"
     Description="Use perks as privately on this server config instead of getting from global steam stats."
}
