| Home Page | Recent Changes

PlayerReplicationInfo (UT)

UT :: Actor (UT) >> Info (UT) >> ReplicationInfo >> PlayerReplicationInfo (Package: Engine)

This class is responsible for replicating information about a player to all clients in a network game.

You'll find PRI as short version of PlayerReplicationInfo in the names of many variables and functions that deal with PlayerReplicationInfos.

See PlayerReplicationInfo for the UT2003 version of this class.

Properties

All properties are replicated unless the description tells something different.

string PlayerName
Player name, or blank if none.
string OldName
Temporary value.
int PlayerID
Unique id number.
string TeamName
Team name, or blank if none.
byte Team
The player's team. (0 = red, 1 = blue, 2 = green, 3 = gold, 255 = none)
int TeamID
Player position in team.
float Score
The player's current score.
float Deaths
How often the player died.
class<VoicePack?> VoiceType
The voice pack used by the player.
Decoration (UT) HasFlag
Any kind of decorative actor, e.g. the flags in CTF or the ball in Frag*Ball, currently held by the player.
int Ping
The players ping as detected by the server.
byte PacketLoss
bool bIsFemale
The player is female.
bool bIsABot
The player is a Bot (UT).
bool bFeigningDeath
The player currently feighs death.
bool bIsSpectator
The player just spectates.
bool bWaitingPlayer
bool bAdmin
The player is logged in as an admin.
Texture TalkTexture
ZoneInfo (UT) PlayerZone
The zone the player currently is in.
LocationID PlayerLocation
A more detailed description of the player's location within a big zone.
int StartTime
int TimeAcc
(not replicated)

Mychaeel: On an admittedly futile note, I wish Epic had provided a means to chain PlayerReplicationInfo with custom ReplicationInfo subclasses via a linked list... that would save so much trouble when dealing with custom replicated information. (Even subclassing PlayerReplicationInfo or BotReplicationInfo and replacing the normal one by means of modifying Pawn (UT).PlayerReplicationInfoClass or anything else is a rather cumbersome hack on most cases.)

Wormbo: They could have created linked lists in many places like e.g. Weapon (UT) affectors, but they also missed something important like the HUD (UT) mutator chain and now we have to mess around with those relics-compatible HUD mutators...

Csimbi: Finally I managed to work out a network-proof (and working) replication.

All codes below are directly copied from the rifle I am making - You will need to rename the actors so there won't be a conflict.

First of all, You need and actor. Here it is:

//=============================================================================
// ClanRifleReplicationInfo.
//=============================================================================
// Initial version : v3.0 (Csimbi<csimbicsimbi@hotmail.com>)
//=============================================================================
class ClanRifleReplicationInfo extends Actor;

var bool bIsSpawn;             // Is it spawn protected?
var bool bPlayHeadShotSound;   // Headshot sound toggle for clients.
var int iReticle;              // ID of the current reticle
var int iBulletsFiredTotal;    // Total number of bullets fired.
var int iBulletTeamMateHits;   // Number of bullets that hit a team mate.
var int iBulletBodyHits;       // Number of bullets that hit the body of an enemy.
var int iBulletHeadHits;       // Number of bullets that hit the head of an enemy.
var int iBulletSpawnHits;      // Number of bullets that hit a spawn.
var int iBulletSpawnFirstHits; // Number of bullets that hit a spawn for the first time.
var int iBulletSpawnerHits;    // Number of bullets that hit while spawn.
var int iSpawnPunishCount;     // Number of times the player was punished (Spawnprotect).
var int iTelefragPunishCount;  // Number of times the player was punished (Telefrag).
var int iOutOfAmmoCount;       // Number of times the player has been out of ammo.

var ClanRifleReplicationInfo prevCRRI; // Previous item in the list
var ClanRifleReplicationInfo nextCRRI; // Next item in the list

replication
{
    // Things the server should send to the client.
    reliable if (Role==ROLE_Authority)
        prevCRRI, nextCRRI, bIsSpawn, iReticle, bPlayHeadShotSound, iBulletBodyHits, iBulletHeadHits, iBulletSpawnHits, iBulletSpawnFirstHits, iBulletSpawnerHits, iSpawnPunishCount, iTelefragPunishCount, iBulletTeamMateHits;
    // Things the client should send to the client.
    reliable if (Role<ROLE_Authority)
        iBulletsFiredTotal, iOutOfAmmoCount;
    reliable if (Role==ROLE_Authority)
        GetCRRI;
}

