Class ExtPlayerController extends KFPlayerController;

struct FAdminCmdType
{
	var string Cmd,Info;
};
enum EDmgMsgType
{
	DMG_PawnDamage,
	DMG_EXP,
	DMG_Heal,
};
var string ServerMOTD,PendingMOTD;

var ExtPerkManager ActivePerkManager;
var class<KFGUI_Page> MidGameMenuClass;
var class<Ext_PerkBase> PendingPerkClass;
var private transient rotator OldViewRot;
var private transient float LastMisfireTime,LastFireTime,MisfireTimer;
var private transient byte MisfireCount,MisrateCounter;
var transient float NextSpectateChange,NextCommTime;
var array<FAdminCmdType> AdminCommands;
var transient byte DropCount;
var transient Object UserAPI;
var transient SoundCue BonusMusic;
var transient Object BonusFX;

// Stats
var transient byte TransitListNum;
var transient int TransitIndex;

// Dramatic end-game camera.
var transient vector EndGameCamFocusPos[2],CalcViewLocation;
var transient rotator EndGameCamRot,CalcViewRotation;
var transient float EndGameCamTimer,LastPlayerCalcView;
var transient bool bEndGameCamFocus;

var globalconfig bool bShowFPLegs,bHideNameBeacons,bHideKillMsg,bHideDamageMsg,bHideNumberMsg,bNoMonsterPlayer,bNoScreenShake,bRenderModes,bUseKF2DeathMessages,bUseKF2KillMessages;
var globalconfig int SelectedEmoteIndex;
var bool bMOTDReceived,bNamePlateShown,bNamePlateHidden,bClientHideKillMsg,bClientHideDamageMsg,bClientHideNumbers,bNoDamageTracking,bClientNoZed,bSetPerk;

struct SavedSkins
{
	var int ID;
	var class<KFWeaponDefinition> WepDef;
};
var globalconfig array<SavedSkins> SavedWeaponSkins;

replication
{
	// Things the server should send to the client.
	if ( bNetDirty )
		MidGameMenuClass,ActivePerkManager;
}

simulated function PostBeginPlay()
{
	Super.PostBeginPlay();
	if( WorldInfo.NetMode!=NM_Client && ActivePerkManager==None )
	{
		ActivePerkManager = Spawn(class'ExtPerkManager',Self);
		ActivePerkManager.PlayerOwner = Self;
		ActivePerkManager.PRIOwner = ExtPlayerReplicationInfo(PlayerReplicationInfo);
		if( ActivePerkManager.PRIOwner!=None )
			ActivePerkManager.PRIOwner.PerkManager = ActivePerkManager;
		SetTimer(0.1,true,'CheckPerk');
	}
}
simulated function Destroyed()
{
	if( ActivePerkManager!=None )
		ActivePerkManager.PreNotifyPlayerLeave();
	Super.Destroyed();
	if( ActivePerkManager!=None )
		ActivePerkManager.Destroy();
}
function CheckPerk()
{
	if( CurrentPerk!=ActivePerkManager )
	{
		CurrentPerk = ActivePerkManager;
		if( KFPlayerReplicationInfo(PlayerReplicationInfo)!=None )
		{
			KFPlayerReplicationInfo(PlayerReplicationInfo).NetPerkIndex = 0;
			KFPlayerReplicationInfo(PlayerReplicationInfo).CurrentPerkClass = ActivePerkManager.Class;
		}
	}
}

reliable client function AddAdminCmd( string S )
{
	local int i,j;
	
	j = InStr(S,":");
	i = AdminCommands.Length;
	AdminCommands.Length = i+1;
	if( j==-1 )
	{
		AdminCommands[i].Cmd = S;
		AdminCommands[i].Info = S;
	}
	else
	{
		AdminCommands[i].Cmd = Left(S,j);
		AdminCommands[i].Info = Mid(S,j+1);
	}
}
reliable client function ClientSetHUD(class<HUD> newHUDType)
{
	Super.ClientSetHUD(newHUDType);
	SendServerSettings();
}
reliable client function ClientSetBonus( SoundCue C, Object FX )
{
	BonusMusic = C;
	BonusFX = FX;
}
simulated final function SendServerSettings()
{
	if( LocalPlayer(Player)!=None )
		ServerSetSettings(bHideKillMsg,bHideDamageMsg,bHideNumberMsg,bNoMonsterPlayer);
}
reliable server function ServerSetSettings( bool bHideKill, bool bHideDmg, bool bHideNum, bool bNoZ )
{
	bClientHideKillMsg = bHideKill;
	bClientHideDamageMsg = bHideDmg;
	bClientHideNumbers = bHideNum;
	bNoDamageTracking = (bHideDmg && bHideNum);
	bClientNoZed = bNoZ;
}
unreliable server function NotifyFixed( byte Mode )
{
	if( Mode==1 && (Pawn==None || (WorldInfo.TimeSeconds-Pawn.SpawnTime)<5.f) )
		return;
	OnClientFixed(Self,Mode);
	if( Default.bRenderModes && ExtPlayerReplicationInfo(PlayerReplicationInfo)!=None )
		ExtPlayerReplicationInfo(PlayerReplicationInfo).SetFixedData(Mode);
}
delegate OnClientFixed( ExtPlayerController PC, byte Mode );

reliable client event ReceiveLocalizedMessage( class<LocalMessage> Message, optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2, optional Object OptionalObject )
{
	if( Message!=class'KFLocalMessage_PlayerKills' && (Message!=class'KFLocalMessage_Game' || (Switch!=KMT_Suicide && Switch!=KMT_Killed)) )
		Super.ReceiveLocalizedMessage(Message,Switch,RelatedPRI_1,RelatedPRI_2,OptionalObject);
}

