Skip to content
Merged
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,9 @@ yarn serve --locale zh-Hans # Chinese
~~~

This command generates static content into the `build` directory and can be served using any static contents hosting service.

## Add a new version

To add a new version for a documentation page, follow this guide: https://docusaurus.io/docs/docs-multi-instance#tagging-new-versions.

Remember, that the `docs:version` command expets the **current** version (one that is getting frozen), not the new one.
2 changes: 1 addition & 1 deletion blog/2026-04-27-sdk-020.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Read on for the full list of changes and the [Migration Guide](/releases/wukongm
## SDK updates

- The PvP mod is now included in the server package, alongside the co-op mod, both built on top of SDK version `0.2.0`.
- RPC event handlers built on top of [RpcClassBase](../wukong-mp/api-reference/ReadyM.Api.Multiplayer.RPC/ReadyM.Api.Multiplayer.RPC.RpcClassBase) now have access to the `RunOnMainThread` method, which allows you to schedule callbacks to run on the main thread in a safe way. This is particularly useful for RPC handlers that need to interact with the game world, as doing so from the network thread can cause crashes.
- RPC event handlers built on top of [RpcClassBase](../wukong-mp/0.2.4/api-reference/ReadyM.Api.Multiplayer.RPC/ReadyM.Api.Multiplayer.RPC.RpcClassBase) now have access to the `RunOnMainThread` method, which allows you to schedule callbacks to run on the main thread in a safe way. This is particularly useful for RPC handlers that need to interact with the game world, as doing so from the network thread can cause crashes.
- In-game chat has been re-enabled.
- API methods related to the built-in chat have been moved from the [Local API](../wukong-mp/api-reference/WukongMp.Sdk.Api/WukongMp.Sdk.Api.IWukongLocalApi) to the new [Chat API](../wukong-mp/api-reference/WukongMp.Sdk.Api/WukongMp.Sdk.Api.IWukongChatApi) for better organization and separation of concerns.
- Two new (temporary) APIs - [PvP API](../wukong-mp/api-reference/WukongMp.Sdk.Api/WukongMp.Sdk.Api.IWukongPvpApi) and [Cheats API](../wukong-mp/api-reference/WukongMp.Sdk.Api/WukongMp.Sdk.Api.IWukongCheatsApi) were used to implement the self-hosted version of the PvP mod before server-side scripting is ready. Once server-side scripting is implemented, these APIs will be removed and their functionality will be implemented using regular server-side mods instead.
Expand Down
99 changes: 99 additions & 0 deletions blog/2026-08-12-sdk-030.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
title: WukongMP SDK 0.3.0 released
description: Server-side scripting, custom component sync, a Network view in the admin panel, and co-op fixes
slug: wukongmp-sdk-030
authors: readym
tags: [wukongmp, adminPanel]
hide_table_of_contents: false
---

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](/releases/wukongmp-sdk-030#migration-guide).

<!-- truncate -->

## 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](/wukong-mp/docs/Server-development/archetypes)**, local (server-only) or networked, attached to the built-in archetypes or to new ones your mod registers.
* **[Systems](/wukong-mp/docs/Server-development/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](/wukong-mp/docs/Server-development/custom-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](/wukong-mp/docs/Server-development/getting-started), and read [Archetypes and components](/wukong-mp/docs/Server-development/archetypes) for what the built-in entities carry. The [co-op mod](https://github.com/readycodeio/WukongMP-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.

```csharp title="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`:

```csharp
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`:

```csharp
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](/wukong-mp/docs/Development/APIs/custom-components) for the client side and [Registering components and archetypes](/wukong-mp/docs/Server-development/getting-started#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](/wukong-mp/docs/Server-development/getting-started#local-components) 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](/wukong-mp/docs/Server/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](/releases/wukongmp-mods-open-source), 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 {#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](https://github.com/readycodeio/wukongmp-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`](/wukong-mp/api-reference/ReadyM.Api.Multiplayer.RPC/ReadyM.Api.Multiplayer.RPC.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](/wukong-mp/docs/Development/custom-rpc#threading).
* **Save file API types moved.** [`SaveFileType`](/wukong-mp/api-reference/ReadyM.Api.Saves/ReadyM.Api.Saves.SaveFileType) and [`FileInfo`](/wukong-mp/api-reference/ReadyM.Api.Saves/ReadyM.Api.Saves.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.
8 changes: 6 additions & 2 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ const config: Config = {
lastVersion: 'current',
versions: {
current: {
label: '0.2.4'
label: '0.3.0'
},
'0.2.4': {
label: '0.2.4',
},
'0.1.0': {
label: '0.1.0',
Expand Down Expand Up @@ -157,7 +160,8 @@ const config: Config = {
{
type: 'docsVersionDropdown',
versions: {
'current': { label: '0.2.4' },
'current': { label: '0.3.0' },
'0.2.4': { label: '0.2.4' },
'0.1.0': { label: '0.1.0' },
},
position: 'right',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ hide_table_of_contents: false

- PvP 模组现已包含在服务器包中,与合作模组一起,二者均基于 SDK 版本 `0.2.0` 构建。
- 基于
[RpcClassBase](../wukong-mp/api-reference/ReadyM.Api.Multiplayer.RPC/ReadyM.Api.Multiplayer.RPC.RpcClassBase)
[RpcClassBase](../wukong-mp/0.2.4/api-reference/ReadyM.Api.Multiplayer.RPC/ReadyM.Api.Multiplayer.RPC.RpcClassBase)
构建的 RPC 事件处理程序现在可以访问 `RunOnMainThread`
方法,该方法允许你以安全的方式将回调安排在主线程上执行。这对于需要与游戏世界交互的 RPC 处理程序尤为有用,因为在网络线程中执行此操作可能导致崩溃。
- 游戏内聊天已重新启用。
Expand Down
6 changes: 5 additions & 1 deletion i18n/zh-Hans/docusaurus-plugin-content-docs/current.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version.label": {
"message": "0.2.4",
"message": "0.3.0",
"description": "The label for version current"
},
"sidebar.wukongMpDocsSidebar.category.APIs": {
Expand Down Expand Up @@ -110,5 +110,9 @@
"sidebar.oblivionMpDocsSidebar.category.Server hosting": {
"message": "Server hosting",
"description": "The label for category 'Server hosting' in sidebar 'oblivionMpDocsSidebar'"
},
"sidebar.wukongMpDocsSidebar.category.Server-side development": {
"message": "服务器端开发",
"description": "The label for category 'Server-side development' in sidebar 'wukongMpDocsSidebar'"
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,79 @@
# 自定义组件

WukongMP SDK 允许在游戏中的实体上定义自定义数据组件。这些组件可以是从原始值到复杂结构的任何形式
WukongMP SDK 允许在游戏中的实体上定义自定义数据组件。**网络化组件**会在服务器和每个已连接客户端之间同步,因此两边看到的数值一致,而你不需要为它编写任何消息传递代码

---
一个组件由两半组成。[服务器模组](../../Server-development/getting-started#registering-components-and-archetypes)注册它并把它挂到原型上,客户端模组也做同样的事,双方用的是共享项目中的同一份定义。本页讲的是客户端这一半。

:::info[正在进行中]
## 声明组件 {#declaring-the-component}

此功能正在开发中,将在未来的 SDK 版本中提供。
把组件放在客户端模组和服务器模组都引用的**共享项目**里。它是一个带 `[DeriveINetworkedComponent]` 特性的 `partial struct`,生成器会把每个私有字段变成公有属性,并生成序列化代码:

```csharp title="ExampleMod.Common/BountyComponent.cs"
using System.Runtime.InteropServices;
using ReadyM.Api.Multiplayer.Generators;

[DeriveINetworkedComponent]
[StructLayout(LayoutKind.Auto)]
public partial struct BountyComponent
{
private int _kills;
private float _multiplier;
}
```

组件字段用到的任何类型也必须放在共享项目里,理由相同:两边绝不能对它的内存布局产生分歧。

## 在客户端注册 {#registering-it-on-the-client}

在模组的 `Initialize` 中要做两件事:注册组件类型,以及注册一个把它挂到实体上的原型变更。

```csharp title="Mod.cs"
protected override void Initialize(IDependencyContainer services)
{
services.Resolve<IComponentApi>().RegisterComponent<BountyComponent>();
services.RegisterSingleton<IArchetypeRegistration, BountyRegistration>();
}
```

[`IComponentApi.RegisterComponent<T>`](../../../api-reference/WukongMp.Api/WukongMp.Api.IComponentApi) 把类型声明给网络层。原型变更则写在一个实现 [`IArchetypeRegistration`](../../../api-reference/ReadyM.Api.ECS.Registry/ReadyM.Api.ECS.Registry.IArchetypeRegistration) 的类中,SDK 会在构建 ECS 世界时调用它:

```csharp title="BountyRegistration.cs"
using ReadyM.Api.ECS.Registry;
using ReadyM.Api.ECS.Worlds;
using WukongMp.Sdk.Api;

public class BountyRegistration : IArchetypeRegistration
{
public void Register(IArchetypeRegistry registry)
{
registry.ModifyArchetype(WukongApi.Archetypes.GlobalPlayerArchetype, b => b.Add<BountyComponent>());
}
}
```

`WukongApi.Archetypes` 用来指定内置原型,与服务器 SDK 以静态成员形式在 `WukongArchetypes` 上暴露的是同一套。每个原型带有哪些组件、你的数据该放在哪一个上,请参见[原型与组件](../../Server-development/archetypes)。

:::note

`WukongApi.Archetypes` 是 `WukongApi` 中唯一可以在 `Register` 内部安全使用的部分。它自身没有依赖项,而其他 API 会在 ECS 世界还在构建时把它重新拉回容器。

:::

注册是叠加式的,所以注册你自己的并不会顶替掉 SDK 的。`ModifyArchetype` 是挂到已有原型上;同一个接口上的 `RegisterArchetype` 则创建一个新原型,适用于你的模组要生成自己的实体种类的情况。

## 让两边保持一致 {#keeping-the-two-sides-in-step}

组件 ID 是按位置分配的。它们按注册顺序编号,以一个字节的形式在网络上传输,并且在连接时不会重新协商。如果客户端和服务器注册了不同的组件,或者以不同顺序注册了相同的组件,这种不匹配不会被报告出来:接收方会从数据流中读出错误的组件。

实际操作中这并不难做对,只要你:

* 把每个组件定义都放在共享项目里,这样内存布局不会漂移,
* 每一侧的组件注册都集中在一处,并保持相同顺序,
* 在两边把它们挂到相同的原型上,
* 把客户端模组和服务器模组作为同一个包的同一版本一起发布。

[模组模板](https://github.com/readycodeio/wukongmp-mod-template)就是围绕这一点组织的:`ExampleMod.Common` 存放组件,`ExampleMod` 注册客户端这一半,`ExampleMod.Serverside` 注册服务器这一半。

## 仅服务器的状态 {#server-only-state}

如果客户端没有必要看到某个值,那么在客户端根本不需要为它准备组件。服务器 SDK 提供了[本地组件](../../Server-development/getting-started#local-components),用于永远不离开服务器的状态,而且它们不占用任何网络流量。一次性事件而非状态,请用 [RPC](../custom-rpc)。
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ mode](../../../api-reference/ReadyM.Api.Multiplayer.Protocol.Enums/ReadyM.Api.Mu
请参阅 [RPC 文档](../custom-rpc),以了解在 RPC 类中你还能做些什么。

```csharp title="自定义 RPC 类"
public partial class Rpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
public partial class Rpc : ClientRpcHandler
{
[RpcEvent(RelayMode.AreaOfInterestAll)]
private void OnSwarmStarted(PlayerId __sender)
Expand Down
Loading
Loading