Multiplayer

Adding Multiplayer support to mods is just like adding multiplayer support to other Unreal Engine projects.

We won’t discuss how replication works in here, so you should really read through Unreal’s documentation about replication before continuing.

Again, please read first the Unreal documentation and Wiki about replication first before continuing with this guide! Replication can be a difficult concept to understand, but understanding it is crucial to making your mods support multiplayer.

There are still some parts that are very difficult to archive, and how to work around them, we will discuss in here further.

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.

Our problem now is, we are not able to add more functionality to the player controller and so we are not able to add functions in the player connection owning scope.

CSS was so nice and 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 CSS code in runtime individually once for every player controller there is. They get created, replicated, and then become owned by their respective player controllers.

Now, 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.

But before any client can even do that, you need to tell the game to create these RCO instances when a player spawns. You simply need to register your RCO class by calling the AFGGameMode::RegisterRemoteCallObjectClass function. CSS recommends to call this function in AFGGameMode::PostLogin when AFGGameMode::IsMainMenuGameMode returns false and the game instance has authority of the game mode (if it is host). But it should also be fine to call it on server one time before it could get used by something.

Here is some example C++ code we use in the StartupModule function to register a RCO:

SUBSCRIBE_METHOD("?PostLogin@AFGGameMode@@UEAAXPEAVAPlayerController@@@Z", AFGGameMode::PostLogin, [](auto& scope, AFGGameMode* gm, APlayerController* pc) {
	if (gm->HasAuthority() && !gm->IsMainMenuGameMode()) {
		gm->RegisterRemoteCallObjectClass(UDocModRCO::StaticClass());
	}
});

The RCO itself just needs to derive from FGRemoteCallObject and then contains the RPCs you want to be able to call as client.

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.

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. If it returns nothing (nullptr) under various conditions, such as when the RCO is not registered yet.