function AddZedKill( class<KFPawn_Monster> MonsterClass, byte Difficulty, class<DamageType> DT, bool bKiller )
{
	// Stats.
	if( ActivePerkManager!=None )
	{
		ActivePerkManager.TotalKills++;
		ActivePerkManager.PRIOwner.RepKills++;
	}
}

unreliable client function ClientPlayCameraShake( CameraShake Shake, optional float Scale=1.f, optional bool bTryForceFeedback, optional ECameraAnimPlaySpace PlaySpace=CAPS_CameraLocal, optional rotator UserPlaySpaceRot )
{
	if( !bNoScreenShake )
		Super.ClientPlayCameraShake(Shake,Scale,bTryForceFeedback,PlaySpace,UserPlaySpaceRot);
}

exec final function AwardXP( int XP, optional byte Mode )
{
	if( WorldInfo.NetMode!=NM_Client && ActivePerkManager!=None )
		ActivePerkManager.EarnedEXP(XP,Mode);
}

/** Perk xp stat */
function OnPlayerXPAdded(INT XP, class<KFPerk> PerkClass)
{
	AwardXP(XP);
}

function AddSmallRadiusKill( byte Difficulty, class<KFPerk> PerkClass )
{
	AwardXP(class'KFPerk_Berserker'.static.GetSmallRadiusKillXP(Difficulty));
}
function AddWeldPoints( int PointsWelded )
{
	AwardXP(PointsWelded,1);
}
function AddHealPoints( int PointsHealed )
{
	AwardXP(PointsHealed,2);
}

function AddShotsHit( int AddedHits )
{
	local KFWeapon W;
	local float T;

	Super.AddShotsHit(AddedHits);
	W = KFWeapon(Pawn.Weapon);
	if( W==None )
	{
		if( LastMisfireTime>WorldInfo.TimeSeconds )
		{
			if( ++MisfireCount>15 && (WorldInfo.TimeSeconds-MisfireTimer)>10.f )
				NotifyFixed(8);
			LastMisfireTime = WorldInfo.TimeSeconds+2.f;
			return;
		}
		MisfireCount = 0;
		LastMisfireTime = WorldInfo.TimeSeconds+2.f;
		MisfireTimer = WorldInfo.TimeSeconds;
		return;
	}
	if( !W.HasAmmo(W.CurrentFireMode) )
	{
		if( LastMisfireTime>WorldInfo.TimeSeconds )
		{
			if( ++MisfireCount>15 && (WorldInfo.TimeSeconds-MisfireTimer)>10.f )
				NotifyFixed(16);
			LastMisfireTime = WorldInfo.TimeSeconds+2.f;
			return;
		}
		MisfireCount = 0;
		LastMisfireTime = WorldInfo.TimeSeconds+2.f;
		MisfireTimer = WorldInfo.TimeSeconds;
		return;
	}
	T = W.GetFireInterval(W.CurrentFireMode);
	ActivePerkManager.ModifyRateOfFire(T,W);
	if( (WorldInfo.TimeSeconds-LastFireTime)<(T*0.5) || !W.IsFiring() )
	{
		if( (WorldInfo.TimeSeconds-LastFireTime)>4.f )
			MisrateCounter = 0;
		LastFireTime = WorldInfo.TimeSeconds;
		if( MisrateCounter<5 )
		{
			++MisrateCounter;
			return;
		}
		if( LastMisfireTime>WorldInfo.TimeSeconds )
		{
			if( ++MisfireCount>15 && (WorldInfo.TimeSeconds-MisfireTimer)>10.f )
				NotifyFixed(2);
			LastMisfireTime = WorldInfo.TimeSeconds+1.f;
			return;
		}
		MisfireCount = 0;
		LastMisfireTime = WorldInfo.TimeSeconds+1.f;
		MisfireTimer = WorldInfo.TimeSeconds;
	}
	else MisrateCounter = 0;
}

// Message of the day.
Delegate OnSetMOTD( ExtPlayerController PC, string S );
reliable client function ReceiveServerMOTD( string S, bool bFinal )
{
	ServerMOTD $= S;
	bMOTDReceived = bFinal;
}
reliable server function ServerSetMOTD( string S, bool bFinal )
{
	PendingMOTD $= S;
	if( bFinal && PendingMOTD!="" )
	{
		OnSetMOTD(Self,PendingMOTD);
		PendingMOTD = "";
	}
}

// TESTING:
reliable server function ServerItemDropGet( string Item )
{
	if( DropCount>5 || Len(Item)>100 )
		return;
	++DropCount;
	WorldInfo.Game.Broadcast(Self,PlayerReplicationInfo.GetHumanReadableName()$" got item: "$Item);
}

