Skip to main content

WukongMP SDK 0.3.0 released

· 7 min read
ReadyM Team
Creators of WukongMP and OblivionMP

Server-side scripting is here. A mod can now run its own code inside the relay server: register components on server entities, tick systems on the server loop, and answer requests from clients over RPC. This is the piece we said was coming and the co-op mod already runs on it.

Custom components now sync both ways too, so a mod can put its own state on an entity and have the server and every client agree on it without writing any messaging code.

This release also adds a Network view to the admin panel, which shows what the relay is actually sending, broken down per ECS component. Alongside that, three co-op fixes, including NPCs that could turn hostile and block quests.

Update your server binaries to 0.3.0. Client mods built on 0.2.4 need a rebuild and a few small code changes, see the Migration guide.

Server-side mods

A server-side mod is a .NET class library that the relay server loads from its server_mods/ directory, separate from the client-facing mods/ folder. Drop the assembly in and the server picks it up on startup.

What it can do:

  • Components on server entities, local (server-only) or networked, attached to the built-in archetypes or to new ones your mod registers.
  • Systems that tick on the server's update loop, for logic that has to run continuously rather than in response to a single client.
  • Server RPC, a request and response channel between a client mod and the server, declared once as a contract shared by both halves.

Start with Getting started, and read Archetypes and components for what the built-in entities carry. The co-op mod is a small worked example of all three: two systems, one RPC handler, and a shared contract project.

The API reference now covers the server SDK as well, under ReadyM.Relay.Server.Sdk and ReadyM.Wukong.Common.

Custom data sync

Networked components replicate both ways in 0.3.0. Declare a partial struct with [DeriveINetworkedComponent] in a project both halves of your mod reference, register it on each side, attach it to an archetype, and the values keep themselves in sync. No messaging code, no manual serialization.

The component, in the shared project
[DeriveINetworkedComponent]
[StructLayout(LayoutKind.Auto)]
public partial struct BountyComponent
{
private int _kills;
private float _multiplier;
}

The server mod registers it in RegisterComponents and attaches it in Init:

registry.RegisterComponent<BountyComponent>();
// ...
archetypeRegistry.ModifyArchetype(WukongArchetypes.GlobalPlayerArchetype, b => b.Add<BountyComponent>());

The client mod does the same from its Initialize, through IComponentApi and an IArchetypeRegistration:

services.Resolve<IComponentApi>().RegisterComponent<BountyComponent>();
services.RegisterSingleton<IArchetypeRegistration, BountyRegistration>();

Both sides name the same archetype: WukongArchetypes on the server, WukongApi.Archetypes on the client. See Custom components for the client side and Registering components and archetypes for the server side.

The one rule worth internalising: register the same components in the same order on both sides. Component IDs are positional and travel as a byte on the wire, so a mismatch misreads the stream rather than failing loudly. Keeping the definitions in the shared project and doing the registration in one place per side is enough to stay safe.

If the client has no business seeing a value, skip all of this and use a local component instead. Those never leave the server and cost nothing on the wire.

Network view in the admin panel

The panel has a new Network tab, showing live relay traffic in one second windows: ingress and egress, connected peers, server tick timing, and protocol overhead.

The part worth your attention as a mod author is the per-component breakdown. It attributes payload bytes to individual ECS components, with fan-out per component, so you can see what a component that replicates every tick actually costs before you ship it. The tick duration chart covers the same update your systems run inside, which makes it the first place a system doing too much per tick shows up.

Viewing it needs the Dashboard access permission. See Network stats for how to read the numbers, in particular the difference between wire bytes and payload bytes.

Co-op fixes

  • NPCs could turn hostile and block quests. Monsters were created in the shared world with a default team ID instead of the team the game assigns them, so quest NPCs could end up hostile and leave the quest unfinishable. They now keep their real team.
  • Pagoda debuff sync. The periodic Beguiling Chant debuff in the Pagoda region in act 3 now stays in sync between players. Its cycle runs on the server, so everyone in the area gets the same warning and the same active window.
  • Boss HP scaling moved to the server, with a new default. Scaling elite and boss HP by player count is now a server-side system rather than client logic, and the default changed. It used to be 100% plus 150% for every extra player: 100% solo, 250% for two, 400% for three. It is now a flat 100% per player: 100% solo, 200% for two, 300% for three.
  • New bosshp command. bosshp <percent> sets the per-player multiplier, so bosshp 150 gives you 150% per player. It applies server-wide and confirms the new value in chat.

What's next?

We are continuing to move the PvP mod's logic into a server-side mod. Once that is done, the temporary PvP and Cheats APIs introduced in 0.2.0 can go away, as planned.

Further out, we plan to open-source the SDKs themselves, both client-side and server-side. The co-op and PvP mods have been public since May, and opening the layer underneath them is the logical next step: you get to read the code your mod is built on, and fixes stop having to wait for us. More on that when we have a date.

Migration guide

Updating a client mod from 0.2.4 to 0.3.0:

  • Update your server binaries to 0.3.0. Older servers cannot load mods built on the 0.3.0 SDK.
  • Rebuild your mod against the 0.3.0 SDK. Download the latest mod template and copy your mod files over, then update the minimum SDK version in your manifest.json dependencies.
  • RPC classes changed base class. RpcClassBase is now ClientRpcHandler, and it no longer takes IRpcClient and IRelaySerializer in the constructor, since the SDK injects them. Replace public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer) with public partial class MyRpc : ClientRpcHandler.
  • RunOnMainThread is gone. [RpcEvent] handlers are now scheduled onto the game thread for you, so unwrap those callbacks and put the body straight in the handler. See Custom RPC.
  • Save file API types moved. SaveFileType and FileInfo are no longer in WukongMp.Sdk, they now live in ReadyM.Api.Saves, shared with the server. Update your using directives.
  • If you write a class extending ServerRpcClient to talk to a server-side mod, it must be annotated with [ServerRpcFor(typeof(YourContracts))] naming the contract class it implements. Same for ServerRpcHandlersBase on the server. Classes using only [RpcEvent] are unaffected.