WukongMP SDK 0.3.0 released
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.
[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
bosshpcommand.bosshp <percent>sets the per-player multiplier, sobosshp 150gives 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 the0.3.0SDK. - Rebuild your mod against the
0.3.0SDK. Download the latest mod template and copy your mod files over, then update the minimum SDK version in yourmanifest.jsondependencies. - RPC classes changed base class.
RpcClassBaseis nowClientRpcHandler, and it no longer takesIRpcClientandIRelaySerializerin the constructor, since the SDK injects them. Replacepublic partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)withpublic partial class MyRpc : ClientRpcHandler. RunOnMainThreadis 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.
SaveFileTypeandFileInfoare no longer inWukongMp.Sdk, they now live inReadyM.Api.Saves, shared with the server. Update yourusingdirectives. - If you write a class extending
ServerRpcClientto talk to a server-side mod, it must be annotated with[ServerRpcFor(typeof(YourContracts))]naming the contract class it implements. Same forServerRpcHandlersBaseon the server. Classes using only[RpcEvent]are unaffected.