reliable client function ReceiveLevelUp( Ext_PerkBase Perk, int NewLevel )
{
	if( Perk!=None )
		MyGFxHUD.LevelUpNotificationWidget.ShowAchievementNotification(class'KFGFxWidget_LevelUpNotification'.Default.LevelUpString, Perk.PerkName, class'KFGFxWidget_LevelUpNotification'.Default.TierUnlockedString, Perk.GetPerkIconPath(NewLevel), false, NewLevel);
}
reliable client function ReceiveKillMessage( class<Pawn> Victim, optional bool bGlobal, optional PlayerReplicationInfo KillerPRI )
{
	if( bHideKillMsg || (bGlobal && KillerPRI==None) )
		return;
	if( bUseKF2KillMessages )
	{
		if( MyGFxHUD != none )
		{
			ExtMoviePlayer_HUD(MyGFxHUD).ShowKillMessageX( (bGlobal ? KillerPRI : None), None, ,false, Victim );
		}
	}
	else if( KFExtendedHUD(myHUD)!=None && Victim!=None )
		KFExtendedHUD(myHUD).AddKillMessage(Victim,1,KillerPRI,byte(bGlobal));
}
unreliable client function ReceiveDamageMessage( class<Pawn> Victim, int Damage )
{
	if( !bHideDamageMsg && KFExtendedHUD(myHUD)!=None && Victim!=None )
		KFExtendedHUD(myHUD).AddKillMessage(Victim,Damage,None,2);
}
unreliable client function ClientNumberMsg( int Count, vector Pos, EDmgMsgType Type )
{
	if( !bHideNumberMsg && KFExtendedHUD(myHUD)!=None )
		KFExtendedHUD(myHUD).AddNumberMsg(Count,Pos,Type);
}

