Skip to main content
Version: 0.1.0

Custom RPC

One of the core WukongMP SDK functionalities is the support for arbitrary Remote Procedure Call (RPC) functions. These can have arbitrary payloads and support a number of Relay Modes.

RPC handlers allow you to define your own events that are sent to other players connected to the same server.

Declaring an RPC handler class​

In order to add custom RPC procedures to your mod, you must define a partial class that extends RpcClassBase.

Minimal RPC class definition
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
// RPC handlers go here
}
important

For any of the RPC handlers to be registered, the class must be added to the DI container in your mod's Initialize method. Consult the documentation for ModBase.

Defining RPC handlers​

In order to add a new RPC handler to your mod, add a method decorated with the RpcEvent attribute.

Method names must start with "On..." — corresponding "Send..." methods for sending the RPC will be generated automatically in the same class.

Minimal RPC handler
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestOthers)]
private void OnMyCustomCall()
{
// do something when this message is received
}

// generated by the SDK
private void SendMyCustomCall() { ... }
}

The attribute requires a parameter of type RelayMode, which indicates how the message will be propagated to players when it reaches the server.

Currently, the following modes are supported:

Relay modeDescription
AreaOfInterestOthersMessage is sent to all other players on the same level
AreaOfInterestAllMessage is sent to all players on the same level, including the sender
GlobalOthersMessage is sent to all other players on the server
GlobalAllMessage is sent to all players on the server, including the sender

Sending data​

RPC handlers support passing data in parameters that are either:

An RPC hander can have any number of these parameters declared in any order. The generated "Send..." methods will have the same parameters (excluding the injected ones).

Primitive data types​

We support all primitive data types defined in the C# runtime: bool, char, sbyte, byte, short, ushort, int, uint, long, ulong, float, double, and string.

Passing primitive data
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestOthers)]
private void OnMyCustomCall(int number, string text)
{
// do something when this message is received
}

// generated by the SDK
private void SendMyCustomCall(int number, string text) { ... }
}

Complex data types​

You can use complex data structures in your RPC parameters. These must extend the INetSerializable interface.

The fields of the payload structures can be of any serializable type, including other complex types, provided they also extend INetSerializable.

Complex RPC parameter example
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestOthers)]
private void OnPlayerBuffed(BuffData payload)
{
// do something when this message is received
}

// generated by the SDK
private void SendPlayerBuffed(BuffData payload) { ... }
}

The SDK provides the [DeriveINetSerializable] attribute, which makes the SDK automatically generate serialization code for most cases. The structure must be declared partial for this to work.

Generating serialization code
[DeriveINetSerializable]
public partial struct BuffData(int buffId, float duration) : INetSerializable
{
public int BuffId = buffId;
public float Duration = duration;
}

You can also implement the interface manually if you want to have full control over the data sent over the wire.

We use the LiteNetLib library as a base for our networking stack. You can refer to its documentation to learn how to use NetDataWriter and NetDataReader.

Writing serialization code manually
public struct BuffAddData(int buffId, float duration) : INetSerializable
{
public int BuffId = buffId;
public float Duration = duration;

public void Serialize(NetDataWriter writer)
{
writer.Put(BuffId);
writer.Put(Duration);
}

public void Deserialize(NetDataReader reader)
{
BuffId = reader.GetInt();
Duration = reader.GetFloat();
}
}

Special parameters​

Right now there is only one special parameter that can be used. We might expand this functionality in future versions of the SDK.

NameTypeDescription
__senderPlayerIdThe identifier of the player sending the RPC

This parameter can be used alongside other parameters, but is not visible in the generated "Send..." method.

Using injected parameters
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestOthers)]
private void OnComplexEvent(int number, PlayerId __sender, BuffData buffData)
{
// example: do something with sender nickname
if (WukongApi.Sync.TryGetPlayerInfoById(__sender, out var playerName, out _))
{
Logging.LogInfo("Received data from {Sender}", playerName);
}
}

// generated by the SDK
private void SendComplexEvent(int number, BuffData buffData) { ... }
}