Replicating an info (invisible) actor to the chosen players.

Get some cool tips about how to tweak your UT graphic, gameplay, and much more!
Post Reply
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Replicating an info (invisible) actor to the chosen players.

Post by Higor »

This is an advanced guide that will allow us to select the target players we want to replicate certain actors to.
It is assumed that whoever intends to use it already has knowledge of the basics of replication, in this case Relevancy.

Relevancy is the concept we're using to describe a state where an actor is being 'replicated' to a client machine, there are various relevancy rules that will trigger said actor appearing or not appearing in the client's world.
In this guide, we will focus on actors that have no visuals, since it's relevancy rules are easier to understand, in this case the actor must have bHidden so that no visual relevancy gets in motion.
Therefore:
bHidden = True

Relevancy rules become simpler to read and understand on hidden actors, and these are consist on these:
- bAlwaysRelevant ===> This actor is relevant to all clients.
- Owner ===> This actor is relevant to it's Owner playerpawn (or Owner.Owner.Owner.....Owner until finding a PlayerPawn).
- Instigator ===> This actor is relevant to it's Instigator PlayerPawn.
- Weapon(Pawn.Weapon) ===> This weapon is relevant to others if it's a relevant pawn's weapon.
- Having been relevent in the last [IpDrv.TcpNetDriver].RelevantTimeout seconds (defaults to 5).

We're now focusing on simple information actors, they can contain anything and the better way to save bandwidth and prevent 'other' players from knowing things they shouldn't know, it's to simply not tell them, no antihack protection will do better than this.
For this reason, the actor shouldn't have a flag that makes it replicate to everyone...
bAlwaysRelevant = False

The other conditions we must take into account here are:
Owner, Instigator, RelevantTimeout.
Why is that? The following formulation will explain it better than any pretty usage of words.

Information:
Actors remain relevant for: [RelevantTimeout] seconds.
Actor's Owner OR/AND Instigator at the end of the Tick determies the relevancy target.

What to do:
Default Server setting of RelevantTimeout or higher (>= 5 secs)
Cycle Owner and/or Instigator once per frame among a cached list of players.

What to expect:
RelevantTimeout keeps the actor relevant to all previous Owner/Instigator
Continuing to cycle them will refresh the timer.
Result is the actor being relevant to this set of players at all times.

It's pretty simple if you visualize it in small blocks, with the replicated block pointing to a different 'player' block on each frame.
There are various ways to cycle among players, and how to trigger relevancy on them.
My code example does a bi-directional assignment of Owner and Instigator based on a cached list of players that doesn't update every less than 10 frames to preserve speed.
In this case, the criteria that chooses replication targets is: Team.

Our variables

Code: Select all

var byte Team;
var PlayerPawn PList[32]; //Relevancy Playerpawn list
var int iSize, pPosition;

//Linked list
var sgBaseBuildRule RuleList;
Refilling the player cache:

Code: Select all

function RefillList()
{
	local PlayerPawn P;
	local int i, k;

	ForEach AllActors (class'PlayerPawn', P)
		if ( (P.PlayerReplicationInfo != none) && (P.PlayerReplicationInfo.Team == Team) )
			PList[i++] = P;
	k = i;
	while ( i<iSize )  //This loop clears references if the list shrunk since last fill
		PList[i++] = none;  //Makes the engine properly destroy unused pawns
	iSize = k;
	pPosition = 0;
}
And our actual relevancy loop, changing Owner and Instigator in opposite directions (this one depends on the coder more than anything).

Code: Select all

event Tick( float DeltaTime)
{
	local sgBaseBuildRule aR;

	if ( (pPosition < iSize) && (PList[pPosition] != none || PList[iSize - (pPosition+1)] != none) )
	{
		iSize--; //Makes operations until iSize++ simpler and faster
		SetOwner(PList[pPosition]);
		Instigator = PList[iSize-pPosition];
		
		For ( aR=RuleList ; aR!=none ;aR=aR.nextRule )  //We can change the relevancy conditions in child actors too!
		{
			aR.SetOwner(PList[pPosition]); //So we avoid doing the player cache in every single component
			aR.Instigator = PList[iSize-pPosition];
		}
		iSize++;
	}
	pPosition++;

	if ( pPosition > 10 && pPosition >= iSize ) //This 10 is an arbitrary minimum amount of ticks required to refill
		RefillList();   //You may increase as you wish, but don't go over TickRate * 2 or the clients may lose the actor cyclically.
}
With this, your actor will only be sent to the chosen ones and you will not have to worry about extra CPU usage due to variable changes checks, wasted bandwidth and anyone looking into things they shouldn't look at.
Post Reply