reliable client event TeamMessage( PlayerReplicationInfo PRI, coerce string S, name Type, optional float MsgLifeTime  )
{
	//if( ( ( Type == 'Say' ) || (Type == 'TeamSay' ) ) && ( PRI != None ) )
	//	SpeakTTS( S, PRI ); <- KF built without TTS...

	// since this is on the client, we can assume that if Player exists, it is a LocalPlayer
	if( Player!=None )
	{
		if( ( ( Type == 'Say' ) || ( Type == 'TeamSay' ) ) && ( PRI != None ) )
			S = PRI.GetHumanReadableName()$": "$S;
		LocalPlayer( Player ).ViewportClient.ViewportConsole.OutputText( "("$Type$") "$S );
	}
	
	if (MyGFxManager != none && MyGFxManager.PartyWidget != none)
    {
    	if( !MyGFxManager.PartyWidget.ReceiveMessage(S) )  //Fails if message is for updating perks in a steam lobby
    		return;
    }

    if( MyGFxHUD != none )
    {
		switch( Type )
		{
		case 'Log':
			break; // Console only message.
		case 'Music':
			MyGFxHUD.MusicNotification.ShowSongInfo(S);
			break;
		case 'Event':
			MyGFxHUD.HudChatBox.AddChatMessage(S, class 'KFLocalMessage'.default.DefaultColor);
			break;
		case 'DeathMessage':
			//MyGFxHUD.HudChatBox.AddChatMessage(S, "FF0000"); // Console message only.
			break;
		case 'Say':
		case 'TeamSay':
			if( ExtPlayerReplicationInfo(PRI)!=None && ExtPlayerReplicationInfo(PRI).ShowAdminName() )
				MyGFxHUD.HudChatBox.AddChatMessage("("$ExtPlayerReplicationInfo(PRI).GetAdminNameAbr()$")"$S, ExtPlayerReplicationInfo(PRI).GetAdminColor());
    		else MyGFxHUD.HudChatBox.AddChatMessage(S, "64FE2E");
			break;
		case 'Priority':
			MyGFxHUD.HudChatBox.AddChatMessage(S, class 'KFLocalMessage'.default.PriorityColor);
			break;
		case 'CriticalEvent':
			PopScreenMsg(S); // HIGH|Low|Time
			break;
		case 'LowCriticalEvent':
			MyGFxHUD.ShowNonCriticalMessage(S);
			break;
		default:
			MyGFxHUD.HudChatBox.AddChatMessage(class'KFLocalMessage'.default.SystemString@S, class 'KFLocalMessage'.default.EventColor);
		}
	}
}
final function PopScreenMsg( string S )
{
	local int i;
	local string L;
	local float T;
	
	T = 4.f;
	
	// Get lower part.
	i = InStr(S,"|");
	if( i!=-1 )
	{
		L = Mid(S,i+1);
		S = Left(S,i);
		
		// Get time.
		i = InStr(L,"|");
		if( i!=-1 )
		{
			T = float(Mid(L,i+1));
			L = Left(L,i);
		}
	}
	MyGFxHUD.DisplayPriorityMessage(S,L,T);
}
reliable client function ClientKillMessage( class<DamageType> DamType, PlayerReplicationInfo Victim, PlayerReplicationInfo KillerPRI, optional class<Pawn> KillerPawn )
{
	local string Msg,S;
	local bool bFF;

	if( Player==None || Victim==None )
		return;
	
	if( bUseKF2DeathMessages && MyGFxHUD!=None )
	{
		if( Victim==KillerPRI || (KillerPRI==None && KillerPawn==None) ) // Suicide
			ExtMoviePlayer_HUD(MyGFxHUD).ShowKillMessageX( None, Victim, ,true );
		else ExtMoviePlayer_HUD(MyGFxHUD).ShowKillMessageX( KillerPRI, Victim, ,true, KillerPawn );
	}
	if( Victim==KillerPRI || (KillerPRI==None && KillerPawn==None) ) // Suicide
	{
		if( Victim.GetTeamNum()==0 )
		{
			Msg = ParseSuicideMsg(Chr(6)$"O"$Victim.GetHumanReadableName(),DamType);
			class'KFMusicStingerHelper'.static.PlayPlayerDiedStinger(Self);
		}
		else Msg = ParseSuicideMsg(Chr(6)$"K"$Victim.GetHumanReadableName(),DamType);
	}
	else
	{
		if( KillerPRI!=None && Victim.Team!=None && Victim.Team==KillerPRI.Team ) // Team-kill
		{
			bFF = true;
			S = KillerPRI.GetHumanReadableName();
			class'KFMusicStingerHelper'.static.PlayTeammateDeathStinger(Self);
		}
		else // Killed by monster.
		{
			bFF = false;
			if( KillerPRI!=None )
			{
				S = KillerPRI.GetHumanReadableName();
			}
			else
			{
				S = class'KFExtendedHUD'.Static.GetNameOf(KillerPawn);
				if( class<KFPawn_Monster>(KillerPawn)!=None && class<KFPawn_Monster>(KillerPawn).Default.MinSpawnSquadSizeType==EST_Boss ) // Boss type.
					S = "the "$S;
				else S = class'KFExtendedHUD'.Static.GetNameArticle(S)@S;
			}
			class'KFMusicStingerHelper'.static.PlayZedKillHumanStinger(Self);
		}
		Msg = ParseKillMsg(Victim.GetHumanReadableName(),S,bFF,DamType);
	}
	S = Class'KFExtendedHUD'.Static.StripMsgColors(Msg);
	if( !bUseKF2DeathMessages )
		KFExtendedHUD(myHUD).AddDeathMessage(Msg,S);
	ClientMessage(S,'DeathMessage');
}
reliable client function ClientZedKillMessage( class<DamageType> DamType, string Victim, optional PlayerReplicationInfo KillerPRI, optional class<Pawn> KillerPawn, optional bool bFFKill )
{
	local string Msg,S;

	if( Player==None )
		return;
	if( bUseKF2DeathMessages && MyGFxHUD!=None )
	{
		if( KillerPRI==None && KillerPawn==None ) // Suicide
			ExtMoviePlayer_HUD(MyGFxHUD).ShowKillMessageX( None, None, Victim, true );
		else ExtMoviePlayer_HUD(MyGFxHUD).ShowKillMessageX( KillerPRI, None, Victim, true, KillerPawn );
	}
	if( KillerPRI==None && KillerPawn==None ) // Suicide
	{
		Msg = ParseSuicideMsg(Chr(6)$"O"$Victim,DamType);
	}
	else
	{
		if( KillerPRI!=None ) // Team-kill
		{
			S = KillerPRI.GetHumanReadableName();
		}
		else // Killed by monster.
		{
			S = class'KFExtendedHUD'.Static.GetNameOf(KillerPawn);
			if( class<KFPawn_Monster>(KillerPawn)!=None && class<KFPawn_Monster>(KillerPawn).Default.MinSpawnSquadSizeType==EST_Boss ) // Boss type.
				S = "the "$S;
			else S = class'KFExtendedHUD'.Static.GetNameArticle(S)@S;
		}
		Msg = ParseKillMsg(Victim,S,bFFKill,DamType);
	}
	S = Class'KFExtendedHUD'.Static.StripMsgColors(Msg);
	if( !bUseKF2DeathMessages )
		KFExtendedHUD(myHUD).AddDeathMessage(Msg,S);
	ClientMessage(S,'DeathMessage');
}
simulated final function string ParseSuicideMsg( string Victim, class<DamageType> DamType )
{
	local string S;

	S = string(DamType.Name);
	if( Left(S,15)~="KFDT_Ballistic_" )
	{
		S = Mid(S,15); // Weapon name.
		return Victim$Chr(6)$"M killed himself with "$S;
	}
	else if( class<KFDT_Fire>(DamType)!=None )
		return Victim$Chr(6)$"M was burned to death";
	else if( class<KFDT_Explosive>(DamType)!=None )
		return Victim$Chr(6)$"M was blown into pieces";
	return Victim$Chr(6)$"M had a sudden heart attack";
}
simulated final function string ParseKillMsg( string Victim, string Killer, bool bFF, class<DamageType> DamType )
{
	local string T,S;

	T = (bFF ? "O" : "K");
	S = string(DamType.Name);
	if( Left(S,15)~="KFDT_Ballistic_" )
	{
		S = Mid(S,15); // Weapon name.
		return Chr(6)$"O"$Victim$Chr(6)$"M was killed by "$Chr(6)$T$Killer$Chr(6)$"M's "$S;
	}
	else if( class<KFDT_Fire>(DamType)!=None )
		return Chr(6)$"O"$Victim$Chr(6)$"M was incinerated by "$Chr(6)$T$Killer;
	else if( class<KFDT_Explosive>(DamType)!=None )
		return Chr(6)$"O"$Victim$Chr(6)$"M was blown up by "$Chr(6)$T$Killer;
	return Chr(6)$"O"$Victim$Chr(6)$"M was killed by "$Chr(6)$T$Killer;
}

reliable server function ServerCamera( name NewMode )
{
	// <- REMOVED CAMERA LOGGING (PlayerController)
	if ( NewMode == '1st' )
    	NewMode = 'FirstPerson';
    else if ( NewMode == '3rd' )
    	NewMode = 'ThirdPerson';
	SetCameraMode( NewMode );
}
exec function Camera( name NewMode )
{
	ServerCamera( PlayerCamera.CameraStyle=='FirstPerson' ? 'ThirdPerson' : 'FirstPerson' );
}
simulated final function ToggleFPBody( bool bEnable )
{
	bShowFPLegs = bEnable;
	Class'ExtPlayerController'.Default.bShowFPLegs = bEnable;
	
	if( ExtHumanPawn(Pawn)!=None )
		ExtHumanPawn(Pawn).UpdateFPLegs();
}

