跳到主要内容
版本:Next

Custom RPC

Server RPC lets a client send a request to the server and, optionally, receive a response. Unlike client-relayed RPC, the server always runs your code first — it decides whether and how to reply.

A server RPC is defined once, as a contract shared between the server mod and the client mod, in a common project both reference. The contract declares the shape of each direction of the message; the server mod implements the handler, and the client mod implements the response handler described in Handling server RPC on the client.

Declaring a contract

A contract is a partial static class decorated with [ServerRpcContracts], containing one partial method per RPC. Each method must be marked with [ClientToServer], [ServerToClient], or both, declaring which direction(s) it carries:

A contract with a one-way command and a request/response pair
using ReadyM.Api.Multiplayer;

[ServerRpcContracts]
public static partial class RpcContracts
{
// one-way: the client asks, the server acts, no reply
[ClientToServer] public static partial void ChooseProfession(Profession profession);

// request/response: empty request, int response — the two directions can have
// different payloads, since they are declared as separate overloads
[ClientToServer] public static partial void GetWalletBalance();
[ServerToClient] public static partial void GetWalletBalance(int amount);
}
  • A method with only [ClientToServer] is a one-way command: the client sends it, the server handles it, and there is no reply.
  • A method with only [ServerToClient] is a one-way push: only the server can send it.
  • Two overloads of the same name, one of each attribute, form a request/response pair — their payloads don't need to match.
  • A single method carrying both attributes is a symmetric two-way message, sharing one payload shape.

Parameters follow the same rules as client-relayed RPC — primitive types or [DeriveINetSerializable] structs.

Declaring server-side handlers

Implement the server side of a contract in a partial class extending ServerRpcHandlersBase. For every [ClientToServer] leg of the contract, define a matching On... partial method; the SDK generates the corresponding Send... method for any [ServerToClient] leg.

Server-side RPC handler
public partial class ServerRpc(EcsApi ecsApi) : ServerRpcHandlersBase
{
partial void OnChooseProfession(RpcContext context, Profession profession)
{
ecsApi.Query<ProfessionComponent>((ref comp) => { comp.Profession = profession; });
}

partial void OnGetWalletBalance(RpcContext context)
{
ecsApi.Query<MainCharacterComponent, WalletComponent>((ref mainCharacter, ref wallet) =>
{
if (mainCharacter.PlayerId == context.Sender)
{
// generated by the SDK
SendGetWalletBalance(context.Sender, wallet.Balance);
}
});
}
}

The RpcContext parameter is always injected first and exposes Sender, the PlayerId of the client that sent the request. Generated Send... methods take a PlayerId recipient as their first parameter, followed by the response payload.

important

For any of the RPC handlers to be registered, the class must be added to the dependency injection container in your mod's Init method.

protected override void Init()
{
Services.RegisterSingleton<ServerRpc>();
}

A client can only ask the server to act on its own behalf — nothing stops a malformed or malicious request from naming someone else's entity. Always check context.Sender against the entity you're about to modify before applying any change, as in the example above.

Client side

See Handling server RPC on the client for how to send requests and handle responses from your client-side mod.