event Spawned ()
{
    AddToList();
    SetTimer(1.0, true);
}

function AddToList()
{
    local ClanRifleReplicationInfo C;

    ForEach AllActors(class'ClanRifleReplicationInfo', C) break;
    if(C==None)
    {
        prevCRRI=None;
        nextCRRI=None;
    }
    else
    {
        C=C.GetLast();
        if(C==None || C==Self)
        {
            prevCRRI=None;
            nextCRRI=None;
        }
        else
        {
            C.nextCRRI=Self;
            prevCRRI=C;
            nextCRRI=None;
        }
    }
}

function ClanRifleReplicationInfo GetNext()
{
    return nextCRRI;
}

function ClanRifleReplicationInfo GetPrevious()
{
    return prevCRRI;
}

function ClanRifleReplicationInfo GetFirst()
{
    local bool done;
    local ClanRifleReplicationInfo C;

    done=false;
    ForEach AllActors(class'ClanRifleReplicationInfo', C) break;
    while(!done && C!=None)
    {
        if(C.prevCRRI==None)
        {
            done=true;
            return C;
        }
        C=C.GetPrevious();
    }
    return None;
}

function ClanRifleReplicationInfo GetLast()
{
    local bool done;
    local ClanRifleReplicationInfo C;

    done=false;
    ForEach AllActors(class'ClanRifleReplicationInfo', C) break;
    while(!done && C!=None)
    {
        if(C.nextCRRI==None)
        {
            done=true;
            return C;
        }
        C=C.GetNext();
    }
    return None;
}

function RemoveFromList()
{
    // Lot of extra checks here in case someone from outside manipulates the list and messes it up.
    if(prevCRRI==None)
    {
        if(nextCRRI!=None) nextCRRI.prevCRRI=None;
    }
    else
    {
        prevCRRI.nextCRRI=nextCRRI;
    }
    if(nextCRRI==None)
    {
        if(prevCRRI!=None) prevCRRI.nextCRRI=None;
    }
    else
    {
        nextCRRI.prevCRRI=prevCRRI;
    }
    nextCRRI=None;
    prevCRRI=None;
}

function ClanRifleReplicationInfo GetCRRI (Pawn P)
{
    local bool found;
    local ClanRifleReplicationInfo C;

    ForEach AllActors(class'ClanRifleReplicationInfo', C)
    {
        found=true;
        break;
    }
    if(found)
    {
        for(C.GetFirst();C!=None;C=C.GetNext())
        {
            if(Pawn(C.Owner)==P)
            {
                return C;
            }
        }
    }
    return None;
}

event Timer()
{
    if(Pawn(Owner)==None)
    {
        RemoveFromList();
        Destroy();
    }
}

function DumpList()
{
    local ClanRifleReplicationInfo C;

    log ("___________Dumped list begin_________________");
    C=GetFirst();
    while(C!=None)
    {
        log("Prev: "$C.prevCRRI);
        log("Item: "$C);
        log("Next: "$C.nextCRRI);
        C=C.nextCRRI;
        if (C!=None) log ("___________");
    }
    log ("___________Dumped list end___________________");
}

defaultproperties
{
    bHidden=true;
    NetUpdateFrequency=2.00;
    bAlwaysRelevant=true;
    bIsSpawn=false;
    bPlayHeadShotSound=false;
    iReticle=0;
    iBulletsFiredTotal=0;
    iBulletBodyHits=0;
    iBulletHeadHits=0;
    iBulletSpawnHits=0;
    iBulletSpawnFirstHits=0;
    iBulletSpawnerHits=0;
    iSpawnPunishCount=0;
    iTelefragPunishCount=0;
    iBulletTeamMateHits=0;
    iOutOfAmmoCount=0;
}

Next, you need to spawn this actor whenever a player enters the game (the actor above is self-destructing, no need to worry about that in if You use this class).

The ListPlayers() is always called from the ModifyPlayer() function of the mutator.