/*exec function KickBan( string S )
{
	if( WorldInfo.Game!=None )
		WorldInfo.Game.KickBan(S);
}*/
exec function Kick( string S )
{
	if( WorldInfo.Game!=None )
		WorldInfo.Game.Kick(S);
}
reliable server function SkipLobby();

Delegate OnChangePerk( ExtPlayerController PC, class<Ext_PerkBase> NewPerk );

reliable server function SwitchToPerk( class<Ext_PerkBase> PerkClass )
{
	if( PerkClass!=None )
		OnChangePerk(Self,PerkClass);
}

Delegate OnBoughtStats( ExtPlayerController PC, class<Ext_PerkBase> PerkClass, int iStat, int Amount );

reliable server function BuyPerkStat( class<Ext_PerkBase> PerkClass, int iStat, int Amount )
{
	if( PerkClass!=None && Amount>0 && iStat>=0 )
		OnBoughtStats(Self,PerkClass,iStat,Amount);
}

Delegate OnBoughtTrait( ExtPlayerController PC, class<Ext_PerkBase> PerkClass, class<Ext_TraitBase> Trait );

reliable server function BoughtTrait( class<Ext_PerkBase> PerkClass, class<Ext_TraitBase> Trait )
{
	if( PerkClass!=None && Trait!=None )
		OnBoughtTrait(Self,PerkClass,Trait);
}

Delegate OnPerkReset( ExtPlayerController PC, class<Ext_PerkBase> PerkClass, bool bPrestige );

reliable server function ServerResetPerk( class<Ext_PerkBase> PerkClass, bool bPrestige )
{
	if( PerkClass!=None )
		OnPerkReset(Self,PerkClass,bPrestige);
}

Delegate OnAdminHandle( ExtPlayerController PC, int PlayerID, int Action );

reliable server function AdminRPGHandle( int PlayerID, int Action )
{
	OnAdminHandle(Self,PlayerID,Action);
}

simulated reliable client event bool ShowConnectionProgressPopup( EProgressMessageType ProgressType, string ProgressTitle, string ProgressDescription, bool SuppressPasswordRetry = false)
{
	switch(ProgressType)
	{
	case    PMT_ConnectionFailure :
	case    PMT_PeerConnectionFailure :
		KFExtendedHUD(myHUD).NotifyLevelChange();
		KFExtendedHUD(myHUD).ShowProgressMsg("Connection Error: "$ProgressTitle$"|"$ProgressDescription$"|Disconnecting...",true);
		return true;
	case    PMT_DownloadProgress :
		KFExtendedHUD(myHUD).NotifyLevelChange();
	case	PMT_AdminMessage :
		KFExtendedHUD(myHUD).ShowProgressMsg(ProgressTitle$"|"$ProgressDescription);
		return true;
	}
	return false;
}

simulated function CancelConnection()
{
	if( KFExtendedHUD(myHUD)!=None )
		KFExtendedHUD(myHUD).CancelConnection();
	else class'Engine'.Static.GetEngine().GameViewport.ConsoleCommand("Disconnect");
}

function NotifyLevelUp(class<KFPerk> PerkClass, byte PerkLevel, byte NewPrestigeLevel);

function ShowBossNameplate( KFInterface_MonsterBoss KFBoss, optional string PlayerName)
{
	if( !bNamePlateShown ) // Dont make multiple bosses pop this up multiple times.
	{
		bNamePlateShown = true;
		Super.ShowBossNameplate(KFBoss,PlayerName);
		SetTimer(8,false,'HideBossNameplate'); // MAKE sure it goes hidden.
	}
}
function HideBossNameplate()
{	
	if( !bNamePlateHidden )
	{
		bNamePlateHidden = false;
		Super.HideBossNameplate();
		ClearTimer('HideBossNameplate');
		if( MyGFxHUD!=None )
			MyGFxHUD.MusicNotification.SetVisible(true);
	}
}

function UpdateRotation( float DeltaTime )
{
	if( OldViewRot!=Rotation && Pawn!=None && Pawn.IsAliveAndWell() )
		NotifyFixed(1);
	Super.UpdateRotation(DeltaTime);
	OldViewRot = Rotation;
}

reliable server function ServerGetUnloadInfo( byte CallID, class<Ext_PerkBase> PerkClass, bool bUnload )
{
	OnRequestUnload(Self,CallID,PerkClass,bUnload);
}
delegate OnRequestUnload( ExtPlayerController PC, byte CallID, class<Ext_PerkBase> PerkClass, bool bUnload );

reliable client function ClientGotUnloadInfo( byte CallID, byte Code, optional int DataA, optional int DataB )
{
	OnClientGetResponse(CallID,Code,DataA,DataB);
}
delegate OnClientGetResponse( byte CallID, byte Code, int DataA, int DataB );
function DefClientResponse( byte CallID, byte Code, int DataA, int DataB );

reliable client function ClientUsedAmmo( Ext_T_SupplierInteract S )
{
	if( Pawn!=None && S!=None )
		S.UsedOnClient(Pawn);
}

unreliable server function ServerNextSpectateMode()
{
	local Pawn HumanViewTarget;

	if( !IsSpectating() )
		return;

	// switch to roaming if human viewtarget is dead
	if( CurrentSpectateMode != SMODE_Roaming )
	{
		HumanViewTarget = Pawn(ViewTarget);
		if( HumanViewTarget == none || !HumanViewTarget.IsAliveAndWell() )
		{
			SpectateRoaming();
			return;
		}
	}

	switch ( CurrentSpectateMode )
	{
	case SMODE_PawnFreeCam:
		SpectatePlayer( SMODE_PawnThirdPerson );
		break;
	case SMODE_PawnThirdPerson:
		SpectatePlayer( SMODE_PawnFirstPerson );
		break;
	case SMODE_PawnFirstPerson:
	case SMODE_Roaming:
		SpectatePlayer( SMODE_PawnFreeCam );
		break;
	}
}

