//=============================================================================
// PlayerReplicationInfo.
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//
// A PlayerReplicationInfo is created for every player on a server (or in a standalone game).
// Players are PlayerControllers, or other Controllers with bIsPlayer=true
// PlayerReplicationInfos are replicated to all clients, and contain network game relevant information about the player,
// such as playername, score, etc.
//=============================================================================
class PlayerReplicationInfo extends ReplicationInfo
	native(ReplicationInfo)
	nativereplication
	dependson(SoundNodeWave,OnlineSubsystem);

/** Player's current score. */
var repnotify float			Score;

/** Number of player's deaths. */
var int				Deaths;

/** Replicated compressed ping for this player (holds ping in msec divided by 4) */
var byte				Ping;

/** Number of lives used by this player */
var 			int					NumLives;

/** Player name, or blank if none. */
var repnotify string	PlayerName;

/** Voice to use for TTS */
var transient	ETTSSpeaker			TTSSpeaker;

/** Previous playername.  Saved on client-side to detect playername changes. */
var string				OldName;

/** Unique id number. */
var int					PlayerID;

/** Player team */
var editinline RepNotify TeamInfo	Team;

/** Player logged in as Administrator */
var bool				bAdmin;

/** Whether this player is currently a spectator */
var bool				bIsSpectator;

/** Whether this player can only ever be a spectator */
var bool				bOnlySpectator;

/** Whether this player is waiting to enter match */
var bool				bWaitingPlayer;

/** Whether this player has confirmed ready to play */
var bool				bReadyToPlay;

/** Can't respawn once out of lives */
var bool				bOutOfLives;

/** True if this PRI is associated with an AIController */
var bool				bBot;

/** client side flag - whether this player has been welcomed or not (player entered message) */
var	bool				bHasBeenWelcomed;

/** Means this PRI came from the GameInfo's InactivePRIArray */
var repnotify bool bIsInactive;

/** indicates this is a PRI from the previous level of a seamless travel,
 * waiting for the player to finish the transition before creating a new one
 * this is used to avoid preserving the PRI in the InactivePRIArray if the player leaves
 */
var bool bFromPreviousLevel;

/** Elapsed time on server when this PRI was first created.  */
var int					StartTime;

/** Used for reporting player location */
var localized String    StringSpectating;
var localized String	StringUnknown;

/** Kills by this player.  Not replicated. */
var int		Kills;

/** Message class to use for PRI originated localized messages */
var class<GameMessage>	GameMessageClass;

/** Exact ping as float (rounded and compressed in Ping) */
var float				ExactPing;

/** Used to match up InactivePRI with rejoining playercontroller. */
var string				SavedNetworkAddress;

/** The id used by the network to uniquely identify a player.
 * NOTE: this property should *never* be exposed to the player as it's transient
 * and opaque in meaning (ie it might mean date/time followed by something else) */
var repnotify UniqueNetId UniqueId;

/** The session that the player needs to join/remove from as it is created/leaves */
var const name SessionName;

struct native AutomatedTestingDatum
{
	/** Number of matches played (maybe remove this before shipping)  This is really useful for doing soak testing and such to see how long you lasted! NOTE:  This is not replicated out to clients atm. **/
	var int NumberOfMatchesPlayed;

	/** Keeps track of the current run so when we have repeats and such we know how far along we are **/
	var int NumMapListCyclesDone;
};

var AutomatedTestingDatum AutomatedTestingData;

var int StatConnectionCounts;	// Used for Averages;
var int StatPingTotals;
var int StatPingMin;
var int StatPingMax;

var int StatPKLTotal;
var int StatPKLMin;
var int StatPKLMax;

var int StatMaxInBPS, StatAvgInBPS;
var int StatMaxOutBPS, StatAvgOutBPS;

/** The online avatar for this player. May be None if we haven't downloaded it yet, or player doesn't have one. */
var transient Texture2D Avatar;   // not replicated.

//@HSL_BEGIN - BWJ - 4-12-16 - Playfab support
/** The playfab player Id */
var string PlayfabPlayerId;
//@HSL_END

cpptext
{
	// AActor interface.
	INT* GetOptimizedRepList( BYTE* InDefault, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel );
}

replication
{
	// Things the server should send to the client.
	if (bNetDirty)
		Score, Deaths,
		PlayerName, Team, bAdmin,
		bIsSpectator, bOnlySpectator, bWaitingPlayer, bReadyToPlay,
		StartTime, bOutOfLives, bFromPreviousLevel, 
`if(`__TW_)
		Kills, //Added this for Scoreboard.
