1
0
KF2-Dev-Scripts/Engine/Classes/PlayerReplicationInfo.uc
2020-12-13 18:01:13 +03:00

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"
}