function ViewAPlayer(int dir)
{
	local PlayerReplicationInfo PRI;

	PRI = GetNextViewablePlayer(dir);
	if ( PRI!=None )
	{
		SetViewTarget(PRI);
		ClientMessage("Now viewing from "$PRI.GetHumanReadableName());
	}
}

exec function ViewPlayerID( int ID )
{
	ServerViewPlayerID(ID);
}
reliable server function ServerViewPlayerID( int ID )
{
	local PlayerReplicationInfo PRI;

	if( !IsSpectating() )
		return;

	// Find matching player by ID
	foreach WorldInfo.GRI.PRIArray(PRI)
	{
		if ( PRI.PlayerID==ID )
			break;
	}
	if( PRI==None || PRI.PlayerID!=ID || Controller(PRI.Owner)==None || Controller(PRI.Owner).Pawn==None || !WorldInfo.Game.CanSpectate(self, PRI) )
		return;
	
	SetViewTarget(PRI);
	ClientMessage("Now viewing from "$PRI.GetHumanReadableName());
	if( CurrentSpectateMode==SMODE_Roaming )
		SpectatePlayer( SMODE_PawnFreeCam );
}

reliable server function SpectateRoaming()
{
	local Pawn P;
	
	P = Pawn(ViewTarget);
	ClientMessage("Viewing from own camera.");
	Super.SpectateRoaming();
	if( P!=None )
	{
		SetLocation(P.Location);
		SetRotation(P.GetViewRotation());
		ClientSetLocation(Location,Rotation);
	}
}
reliable client function ClientSetLocation( vector NewLocation, rotator NewRotation )
{
	SetLocation(NewLocation);
	Super.ClientSetLocation(NewLocation,NewRotation);
}

unreliable server function ServerPlayLevelUpDialog()
{
	if( NextCommTime<WorldInfo.TimeSeconds )
	{
		NextCommTime = WorldInfo.TimeSeconds+2.f;
		Super.ServerPlayLevelUpDialog();
	}
}
unreliable server function ServerPlayVoiceCommsDialog( int CommsIndex )
{
	if( NextCommTime<WorldInfo.TimeSeconds )
	{
		NextCommTime = WorldInfo.TimeSeconds+2.f;
		Super.ServerPlayVoiceCommsDialog(CommsIndex);
	}
}

// The player wants to fire.
// Setup bFire/bAltFire so that Auto-Fire trait will work.
exec function StartFire( optional byte FireModeNum )
{
	if( FireModeNum==0 )
		bFire = 1;
	else if( FireModeNum==1 )
		bAltFire = 1;
	Super.StartFire(FireModeNum);
}
exec function StopFire( optional byte FireModeNum )
{
	if( FireModeNum==0 )
		bFire = 0;
	else if( FireModeNum==1 )
		bAltFire = 0;
	Super.StopFire(FireModeNum);
}

state Spectating
{
	function BeginState(Name PreviousStateName)
	{
		Super.BeginState(PreviousStateName);
		bCollideWorld = false;
	}
	function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
	{
		Acceleration = Normal(NewAccel) * SpectatorCameraSpeed;
		Velocity = Acceleration;
		MoveSmooth( Acceleration * DeltaTime );
	}
	function PlayerMove(float DeltaTime)
	{
		local vector X,Y,Z;
		local rotator OldRotation;

		OldRotation = Rotation;
		GetAxes(Rotation,X,Y,Z);
		Acceleration = (Normal(PlayerInput.aForward*X + PlayerInput.aStrafe*Y + PlayerInput.aUp*vect(0,0,1)) - bDuck*vect(0,0,1))*100.f;
		UpdateRotation(DeltaTime);

		if (Role < ROLE_Authority) // then save this move and replicate it
		{
			ReplicateMove(DeltaTime, Acceleration, DCLICK_None, rot(0,0,0));
			
			// only done for clients, as LastActiveTime only affects idle kicking
			if( (!IsZero(Acceleration) || OldRotation != Rotation) && LastUpdateSpectatorActiveTime<WorldInfo.TimeSeconds )
			{
				LastUpdateSpectatorActiveTime = WorldInfo.TimeSeconds+UpdateSpectatorActiveInterval;
				ServerSetSpectatorActive();
			}
		}
		else
		{
			ProcessMove(DeltaTime, Acceleration, DCLICK_None, rot(0,0,0));
		}
	}
	exec function SpectateNextPlayer()
	{
		SpectateRoaming();
	}
	exec function SpectatePreviousPlayer()
	{
		ServerViewNextPlayer();
		if( Role == ROLE_Authority )
		{
			NotifyChangeSpectateViewTarget();
		}
	}
	unreliable server function ServerViewNextPlayer()
	{
		if( CurrentSpectateMode==SMODE_Roaming )
		{
			CurrentSpectateMode = SMODE_PawnFreeCam;
			SetCameraMode('FreeCam');
		}
		Global.ServerViewNextPlayer();
	}
	reliable client function ClientSetCameraMode( name NewCamMode )
	{
		Global.ClientSetCameraMode(NewCamMode);
		if( NewCamMode=='FirstPerson' && ViewTarget==Self && MyGFxHUD!=None )
			MyGFxHUD.SpectatorInfoWidget.SetSpectatedKFPRI(None); // Possibly went to first person, hide player info.
	}
}