`endif
		// NOTE: This needs to be replicated to the owning client so don't move it from here
//@HSL_BEGIN - BWJ - 4-12-16 - Playfab support
		UniqueId, PlayfabPlayerId;
//@HSL_END

	// sent to everyone except the player that belongs to this pri
	if (bNetDirty && !bNetOwner)
		Ping;

	if (bNetInitial)
		PlayerID, bBot, bIsInactive;
}

simulated event PostBeginPlay()
{
	// register this PRI with the game's ReplicationInfo
	if ( WorldInfo.GRI != None )
		WorldInfo.GRI.AddPRI(self);

	if ( Role < ROLE_Authority )
		return;

    if (AIController(Owner) != None)
	{
		bBot = true;
	}

	StartTime = WorldInfo.GRI.ElapsedTime;
}

/* epic ===============================================
* ::ClientInitialize
*
* Called by Controller when its PlayerReplicationInfo is initially replicated.
* Now that
*
* =====================================================
*/
simulated function ClientInitialize(Controller C)
{
	local Actor A;
	local PlayerController PlayerOwner;

	SetOwner(C);

	PlayerOwner = PlayerController(C);
	if (PlayerOwner != None)
	{
		// any replicated playercontroller  must be this client's playercontroller
		if (Team != default.Team)
		{
			// wasnt' able to call this in ReplicatedEvent() when Team was replicated, because PlayerController did not have me as its PRI
			foreach AllActors(class'Actor', A)
			{
				A.NotifyLocalPlayerTeamReceived();
			}
		}
	}
}

function SetPlayerTeam( TeamInfo NewTeam )
{
	bForceNetUpdate = Team != NewTeam;

	Team = NewTeam;
}

/* epic ===============================================
* ::ReplicatedEvent
*
* Called when a variable with the property flag "RepNotify" is replicated
*
* =====================================================
*/
simulated event ReplicatedEvent(name VarName)
{
	local Pawn P;
	local PlayerController PC;
	local int WelcomeMessageNum;
	local Actor A;
	local UniqueNetId ZeroId;

	if ( VarName == 'Team' )
	{
		ForEach DynamicActors(class'Pawn', P)
		{
			// find my pawn and tell it
			if ( P.PlayerReplicationInfo == self )
			{
				P.NotifyTeamChanged();
				break;
			}
		}
		ForEach LocalPlayerControllers(class'PlayerController', PC)
		{
			if ( PC.PlayerReplicationInfo == self )
			{
				ForEach AllActors(class'Actor', A)
				{
					A.NotifyLocalPlayerTeamReceived();
				}

				break;
			}
		}
	}
	else if ( VarName == 'PlayerName' )
	{
		// If the new name doesn't match what the local player thinks it should be,
		// then reupdate the name forcing it to be the unique profile name
		if (IsInvalidName())
		{
			return;
		}

		if ( WorldInfo.TimeSeconds < 2 )
		{
			bHasBeenWelcomed = true;
			OldName = PlayerName;
			return;
		}

		// new player or name change
		if ( bHasBeenWelcomed )
		{
			if( ShouldBroadCastWelcomeMessage() )
			{
				ForEach LocalPlayerControllers(class'PlayerController', PC)
				{
					PC.ReceiveLocalizedMessage( GameMessageClass, 2, self );
				}
			}
		}
		else
		{
			if ( bOnlySpectator )
				WelcomeMessageNum = 16;
			else
				WelcomeMessageNum = 1;

			bHasBeenWelcomed = true;

			if( ShouldBroadCastWelcomeMessage() )
			{
				ForEach LocalPlayerControllers(class'PlayerController', PC)
				{
					PC.ReceiveLocalizedMessage( GameMessageClass, WelcomeMessageNum, self );
				}
			}
		}
		OldName = PlayerName;
	}
	else if (VarName == 'UniqueId')
	{
		if (UniqueId != ZeroId)
		{
			// Register the player as part of the session
			RegisterPlayerWithSession();
		}
	}
	else if (VarName == 'bIsInactive')
	{
		// remove and re-add from the GRI so it's in the right list
		if( WorldInfo.GRI != none )
		{
			WorldInfo.GRI.RemovePRI(self);
			WorldInfo.GRI.AddPRI(self);
		}
	}
}

/**
 * update average ping based on newly received round trip timestamp.
 */
final native function UpdatePing(float TimeStamp);

/**
 * Returns true if should broadcast player welcome/left messages.
 * Current conditions: must be a human player a network game */
simulated function bool ShouldBroadCastWelcomeMessage(optional bool bExiting)
{
	return (!bIsInactive && WorldInfo.NetMode != NM_StandAlone);
}

simulated event Destroyed()
{
	local PlayerController PC;
	local UniqueNetId ZeroId;

	if ( WorldInfo.GRI != None )
	{
		WorldInfo.GRI.RemovePRI(self);
	}

	if( ShouldBroadCastWelcomeMessage(TRUE) )
	{
		ForEach LocalPlayerControllers(class'PlayerController', PC)
		{
			PC.ReceiveLocalizedMessage( GameMessageClass, 4, self);
		}
	}

	if (UniqueId != ZeroId)
	{
		// Remove the player from the online session
		UnregisterPlayerFromSession();
	}

    Super.Destroyed();
}

/* Reset()
reset actor to initial state - used when restarting level without reloading.
*/
function Reset()
{
	Super.Reset();
	Score = 0;
	Kills = 0;
	Deaths = 0;
	bReadyToPlay = false;
	NumLives = 0;
	bOutOfLives = false;
	bForceNetUpdate = TRUE;
}

simulated function string GetHumanReadableName()
{
	return PlayerName;
}

/* DisplayDebug()
list important controller attributes on canvas
*/
simulated function DisplayDebug(HUD HUD, out float YL, out float YPos)
{
	local float XS, YS;

	if ( Team == None )
		HUD.Canvas.SetDrawColor(255,255,0);
	else if ( Team.TeamIndex == 0 )
		HUD.Canvas.SetDrawColor(255,0,0);
	else
		HUD.Canvas.SetDrawColor(64,64,255);
	HUD.Canvas.SetPos(4, YPos);
    HUD.Canvas.Font	= class'Engine'.Static.GetSmallFont();
	HUD.Canvas.StrLen(PlayerName, XS, YS);
	HUD.Canvas.DrawText(PlayerName);
	HUD.Canvas.SetPos(4 + XS, YPos);
	HUD.Canvas.Font	= class'Engine'.Static.GetTinyFont();
	HUD.Canvas.SetDrawColor(255,255,0);

	YPos += YS;
	HUD.Canvas.SetPos(4, YPos);

	if ( (PlayerController(Owner) != None) && (PlayerController(HUD.Owner).ViewTarget != PlayerController(HUD.Owner).Pawn) )
	{
		HUD.Canvas.SetDrawColor(128,128,255);
		HUD.Canvas.DrawText("      bIsSpec:"@bIsSpectator@"OnlySpec:"$bOnlySpectator@"Waiting:"$bWaitingPlayer@"Ready:"$bReadyToPlay@"OutOfLives:"$bOutOfLives);
		YPos += YL;
		HUD.Canvas.SetPos(4, YPos);
	}
}

event SetPlayerName(string S)
{
	PlayerName = S;

	// ReplicatedEvent() won't get called by net code if we are the server
	if (WorldInfo.NetMode == NM_Standalone || WorldInfo.NetMode == NM_ListenServer)
	{
		ReplicatedEvent('PlayerName');
	}
	OldName = PlayerName;
	bForceNetUpdate = TRUE;
}

function SetWaitingPlayer(bool B)
{
	bIsSpectator = B;
	bWaitingPlayer = B;
	bForceNetUpdate = TRUE;
}

/* epic ===============================================
* ::Duplicate
Create duplicate PRI (for saving Inactive PRI)
*/
function PlayerReplicationInfo Duplicate()
{
	local PlayerReplicationInfo NewPRI;

	NewPRI = Spawn(class);
	CopyProperties(NewPRI);
	return NewPRI;
}

/* epic ===============================================
* ::OverrideWith
Get overridden properties from old PRI
*/
function OverrideWith(PlayerReplicationInfo PRI)
{
	bIsSpectator = PRI.bIsSpectator;
	bOnlySpectator = PRI.bOnlySpectator;
	bWaitingPlayer = PRI.bWaitingPlayer;
	bReadyToPlay = PRI.bReadyToPlay;
	bOutOfLives = PRI.bOutOfLives || bOutOfLives;

	Team = PRI.Team;
}

/* epic ===============================================
* ::CopyProperties
Copy properties which need to be saved in inactive PRI
*/
function CopyProperties(PlayerReplicationInfo PRI)
{
	PRI.Score = Score;
	PRI.Deaths = Deaths;
	PRI.Ping = Ping;
	PRI.NumLives = NumLives;
	PRI.PlayerName = PlayerName;
	PRI.PlayerID = PlayerID;
	PRI.StartTime = StartTime;
	PRI.Kills = Kills;
	PRI.bOutOfLives = bOutOfLives;
	PRI.SavedNetworkAddress = SavedNetworkAddress;
	PRI.Team = Team;
	PRI.UniqueId = UniqueId;
	PRI.AutomatedTestingData = AutomatedTestingData;

//@HSL_BEGIN - BWJ - 4-12-16 - Playfab support
	PRI.PlayfabPlayerId = PlayfabPlayerId;
//@HSL_END
}

function IncrementDeaths(optional int Amt = 1)
{
	Deaths += Amt;
}

/** called by seamless travel when initializing a player on the other side - copy properties to the new PRI that should persist */
function SeamlessTravelTo(PlayerReplicationInfo NewPRI)
{
	CopyProperties(NewPRI);
	NewPRI.bOnlySpectator = bOnlySpectator;
}


/**
 * Sets the player's unique net id on the server.
 */
simulated function SetUniqueId( UniqueNetId PlayerUniqueId )
{
	// Store the unique id, so it will be replicated to all clients
	UniqueId = PlayerUniqueId;
}

simulated native function byte GetTeamNum();

/**
 * Validates that the new name matches the profile if the player is logged in
 *
 * @return TRUE if the name doesn't match, FALSE otherwise
 */
simulated function bool IsInvalidName()
{
	local LocalPlayer LocPlayer;
	local PlayerController PC;
	local string ProfileName;
	local OnlineSubsystem OnlineSub;

	OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
	if (OnlineSub != None)
	{
		PC = PlayerController(Owner);
		if (PC != None)
		{
			LocPlayer = LocalPlayer(PC.Player);
			if (LocPlayer != None &&
				OnlineSub.GameInterface != None &&
				OnlineSub.PlayerInterface != None)
			{
				// Check to see if they are logged in locally or not
				if (OnlineSub.PlayerInterface.GetLoginStatus(LocPlayer.ControllerId) == LS_LoggedIn)
				{
					// Ignore what ever was specified and use the profile's nick
					ProfileName = OnlineSub.PlayerInterface.GetPlayerNickname(LocPlayer.ControllerId);
					if (ProfileName != PlayerName)
					{
						// Force an update to the proper name
						PC.SetName(ProfileName);
						return true;
					}
				}
			}
		}
	}
	return false;
}

/**
 * The base implementation registers the player with the online session so that
 * recent players list and session counts are updated.
 */
simulated function RegisterPlayerWithSession()
{
	local OnlineSubsystem Online;
	local OnlineRecentPlayersList PlayersList;
	local UniqueNetId ZeroId;

	Online = class'GameEngine'.static.GetOnlineSubsystem();
	if (UniqueId != ZeroId &&
		Online != None &&
		Online.GameInterface != None &&
		SessionName != 'None' &&
		Online.GameInterface.GetGameSettings(SessionName) != None)
	{
		// Register the player as part of the session
		Online.GameInterface.RegisterPlayer(SessionName,UniqueId,false);
		// If this is not us, then add the player to the recent players list
		if (!bNetOwner)
		{
			PlayersList = OnlineRecentPlayersList(Online.GetNamedInterface('RecentPlayersList'));
			if (PlayersList != None)
			{
				PlayersList.AddPlayerToRecentPlayers(UniqueId);
			}
		}
	}
}

/**
 * The base implementation unregisters the player with the online session so that
 * session counts are updated.
 */
simulated function UnregisterPlayerFromSession()
{
	local OnlineSubsystem OnlineSub;
	local UniqueNetId ZeroId;

	OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();

	// If there is a game and we are a client, unregister this remote player
	if (UniqueId != ZeroId &&
		WorldInfo.NetMode == NM_Client &&
		SessionName != 'None')
	{
		if (OnlineSub != None &&
			OnlineSub.GameInterface != None &&
//@HSL_BEGIN - BWJ - 12-12-16 - Game settings is not important for this
		//	OnlineSub.GameInterface.GetGameSettings(SessionName) != None &&
//@HSL_END
			// if host migration is currently in-progress then don't unregister players as this is handled manually (see RemoveMissingPeersFromSession)
			!(WorldInfo.PeerHostMigration.bHostMigrationEnabled && WorldInfo.PeerHostMigration.HostMigrationProgress != HostMigration_None))
		{
			// Remove the player from the session
			OnlineSub.GameInterface.UnregisterPlayer(SessionName,UniqueId);
		}
	}
}

/** return TRUE if PRI is primary (ie. non-splitscreen) player */
simulated function bool IsPrimaryPlayer()
{
	return true;
}


//@HSL_BEGIN - BWJ - 10-5-16 - Check to see if player has had initial spawn. used for PS4 realtime multiplay
native simulated function bool HasHadInitialSpawn();
//@HSL_END


defaultproperties
{
	TickGroup=TG_DuringAsyncWork

	RemoteRole=ROLE_SimulatedProxy
	bAlwaysRelevant=True
	NetUpdateFrequency=1
	GameMessageClass=class'GameMessage'

	// The default online session is the game one
	SessionName="Game"
}
