Multiplayer Scripting – Arma Reforger
Multiplayer Scripting in Enfusion is based on Replication - it determines which machine does what in a situation.
When a Prefab entity is created on the server (and has a replication node set to broadcast), it is replicated on other network machines for them to see a representation of this object. This created representation is called a proxy – more information below.
Network
The Arma Reforger network architecture is a classical one with one server to which clients connect. Clients do not communicate between each others, they communicate with the server which redistributes data.
Server
The Server is the core network machine: it is the central system that receives and redistributes network information. There is only one server per multiplayer game, and the multiplayer game is destroyed if the server loses connection (server role does not get transferred).
Client
A Client is a machine that is connected to the server. A client is everything else that is not the server. A client can join after a game has started (even leave and join again) depending on the scenario. Such client is known as JIP, standing for Join in Progress - see the Join In Progress section below.
Replication
Replication (shortened as RPL) is the name of the system used to replicate an entity or effect on all the concerned machines. For example, when a grenade explodes the damage is calculated on the server, whereas the visual effect is broadcast (replicated) to all the clients.
Remote Procedure Call (shortened as RPC) is the system allowing to queue network messages on other machines.
Preamble
The network structure is based on server-client relationship; Scripting and RPC Replication methods, on the other hand, should be oriented towards Roles - code should be written on authority role principle, server-client principle should be avoided:
- is the entity present on this machine the Authority?
- if not, the local entity is a Proxy
- if so, is it an Owner entity?
See the next chapters for further explanations.
Entity Role
When an entity gets created on a network machine, this entity is the Authority and this role is set in stone; it can never get changed or transferred.
If the network machine is the server, the entity's existence is broadcast to other network machines; the authority (server's entity) is represented on each client by locally-created entities named Proxies (local representation of the authority's reference).
- For an entity created on the server:
- the server hosts the authority entity
- the entity is replicated as proxy on other machines
- ownership can be given and taken by the authority
- For an entity created on a client:
- the client hosts the authority entity
- the server being unaware of it, the entity is not replicated on any network machine
Authority
The Authority is the reference entity.
All mission objects belong to the server; the server has almost if not all the authority entities.
Proxy
A Proxy is an entity that represents an authority on another machine; it only receives entity updates from the authority and cannot send network updates about it - unless the machine has been designated owner (see below).
Owner
An Owner is a special entity role that comes in addition to an authority or a proxy. An owner entity is the entity that has minor rights on the entity such as owner-specific methods. Only one machine can own an entity. The server can be an owner, and ownership can be transferred by the authority.
A client owner is only a machine with an "elevated" proxy access, being able to use RPC methods about this entity towards the authority (and only towards the authority).
Ownership behaviour example:
- a machinegun car exists on the server; the server is hosting the authority and is the owner of it
- a player enters the car as the driver; the host (the server) gives car's ownership to the player's machine, which becomes the owner of the car
- another player enters the car's machinegun; the host (still and always the server) gives machinegun's ownership to this player's machine, which becomes the owner of the car's machinegun entity
- the server keeps authority of this car and its machinegun - it is the one that overrides all clients' positions/states
- when said players leave their respective entities, the server takes ownership back
Replication State
There are two main replication game states which replication distinguishes and which affect some characteristics of replicated items: loadtime state and runtime state. Items inserted into replication while game is loading (ie. at loadtime) are treated as loadtime items, while those inserted once the game is running (ie. at runtime) are treated as runtime items when inserted on server, or local items when inserted on client (as they only exist locally).
for simplicity, this page will use "items" to refer to both entities, components and in general anything else that might be replicated.
Loadtime
Loadtime items are generally entity instances placed in world. These are objects that shouldn't be moved around too much (though some degree of movement is allowed) and usually need to be visible from greater distances. Typical examples are buildings or street signs. Main things to keep in mind:
- They do not require prefab for spawning.
- Their insertion must be deterministic on the server and clients. The server relies on clients to have the same initial world state after the map has been loaded so it can replicate changes from this initial state as they become relevant for a given client, reducing overall traffic (by sending only changes) and spreading the load over time. Clients will still be able to see things in the distance, even though their state has not been perfectly synchronised.
- Replication validates that the initial world state matches. That is, RplId and type information of each loadtime item is the same on client as it initially was on the server. When a mismatch is detected, "inconsistent item table" error will appear in log and client will be disconnected with JIP_ERROR.
- They may be out-of-sync with the server for a long time (possibly through the whole game session). The replication scheduler (running server-side) decides when to stream their current state to each client. When this decision is based on proximity, clients will only get the current state streamed in when they get "close enough" for these changes to be relevant. Because of the world size, this may take time or never happen at all.
- The only exception is complete removal of these items on the server. In that case, removal will be replicated to clients unconditionally.
- As long as the authority exists on the server, the proxy exists on the clients.
- Streaming in synchronizes state of proxy with authority. Once streamed in, proxy starts receiving state updates and it can send and receive RPCs.
- Streaming in synchronizes state of proxy with authority. Once streamed in, proxy starts receiving state updates and it can send and receive RPCs.
Runtime
Runtime items generally come from a game system that creates them during the session (A game mode, Arma Reforger:Game Master, etc). They can move around the map freely and they are usually not visible from far away. Typical examples are vehicles, player characters, collectible items. Main things to keep in mind:
- They require prefab for spawning.
- They may only be inserted on the server. This is the authority.
- Their proxy may or may not exist on a client.
Local Runtime
Local items are items created on client during the session. They can be used for locally predicted effects of player actions, such as firing from rocket launcher immediately creating a flying rocket on client who fired it, instead of waiting for server-side rocket to be streamed to this client. Main things to keep in mind:
- They do not require prefab for spawning.
- They may only be inserted on a client. This is the authority.
- There are no proxies on the server or other clients.
Replication State Override
Replication state override (a.k.a "Rpl State Override") is a RplComponent property that allows modifying the behaviour of spawning and insertion process to behave as-if insertion of node hierarchy happened in the specified state. The currently supported values are:
- None: no override. The state for this node and its descendants at the time of insertion is inherited from its parent node (whether it is Loadtime or Runtime).
- Runtime: The state for this node and its descendants at the time of insertion is overridden to be Runtime.
RPC Call
An RPC call is an entity method's call through the network, e.g from the authority to its proxies (on other machines). Specific targets can be defined, such as "all proxies" or "owner only".
Join In Progress
Join In Progress is the fact of having a client that connects to the server while a multiplayer game is already running.
Streaming
Streaming is the system creating/updating/deleting entities that are relevant to the local machine. An entity is not always present on all machines. Streaming is the way of saving network and CPU process by only sending relevant entities' information.
Consider a server hosting a game with multiple clients, with "player" designating the local player on the local client machine. A car parked 5 km away from the player is not important to them, so only the server and players near the car have this entity's information - it actually does not exist on the player's machine. If someone damages that car, the server is informed and this information is transferred to whomever it is relevant - nearby players, not distant players.
If the player gets close to it, the server decides this car becomes relevant to that client and streams it the proxy's information along with its current state (damage, position, etc).
- Streaming in creates a proxy
- Streaming out destroys this proxy
- While the proxy exists, it receives state updates and can send and receive RPCs.
Relevance
Relevance is a value decided authority-side by the engine that sets whether or not a proxy should be streamed to a network machine or not, in order to save network messages and CPU cycles.
A simple example would be a car that is five kilometres away from a player is not relevant to that player; so the proxy on this player's machine is deleted by the replication system automatically, and re-created/synchronised again when it becomes relevant (e.g close-by) again.
Special Cases
The authority always exists, as it is the reference entity for replication.
The owner entity is always present on its machine; it is never unloaded/reloaded depending on relevance unless ownership is taken away.
Broadcast
An RPC call with broadcast as a target will send the message on machines that have said entity present on their system; as seen above, a proxy owner entity is guaranteed to exist on
Operations Order
Current loading order:
Order | Operation |
---|---|
1 | entity hierarchy creation |
2 | RplLoad operations |
3 | member variable values in unguaranteed order
triggering their respective onRplName methods |
Code
Entity methods get executed with the Rpc method using their RplRpc attribute to determine the targets.
Rpc Method
The Rpc method (present in both GenericEntity and GenericComponent) is the starting point to trigger a network-friendly code call.
The engine will make it so that this method will call the provided one where it needs to be, depending on the RplRpc target method attribute.
By convention, methods that can be called through replication are prefixed with the Rpc_ prefix: e.g Rpc_MyMethod().
the RpcAsk_ prefix is used by the owner to ask the authority to do an action (sender = owner, receiver = authority).
the RpcDo_ prefix is used by the authority to do an action (sender = authority, receiver = owner proxy or all proxies).
Usage
Network-wise, a method RPC is added to a list of "to do tasks"; it means that they might be sent separately, or all in one network packet. The order of the calls between two machines is however guaranteed.
RplRpc Attribute
The RplRpc attribute is to decorate an entity's method, allowing the engine to run said method on targeted machines through the Rpc method.
Usage
Here is RplRpc attribute's signature:
Parameter | Usage | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
channel | RplChannel enum, can be one of:
| ||||||||||||||||||||||||||||||||||
rcver (receiver) | RplRcver enum, can be one of:
| ||||||||||||||||||||||||||||||||||
condition | RplCondition enum, can be one of:
| ||||||||||||||||||||||||||||||||||
customConditionName | if condition has been set to Custom, customConditionName must be set to the name of the method to be checked. This method must return a bool.
Example: class RplClass
{
[RplProp(onRplName: "OnBroadcastValueUpdated", condition: RplCondition.Custom, customConditionName: "RpcConditionMethod")]
protected int m_iBroadcastValue;
void OnBroadcastValueUpdated()
{
// this method will only run on proxies if authority's RpcConditionMethod returns true
}
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
void RpcAsk_Method()
{
m_iBroadcastValue = 33;
Replication.BumpMe();
}
bool RpcConditionMethod()
{
return m_iBroadcastValue < 50;
}
} |
RplProp Attribute
The RplProp attribute is to decorate an object's property, allowing the engine to synchronise values on network machines.
Only a change on the authority and notifying the replication system with Replication.BumpMe() method will trigger a broadcast to other machines (see the BumpMe section).
Usage
Here is RplProp attribute's signature:
Parameter | Usage |
---|---|
group | RplGroup enum, can be one of:
|
onRplName | The method name to be called on proxies when the property's value is changed on the authority (and synchronised with Replication.BumpMe() method). |
ctx | Script context - engine only, unused script-side |
condition | RplCondition, can be one of:
|
customConditionName | if condition has been set to Custom, customConditionName must be set to the name of the method to be checked. This method must return a bool.
Example: class RplClass
{
[RplProp(onRplName: "OnBroadcastValueUpdated", condition: RplCondition.Custom, customConditionName: "RpcConditionMethod")]
protected int m_iBroadcastValue;
void OnBroadcastValueUpdated()
{
// this method will only run on proxies if authority's RpcConditionMethod returns true
}
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
void RpcAsk_Method()
{
m_iBroadcastValue = 33;
Replication.BumpMe();
}
bool RpcConditionMethod()
{
return m_iBroadcastValue < 50;
}
} |
RplSave/RplLoad
RplSave
RplSave is the method called on the authority's side to write custom synchronisation data.
RplLoad
RplLoad is the method called on the streamed entity after the entity, its components and the whole entities hierarchy to which it belongs has been loaded, if any.
It is called immediately after initialisation but before anything else (EOnFrame, etc).
Replication Class
The Replication class is a very useful Replication tool, holding the following methods (only the most used ones are listed here).
FindId
Return the provided entity's id. should be an RplId
If the entity does not have one, it returns Replication.INVALID_ID.
FindItem
Return the provided id's entity.
If no entities have the provided id, it returns null.
FindOwner
Return the owner machine's network id.
If the entity has no owner id, it returns Replication.INVALID_IDENTITY.
BumpMe
This method tells the Replication system that at least one property has been updated and that the change(s) should be broadcast.
Network Modding
Defining codec methods is required when a scripted class is marked with [RplProp()]. It is recommended to
Definitions
- Codec
- A Coder-Decoder - an element that is in charge of encoding/decoding data
- Snapshot
- An "image" of an object's replicated properties - used by the engine to compare with the current status' snapshot and detect a change
- Packet
- (naming may change) An object's transferrable data - unrelated to network packet
Methods
EnNetwork.c provides an example class with declarable methods overriding the default behaviour. These methods can be written/overridden in any Enforce Script classes.
Codec Steps
On Server | On Client |
---|---|
|
|
Examples
For the sake of the examples, let's assume that:
- the server hosts the ComputerEntity authority entity
- a client hosts the owner entity
RplRpc
In the following code:
- the protected RpcAsk_Authority_Method method is set to be RPC-called on the authority
- the protected RpcDo_Owner_Method method is set to be RPC-called on the owner
- the protected RpcDo_Broadcast_Method method is set to be RPC-called on every proxy
We assume the public TurnOn method has been triggered owner-side, making the following events happen:
- the TurnOn method calls the RpcAsk_Authority_Method through RPC; as stated earlier, this method executes on the authority's machine - the server in this case
- the RpcAsk_Authority_Method method (running on the authority):
- prints "authority-side code" in the authority machine's logs
- calls the PlayMusic method on the authority
- calls the RpcDo_Broadcast_Method method on all the proxies (see below)
- calls the RpcDo_Owner_Method method on the owner's proxy (see below)
- the RpcDo_Broadcast_Method method (running on every proxy, including owner's):
- prints "proxy-side code" in proxy machines' logs
- calls the PlayMusic method on the proxy
- the RpcDo_Owner_Method method (running on the owner's proxy):
- prints "owner-side code"
The end result:
- all the machines' instances:
- called the PlayMusic method and played the "OSWelcome" music
- the authority's machine has
- "authority-side code" in the logs - had the server been the owner too, "owner-side code" would be present as well
- the owner's machine has
- "proxy-side code" then "owner-side code" in the logs (the order is guaranteed)
- all other machines have
- "proxy-side code" in the logs
RplProp
In the following code:
- the m_bIsTurnedOn property is set to update to all the proxies on authority value's modification
- the m_bIsTurnedOn property is set to execute the OnTurnedOnUpdated method on proxies when its value is updated by Replication (JIP included)
We assume the public TurnOn method has been triggered owner-side, making the following events happen:
- the TurnOn method calls the RpcAsk_Authority_Method through RPC; as stated earlier, this method executes on the authority's machine - the server in this case
- the RpcAsk_Authority_Method method (running on the server):
- sets LED colour on the server
- sets the m_bIsTurnedOn property to "true" on the authority; as the m_bIsTurnedOn property is set to broadcast its changes thanks to the RplProp attribute, the change is broadcast to every proxy machine
- the OnTurnedOn method is triggered on proxies and "The proxy has been updated" is printed
The end result:
- all the machines' instances:
- have the m_bIsTurnedOn property set to "true"
- have their light set to green
- the authority:
- printed "authority-side code"
- all the proxies:
- printed "proxy-side code"
RplSave/RplLoad
Without RplSave/RplLoad override, the following entity would be created with m_iSoldierId, m_iHealth and m_bHadLunch set to their default value, as these are not decorated with RplProp.