637 lines
17 KiB
Ucode
637 lines
17 KiB
Ucode
//=============================================================================
|
|
// 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"
|
|
}
|