// Feign death:
function EnterRagdollMode( bool bEnable )
{
	if( bEnable )
		GoToState('RagdollMove');
	else if( Pawn==None )
		GotoState('Dead');
	else if ( Pawn.PhysicsVolume.bWaterVolume )
		GotoState(Pawn.WaterMovementState);
	else GotoState(Pawn.LandMovementState);
}

// Optional dramatic end-game camera!
simulated function EndGameCamFocus( vector Pos )
{
	local vector CamPos;
	local rotator CamRot;

	GetPlayerViewPoint(CamPos,CamRot);
	bEndGameCamFocus = true;
	EndGameCamFocusPos[0] = Pos;
	EndGameCamFocusPos[1] = CamPos;
	EndGameCamRot = CamRot;
	EndGameCamTimer = WorldInfo.RealTimeSeconds;
	
	if( LocalPlayer(Player)==None )
		ClientFocusView(Pos);
	else if( KFPawn(ViewTarget)!=None )
		KFPawn(ViewTarget).SetMeshVisibility(true);
}
reliable client function ClientFocusView( vector Pos )
{
	if( WorldInfo.NetMode==NM_Client )
		EndGameCamFocus(Pos);
}
final function bool CalcEndGameCam()
{
	local float T,RT;
	local vector HL,HN;

	if( LastPlayerCalcView==WorldInfo.TimeSeconds )
		return true;

	T = WorldInfo.RealTimeSeconds-EndGameCamTimer;
	
	if( T>=20.f ) // Finished view.
	{
		bEndGameCamFocus = false;
		if( LocalPlayer(Player)!=None && KFPawn(ViewTarget)!=None )
			KFPawn(ViewTarget).SetMeshVisibility(!Global.UsingFirstPersonCamera());
		return false;
	}
	// Setup other cache params.
	LastPlayerCalcView	= WorldInfo.TimeSeconds;

	CalcViewLocation.Z = 1.f;
	RT = WorldInfo.RealTimeSeconds;
	if( T<4.f )
		RT += (4.f-T);
	CalcViewLocation.X = Sin(RT*0.08f);
	CalcViewLocation.Y = Cos(RT*0.08f);
	CalcViewLocation = EndGameCamFocusPos[0] + Normal(CalcViewLocation)*350.f;
	if( Trace(HL,HN,CalcViewLocation,EndGameCamFocusPos[0],false,vect(16,16,16))!=None )
		CalcViewLocation = HL;

	CalcViewRotation = rotator(EndGameCamFocusPos[0]-CalcViewLocation);

	if( T<4.f && LocalPlayer(Player)!=None ) // Zoom in to epic death.
	{
		T*=0.25;
		CalcViewLocation = CalcViewLocation*T + EndGameCamFocusPos[1]*(1.f-T);
		CalcViewRotation = RLerp(EndGameCamRot,CalcViewRotation,T,true);
	}
	return true;
}
simulated event GetPlayerViewPoint( out vector out_Location, out Rotator out_Rotation )
{
	if( bEndGameCamFocus && CalcEndGameCam() )
	{
		out_Location = CalcViewLocation;
		out_Rotation = CalcViewRotation;
		return;
	}
	Super.GetPlayerViewPoint(out_Location,out_Rotation);
}
exec function DebugRenderMode()
{
	if( WorldInfo.NetMode!=NM_Client )
	{
		bRenderModes = !bRenderModes;
		SaveConfig();
		ClientMessage(bRenderModes);
	}
}

// Stats traffic.
reliable server function ServerRequestStats( byte ListNum )
{
	if( ListNum<3 )
	{
		TransitListNum = ListNum;
		TransitIndex = 0;
		SetTimer(0.001,true,'SendNextList');
	}
}
function SendNextList()
{
	if( !OnClientGetStat(Self,TransitListNum,TransitIndex++) )
	{
		ClientGetStat(TransitListNum,true);
		ClearTimer('SendNextList');
	}
}
simulated reliable client function ClientGetStat( byte ListNum, bool bFinal, optional string N, optional UniqueNetId ID, optional int V )
{
	OnClientReceiveStat(ListNum,bFinal,N,ID,V);
}

Delegate OnClientReceiveStat( byte ListNum, bool bFinal, string N, UniqueNetId ID, int V );
Delegate bool OnClientGetStat( ExtPlayerController PC, byte ListNum, int StatIndex );

reliable server function ChangeSpectateMode( bool bSpectator )
{
	OnSpectateChange(Self,bSpectator);
}
simulated reliable client function ClientSpectateMode( bool bSpectator )
{
	UpdateURL("SpectatorOnly",(bSpectator ? "1" : "0"),false);
}
Delegate OnSpectateChange( ExtPlayerController PC, bool bSpectator );

