Multiplayer

Although not all mods need special code to handle multiplayer, it’s important to create your mods with multiplayer in mind.

Adding support for multiplayer to your mod is similar to creating multiplayer code for any other Unreal Engine project.

Does your mod need special changes to support multiplayer?

Long answer: It’s complicated. It’s always best to test your mod to determine if it’s working or not. But, in general, if your mod only adds new items, recipes, and schematics, or adds content only by extending base-game classes, you probably don’t need to write any multiplayer code. As soon as you start handling user input or using custom logic to modify the state of the game, you should start thinking about multiplayer.

Before You Continue

Replication in Unreal Engine can be a very confusing system at first, so it’s important to build a strong understanding before continuing.

This Alex Forsythe video provides an excellent introduction to replication in Unreal Engine, and you should watch it before continuing on this page. By watching this video now, you’ll save yourself hours of troubleshooting in the future.

The rest of this page will assume you have watched, and understand, the video.

After watching the video, check out the Unreal Community Wiki’s replication page. You will not need to use every concept here, but understanding the factors at play will help you figure out what is going wrong when things don’t work as expected.

Consider checking out Unreal’s own examples in the Networking Content Examples pack.

Again, please watch the video before continuing.

ExampleMod

The ExampleMod (included in the starter project) contains some examples of correctly adding multiplayer functionality to mods.

The examples are not exhaustive, but they should help you get started.

Checking for Authority

The video explains the concept of network authority.

To check if your code has authority, use Actor 'Has Authority'. If you don’t have an actor to check against, consider using the return of GameplayStatics 'Get Game State' as the actor.

Avoid Using Player Controller Index 0

Keep in mind that dedicated servers have no player controller, so you should generally avoid using GameplayStatics 'Get Player Controller' with index 0 in your code. For example, don’t use its returned controller as an input for Has Authority, and don’t get its pawn to check Is Locally Human Controlled.

It’s fine to use player controller index 0 for user interface related tasks since that code should only be running on sides that have a valid player controller index 0.

Remote Procedure Calls (RPCs)

The video explains what a Remote Procedure Call (RPC) is. You can find more detailed information in the Unreal Engine documentation.

Satisfactory mods generally have their RPCs defined in one of two places depending on the use case. The table below contains information about when to use each. Be sure to read the sections below that explain each system in more detail.

Table 1. Remote Call Object or Replicated Mod Subsystem
Use a Remote Call Object (RCO) when…​ Use a Replicated Mod Subsystem when…​

Your RPC is sending info from one client to the server.

Your RPC is sending info from the server to one or more clients.

When the RPC is being called because of some action the client took.

When the RPC is being called automatically or without client action.

When your RPC is Multicast

RPC Execution

The outcome of calling an RPC depends on the RPC’s type and the network ownership of the calling actor. The Unreal Documentation has an excellent table summarizing the different possible execution results.

Remote Call Objects (RCOs)

A Remote Call Object (RCO) is a convenient place for modded RPCs to be defined. Coffee Stain Studios has written custom code to make it easier for mods to register their own RCOs. The inner details of this system are described in the appendix if you want to learn more.

RCOs are spawned by the server for each player controller, but importantly, that player controller is given network ownership of the RCO. This gives RCOs the ability to call RPCs - RPCs only work on objects that are (transitively) owned by the connection (i.e. the player controller, which is owned by the connection). Without this network ownership, RPCs would fail to execute.

RCOs can be registered via listing their classes in your Game Instance Module’s Remote Call Objects array. Registered RCOs can be retrieved at runtime from a player controller instance via FGPlayerController::GetRemoteCallObjectOfClass which is accessible in both blueprints and C++.

If for some reason you need to manually register an RCO it can be done via FGPlayerController::RegisterRemoteCallObjectOfClass or FGGameMode::RegisterRemoteCallObjectClass.

Do Not Use Multicast in RCOs!

It is important to note that since the RCOs are owned by the player controller, they do not exist on every connection. As such, you should not use them for any multicast RPCs - triggering one will softlock and crash client players! If you want to use Multicast, do it from an Replicated Subsystem instead.

An example why you should not do this is demonstrated in the ExampleMod’s CC_ExampleReplication chat command.

Blueprint Example

Your remote call objects should inherit from BP Remote Call Object.

The ExampleMod demonstrates blueprint remote call object usage in Widget_MultiplayerDemoBuilding.

C++ Example

Your remote call objects should inherit from FGRemoteCallObject.

If you were to try this as is, it still won’t work because Unreal is weird and we still need to do one more thing. You will need to add any kind of UPROPERTY to the RCO, which is replicated. That also means you need to add it to the GetLifetimeReplicatedProps function. This property just needs to exist, you don’t need to do anything with it.

Here is a small example C++ showing a simple RCO with one RPC.

