PlayerReplicationInfo (UT)
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
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)