state RagdollMove extends PlayerWalking
{
Ignores NotifyPhysicsVolumeChange,ServerCamera,ResetCameraMode;

	event BeginState(Name PreviousStateName)
	{
		FOVAngle = DesiredFOV;

		if( WorldInfo.NetMode!=NM_Client )
			SetCameraMode('ThirdPerson');
	}
	event EndState(Name NewState)
	{
		FOVAngle = DesiredFOV;

		if( Pawn!=none && NewState!='Dead' )
			Global.SetCameraMode('FirstPerson');
	}
	function PlayerMove( float DeltaTime )
	{
		local rotator			OldRotation;

		if( Pawn == None )
			GotoState('Dead');
		else
		{
			// Update rotation.
			OldRotation = Rotation;
			UpdateRotation( DeltaTime );
			bDoubleJump = false;
			bPressedJump = false;

			if( Role < ROLE_Authority ) // then save this move and replicate it
				ReplicateMove(DeltaTime, vect(0,0,0), DCLICK_None, OldRotation - Rotation);
			else ProcessMove(DeltaTime, vect(0,0,0), DCLICK_None, OldRotation - Rotation);
		}
	}
	simulated event GetPlayerViewPoint( out vector out_Location, out Rotator out_Rotation )
	{
		local Actor TheViewTarget;
		local vector HL,HN,EndOffset;

		if( bEndGameCamFocus && CalcEndGameCam() )
		{
			out_Location = CalcViewLocation;
			out_Rotation = CalcViewRotation;
			return;
		}
		if( Global.UsingFirstPersonCamera() )
			Global.GetPlayerViewPoint(out_Location,out_Rotation);
		else
		{
			out_Rotation = Rotation;
			TheViewTarget = GetViewTarget();
			if( TheViewTarget==None )
				TheViewTarget = Self;
			out_Location = TheViewTarget.Location;
			EndOffset = out_Location-vector(Rotation)*250.f;
			
			if( TheViewTarget.Trace(HL,HN,EndOffset,out_Location,false,vect(16,16,16))!=None )
				out_Location = HL;
			else out_Location = EndOffset;
		}
	}
}

state PlayerWalking
{
ignores SeePlayer, HearNoise, Bump;

	function PlayerMove( float DeltaTime )
	{
		local vector			X,Y,Z, NewAccel;
		local eDoubleClickDir	DoubleClickMove;
		local rotator			OldRotation;
		local bool				bSaveJump;

		if( Pawn == None )
		{
			GotoState('Dead');
		}
		else
		{
			GetAxes(Pawn.Rotation,X,Y,Z);
			if( VSZombie(Pawn)!=None )
				VSZombie(Pawn).ModifyPlayerInput(Self,DeltaTime);

			// Update acceleration.
			NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y;
			NewAccel.Z	= 0;
			NewAccel = Pawn.AccelRate * Normal(NewAccel);

			if (IsLocalPlayerController())
			{
				AdjustPlayerWalkingMoveAccel(NewAccel);
			}

			DoubleClickMove = PlayerInput.CheckForDoubleClickMove( DeltaTime/WorldInfo.TimeDilation );

			// Update rotation.
			OldRotation = Rotation;
			UpdateRotation( DeltaTime );
			bDoubleJump = false;

			if( bPressedJump && Pawn.CannotJumpNow() )
			{
				bSaveJump = true;
				bPressedJump = false;
			}
			else
			{
				bSaveJump = false;
			}

			if( Role < ROLE_Authority ) // then save this move and replicate it
			{
				ReplicateMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
			}
			else
			{
				ProcessMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
			}
			bPressedJump = bSaveJump;
		}
	}
}

state Dead
{
	event BeginState(Name PreviousStateName)
	{
		local KFPlayerInput KFPI;

		SetTimer( 5.f, false, nameof(StartSpectate) );
		if ( (Pawn != None) && (Pawn.Controller == self) )
			Pawn.Controller = None;
		Pawn = None;
		FOVAngle = DesiredFOV;
		Enemy = None;
		bPressedJump = false;
		FindGoodView();
		CleanOutSavedMoves();
		
		if( KFPawn(ViewTarget)!=none )
		{
			KFPawn(ViewTarget).SetMeshVisibility(true);
		}

        // Deactivate any post process effects when we die
		ResetGameplayPostProcessFX();

		if(CurrentPerk != none)
			CurrentPerk.PlayerDied();

		KFPI = KFPlayerInput(PlayerInput);
		if(KFPI != none)
			KFPI.HideVoiceComms();

		if( MyGFxManager != none )
			MyGFxManager.CloseMenus();

		if(MyGFxHUD != none )
			MyGFxHUD.ClearBuffIcons();
	}
	simulated event GetPlayerViewPoint( out vector out_Location, out Rotator out_Rotation )
	{
		local Actor TheViewTarget;
		local vector HL,HN,EndOffset;

		if( bEndGameCamFocus && CalcEndGameCam() )
		{
			out_Location = CalcViewLocation;
			out_Rotation = CalcViewRotation;
			return;
		}
		out_Rotation = Rotation;
		TheViewTarget = GetViewTarget();
		if( TheViewTarget==None )
			TheViewTarget = Self;
		out_Location = TheViewTarget.Location;
		EndOffset = out_Location-vector(Rotation)*400.f;
		
		if( TheViewTarget.Trace(HL,HN,EndOffset,out_Location,false,vect(16,16,16))!=None )
			out_Location = HL;
		else out_Location = EndOffset;
	}
}

exec function RequestSwitchTeam()
{
    ConsoleCommand("disconnect");
}

exec function SwitchTeam()
{
    ConsoleCommand("disconnect");
}

defaultproperties
{
	InputClass=Class'ExtPlayerInput'
	PurchaseHelperClass=class'ExtAutoPurchaseHelper'
	bIgnoreEncroachers=true
	SpectatorCameraSpeed=900
	MidGameMenuClass=class'UI_MidGameMenu'
	PerkList.Empty()
	PerkList.Add((PerkClass=Class'ExtPerkManager'))
	
	NVG_DOF_FocalDistance=3800.0
	NVG_DOF_SharpRadius=2500.0
	NVG_DOF_FocalRadius=3500.0
	NVG_DOF_MaxNearBlurSize=0.25
}