function ListPlayers()
{
    local pawn P;
    local ClanRifleReplicationInfo C;
    local int i,x;

    i=0;
    ForEach AllActors(class'ClanRifleReplicationInfo', C) break;
    for(P=Level.Pawnlist; P!=None; P=P.Nextpawn)
    {
        killers[i]=P;
        // Create rifle replication info.
        if(C!=None) C=C.GetCRRI(P);
        if(C==None)
        {
            C=Spawn(class'ClanRifleReplicationInfo',P);
            x=class'ClientOptionsPage'.default.Reticle;
            if(x > class'ClientOptionsPage'.default.ReticleMax) x=class'ClientOptionsPage'.default.ReticleMin;
            if(x < class'ClientOptionsPage'.default.ReticleMin) x=class'ClientOptionsPage'.default.ReticleMax;
            C.iReticle=x;
        }
        // Increase player count
        i++;
    }
}

Then the variables need to be updated.

Here is function that updates a toggle (implemented within the Mutator - running on the server):

function bool ToggleCRRIHSS (Pawn SP)
{
    local ClanRifleReplicationInfo C;
    local pawn P;
    local int i;
    local bool found;

    if(SP==None) return false;
    i=0;
    found=false;
    for(P=Level.Pawnlist; P!=None && !found; P=P.Nextpawn)
    {
        if(SP==P) found=true;
        i++;
    }
    if(found)
    {
        ForEach AllActors(class'ClanRifleReplicationInfo', C) break;
        C=C.GetCRRI(SP);
        if(C==None) return false;
        C.bPlayHeadShotSound=!C.bPlayHeadShotSound;
        return true;
    }
    return false;
}

There is function here that updates a counter (implemented within the Mutator - running on the server):

function bool UpdateCRRICounter (Pawn SP, int Type, int Counter)
{
    local ClanRifleReplicationInfo C;
    local pawn P;
    local int i, x;
    local bool found;

    if(SP==None) return false;
    i=0;
    found=false;
    for(P=Level.Pawnlist; P!=None && !found; P=P.Nextpawn)
    {
        if(SP==P) found=true;
        i++;
    }
    if(found)
    {
        ForEach AllActors(class'ClanRifleReplicationInfo', C) break;
        C=C.GetCRRI(SP);
        if(C==None) return false;
        switch(Type)
        {
            case COUNTER_TYPE_RETICLE:
                x=C.iReticle+Counter;
                if(x > class'ClientOptionsPage'.default.ReticleMax) x=class'ClientOptionsPage'.default.ReticleMin;
                if(x < class'ClientOptionsPage'.default.ReticleMin) x=class'ClientOptionsPage'.default.ReticleMax;
                C.iReticle=x;
                break;
            case COUNTER_TYPE_BODY: C.iBulletBodyHits+=Counter; break;
            case COUNTER_TYPE_HEAD: C.iBulletHeadHits+=Counter; break;
            case COUNTER_TYPE_SPAWNSHOT: C.iBulletSpawnHits+=Counter; break;
            case COUNTER_TYPE_SPAWNSHOT_FIRST: C.iBulletSpawnFirstHits+=Counter; break;
            case COUNTER_TYPE_SPAWNER: C.iBulletSpawnerHits+=Counter; break;
            case COUNTER_TYPE_SPAWNPUNISH: C.iSpawnPunishCount+=Counter; break;
            case COUNTER_TYPE_TELEFRAGPUNISH: C.iTelefragPunishCount+=Counter; break;
            case COUNTER_TYPE_TEAMMATE: C.iBulletTeamMateHits+=Counter; break;
        }
        return true;
    }
    return false;
}

Here is a function that reads and updates a variable on the client-side:

simulated function ReplicationTick(float DeltaTime)
{
    local ClanRifleReplicationInfo C;
    local Pawn PawnOwner;
    local PlayerPawn PlayerPawnOwner;
    local bool FoundOwnerC;

    PawnOwner=Pawn(Owner);
    PlayerPawnOwner=PlayerPawn(Owner);
    FoundOwnerC=false;
    if(PawnOwner!=None)
    {
        DT+=DeltaTime;
        if(DT>=RefreshInterval)
        {
            DT=0.000000;
            ForEach AllActors(class'ClanRifleReplicationInfo', C)
            {
                if(Pawn(C.Owner)==PawnOwner)
                {
                    FoundOwnerC=true;
                    break;
                }
            }
            if(FoundOwnerC)
            {
                OwnerIsASpawn=C.bIsSpawn;
                C.iBulletsFiredTotal+=BulletsFired;
                BulletsFired=0;
                if(OutOfAmmo)
                {
                    OutOfAmmo=false;
                    C.iOutOfAmmoCount+=1;
                }
                if(PlayerPawnOwner!=None) // not for bots.
                {
                    if(bPrevPlayHeadShotSound!=C.bPlayHeadShotSound)
                    {
                        bPrevPlayHeadShotSound=C.bPlayHeadShotSound;
                        if(class'ClientOptionsPage'.default.HSAudio>HeadshotSoundCount-1) PlayerPawnOwner.ClientPlaySound(HeadshotSound[Rand(HeadshotSoundCount)], false, false);
                        else PlayerPawnOwner.ClientPlaySound(HeadshotSound[class'ClientOptionsPage'.default.HSAudio], false, false);
                    }
                }
            }
            else OwnerIsASpawn=false;
            TargetIsASpawn=HasSPInventory(TP);
            selectedreticle=GetCurrentReticle(OP);
        }
    }
}

A couple of suggestions:

The GetCRRI() function does not work on client side - do not use it. It will only work in Standalone (no network) games.

Do NOT read/write the replicated variables from a PostRender() or Tick() function unless You add there some flow control (as done in the code above), otherwise the network performance will be very poor.

Csimbi: I could not get replication working with NetUpdateFrequency values lower than 2.00. Is there any way to do that?

Iv: I just managed to get that subclassing of PlayerReplicationInfo working by just destroying the actual PlayerReplicationInfo in the Pawn and spawning a new subclassed one in place, all of this from the ModifyPlayer function of the Mutator class. The trick seemed to work fine in here so here I'm posting the snip:

function ModifyPlayer(Pawn Other) {
    local PlayerPawn pp;
    local SpyPlayerReplicationInfo spri;
    local string PlayerName;

    if( Other.isA('PlayerPawn') ) {
        pp = PlayerPawn(Other);
        pp.PlayerReplicationInfoClass = class'SpyPlayerReplicationInfo';

        /*
         * We are going to spawn a new replication info
         * and remove the previous one
         */
        
        PlayerName = pp.PlayerReplicationInfo.PlayerName;
        pp.PlayerReplicationInfo.destroy();
        pp.PlayerReplicationInfo = Spawn( class 'SpyPlayerReplicationInfo', pp,,vect(0,0,0),rot(0,0,0));
    
        pp.PlayerReplicationInfo.PlayerName = PlayerName;
        pp.initPlayerReplicationInfo();
        spri = SpyPlayerReplicationInfo( pp.PlayerReplicationInfo );
        spri.ipAddrString = pp.GetPlayerNetworkAddress();       
    }
    super.ModifyPlayer( Other );
}

Xian:

Csimbi: That is because the main ClanRifleReplicationInfo is not replicated to all Clients. It exists only on Role=ROLE_Authority. But there are other ways to update variables client-side... I don't get why you had to use a function that is executed each frame O_o

Iv: From what I can remember, ModifyPlayer() is called on each respawn. Spawn/Destroy use A LOT of bandwidth, not to mention CPU resources... best way is to use a Mutator like this:

function ModifyLogin(out class<playerpawn> SpawnClass, out string Portal, out string Options)
{
    if ( NextMutator != None )
        NextMutator.ModifyLogin(SpawnClass, Portal, Options);

        SpawnClass.default.PlayerReplicationInfoClass = Class'SpyPlayerReplicationInfo';
}

Done :) And even more efficient (note: if other mutators modify it, then you will mess up their functions... also note a SpawnNotify can override your Class)

Known Subclasses

Related Topics


Category Class (UT)

The Unreal Engine Documentation Site

Wiki Community

Topic Categories

Recent Changes

Offline Wiki

Unreal Engine

Console Commands

Terminology

FAQs

Help Desk

Mapping Topics

Mapping Lessons

UnrealEd Interface

UnrealScript Topics

UnrealScript Lessons

Making Mods

Class Tree

Modeling Topics

Chongqing Page

Log In