UCLASS()
class DOCMOD_API UDocModRCO : public UFGRemoteCallObject {
	GENERATED_BODY()

public:
	UFUNCTION(Server, WithValidation, Reliable)
		void SetSomeStuffOfTheDocMachineRPC(ADocMachine* machineContext, bool bSomeData);

	UPROPERTY(Replicated)
		bool bDummy = true;
};

The parameters of the RPC are just examples, but most of the time you actually want to pass one context parameter so you can change the state of the given context. With this example function we might allow the GUI to be able to reset the counter of the machine passed. Without the context it won’t be able to know of which machine it should reset the counter of.

The implementation of the RPC is not covered in this tutorial because that’s completely up to you to decide, but below is a short example for the GetLifetimeReplicatedProps function.

#include "Net/UnrealNetwork.h"

void UDocModRCO::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(UDocModRCO, bDummy);
}

Now in the GUI or wherever you need to call the RPC, you can just get the first player controller of the world and then call the AFGPlayerController::GetRemoteCallObjectByClass function and pass the class of your RCO to get the instance of the RCO for the client.

Here is a example calling the RPC in C++:

ADocMachine* machine = GetMachine(); // get the context object from somewhere
UWorld* world = machine->GetWorld(); // get the world context from anywhere, like a world
UDocModRCO* rco = Cast<AFGPlayerController>(world->GetFirstPlayerController())->GetRemoteCallObjectByClass(UDocModRCO::StaticClass()); // get the RCO instance from the player controller
rco->SetSomeStuffOfTheDocMachineRPC(machine, false); // call the RPC of the RCO

You might also want check if AFGPlayerController::GetRemoteCallObjectByClass actually returns something. It returns nothing (nullptr) under various conditions, such as when the RCO is not registered yet.

Replicated Mod Subsystems

Mod Subsystems are a concept implemented by Satisfactory Mod Loader.

Learn more about them on the Subsystems page.

Configure if a subsystem is replicated via its Replication Policy field.

Replicated subsystems are a good place to implement multicast RPCs since they will be present on all connections.

Blueprint Example

The ExampleMod uses a Multicast RPC on the ReplicationExampleSubsystem to implement the CC_ExampleReplication chat command.

C++ Example

No example is currently provided. Consider looking at an open-source mod instead.

Replicated Properties

See the video or Unreal documentation for more info on their purpose.

Blueprint Example

Variables can be configured to replicate by specifying their Replication option in the details panel.

The ExampleMod uses a replicated property with RepNotify in Build_MultiplayerDemoBuilding.

C++ Example

See the video or Unreal documentation for more info.

Replication Detail Components

Replication Detail Components were previously critical in handling replication of inventories to multiplayer clients.

As of Update 8, this system has been replaced with a new system involving structs, which Arch has informed us is planned to be phased out soon. If you have questions about this system, please ask about it on the discord, as it’s not worth documenting something that will be removed so soon.

Replicated Maps

For unknown reasons, Unreal does not provide systems that allow TMaps to be replicated. There are multiple approaches you can implement yourself to work around this:

  • Replicating an array of custom structs which have properties for key and value. The host can use a regular map, updating this array in response to map changes. On the client, implement the OnRep callback and construct a map from the array.

  • If your keys can be computed from your values, such as a map containing FGBuildables by name, replicate just an array of values and construct a map from them in the OnRep callback.

  • A more performant approach would involve creating a custom (replicating) struct to hold the map, then writing custom NetSerialize and NetDeltaSerialize overrides to efficiently handle replication of partial updates. Such an approach is most certainly not for the faint of heart, though. If your map is updating so often that the overhead of converting it to/from an array is important, reconsider if you really need to replicate all that data, and if you would encounter network problems first.

Note that replicating one array of keys and one array of values is not suggested because changes to each array are not guaranteed to arrive at the same time.

Appendix

Additional information on various topics.

Note on Client-to-Server Remote Procedure Calling

You might have noticed that triggering a Remote Procedure Call (RPC) isn’t as straightforward as it may first appear. The reason is simple: as you might be aware, to be able to call a RPC from the client, the calling object needs to be the authority of the object. This is only the case if the object is somehow owned by the player connection. The player controller, for example, is owned by the player connection.

As modders, we are not able to directly add more functionality to the player controller, so we are not able to add functions in the player connection owning scope at compile time.

Thankfully Coffee Stain has implemented a system that allows us to add functionality owned by the player connection afterwards in runtime. This system is implemented through Remote Call Objects.

Remote Call Objects (aka. RCOs) get created by the in runtime individually once for each player controller. CSS’s code handles the creation, replication, and ownership transfer to their respective player controllers for us.

The client owning the player controller is able to get the RCO instance by passing the class of the RCO to the AFGPlayerController::GetRemoteCallObjectByClass function. With that RCO reference, you will be able to call anywhere RPCs of the RCO, even in the GUI which exists only on the client side.