添加世界逻辑 type
自 SDK 1.2 起,插件可以为世界逻辑添加新的触发器 · 条件 · 动作。通过 host.AddSceneEvent 注册的 type 与内置 type 一样出现在以下位置。
| 出现位置 | 显示内容 |
|---|---|
| 世界逻辑表单 | 触发器 · 条件 · 动作菜单中的 Category 分组、字段输入 |
| 逻辑图 | 节点与引脚 |
| 检查 | 必填字段 · Actor kind · 数值范围 · enum 值 |
| 规则一行说明 | Summary 模板 |
| 运行预览 | Simulate 处理 · 「触发」按钮 |
| AI 连接(MCP) | 在 scene_rules section=schema 列表中显示 [<插件 id>] |
编辑器中的注册用于表单 · 检查 · MCP · 预览。若要在游戏服务器上实际运行,还必须在服务器插件 NP Scene 中注册相同的 type。参见下方「服务器端」。
在 plugin.json 中写 "sdk": "^1.2" 或更高。使用字段类型 ActorList 时为 "^1.3"。
host.AddSceneEvent(new NpSceneEvent(NpSceneEventRole.Trigger, "myquest_npc_talk", "NPC 에게 말 걸 때", "퀘스트", new[] { new NpSceneField("npc", NpSceneFieldKind.Actor, Required: true, ActorKinds: new[] { "display", "text", "spawn" }, Label: "NPC"), new NpSceneField("line", NpSceneFieldKind.Text, Label: "대사 id"), }, Help: "플레이어가 NPC 와 대화를 시작할 때", Icon: "interact", Summary: "플레이어가 NPC {npc} 에게 말을 걸면")); // 无 Simulate = 只能用「触发」按钮
host.AddSceneEvent(new NpSceneEvent(NpSceneEventRole.Action, "myquest_start", "퀘스트 시작", "퀘스트", new[] { new NpSceneField("quest", NpSceneFieldKind.Text, Required: true, Label: "퀘스트 id"), }, Icon: "star", Summary: "퀘스트 {quest} 시작", Simulate: s => { s.Log($"퀘스트 {s.Str("quest")} 시작 → {s.Player}"); return true; }));使用这些 type 的规则在 scene.json 中这样保存(规则的一部分)。
{ "trigger": { "type": "myquest_npc_talk", "npc": "npc_guard" }, "actions": [ { "type": "myquest_start", "quest": "q1" } ]}NpSceneEvent
Section titled “NpSceneEvent”public sealed record NpSceneEvent(NpSceneEventRole Role, string Type, string Label, string Category, IReadOnlyList<NpSceneField> Fields, string Help = "", string Icon = "event", string Summary = "", Func<NpSceneSim, bool>? Simulate = null);public enum NpSceneEventRole { Trigger, Condition, Action }| 字段 | 必填 | 说明 |
|---|---|---|
Role | 是 | 角色:Trigger(触发器) · Condition(条件) · Action(动作) |
Type | 是 | 保存名称(JSON "type")。在前面加上自己的名称(见下方「命名规则」)。 |
Label | 是 | 在表单 · 图中显示的名称 |
Category | 否 | 菜单分组名称。留空时为插件名称(plugin.json 的 name) |
Fields | 是 | 字段列表(NpSceneField)。没有字段时为空数组 |
Help | 否 | 说明(工具提示) |
Icon | 否 | 图标名称(trigger · event · signal · interact · logic · star …)。默认 event,未知名称时使用默认图案 |
Summary | 否 | 一行说明模板(见下方「Summary 模板」)。留空时为 Label |
Simulate | 否 | 运行预览处理(见下方「Simulate」) |
插件 type 在同一分组中排在内置 type 之后。
NpSceneField
Section titled “NpSceneField”public sealed record NpSceneField(string Name, NpSceneFieldKind Kind, bool Required = false, string Default = "", string[]? Options = null, string[]? ActorKinds = null, string Label = "", string Help = "", double? Min = null, double? Max = null);| 字段 | 说明 |
|---|---|
Name | JSON 键 |
Kind | 字段类型(见下表) |
Required | 若为必填,为空时检查报错 |
Default | 默认值字符串。格式见下表的「Default 写法」 |
Options | Enum 的选项 |
ActorKinds | Actor 字段接受的 Actor kind(trigger · blocker · display · text · spawn · camera · path …)。留空时接受任意 kind |
Label · Help | 表单中显示的名称 · 说明 |
Min · Max | Number 的范围(检查) |
字段类型 NpSceneFieldKind
Section titled “字段类型 NpSceneFieldKind”| 类型 | 表单输入 | JSON 值 | Default 写法 | SDK |
|---|---|---|---|---|
Text | 文本 | 字符串 | "안녕" | 1.2 |
Number | 数值 | 数值 | "1.5" | 1.2 |
Bool | 勾选 | true / false | "true" | 1.2 |
Enum | 选项之一(Options) | 字符串 | "none" | 1.2 |
Actor | 选择 Actor(仅 ActorKinds) | Actor id 字符串 | Actor id | 1.2 |
Anim | 动画片段 | 片段 id 字符串 | 片段 id | 1.2 |
SoundEvent | 声音事件 | "ns:path" 字符串 | "minecraft:block.note_block.bell" | 1.2 |
Block | 方块状态 | 字符串(minecraft:stone) | 方块状态字符串 | 1.2 |
Item | 物品 | 字符串(minecraft:diamond_sword) | 物品 id | 1.2 |
Particle | 粒子 | 字符串(minecraft:happy_villager) | 粒子 id | 1.2 |
Effect | 药水效果 | 字符串(minecraft:speed) | 效果 id | 1.2 |
Vec3 | x · y · z | [x, y, z] | "[0,1,0]" | 1.2 |
Signal | 信号名称 | 字符串 | 信号名称 | 1.2 |
Flag | 场景标志名称 | 字符串 | 标志名称 | 1.2 |
ActorList | 多个 Actor | Actor id 数组 | "cam_a,cam_b" | 1.3 |
还有一些仅内置 type 使用的字段类型:标志值 · 函数(调用逻辑函数) · 方块格列表(
Cells)。这些类型尚未加入 SDK。字段显示条件 · 仅限整数 · 允许#标签等字段选项也尚未加入 SDK。
插件 type 也会作为节点出现在逻辑图中。引脚规则与内置 type 相同。
| 引脚 | 可连接数量 |
|---|---|
| 执行引脚(白色箭头) | 一条链。一个节点后接一个节点 |
| 函数引脚(黄色菱形) | 一个引脚一条线。对已连接的引脚再次连接时会替换。 |
| 数据引脚(紫色圆形,Actor 引用) | 一个 Actor 可以连接到多个字段。 |
Actor字段接收数据引脚。在图中连接 Actor 时会检查ActorKinds。- 函数引脚只存在于内置的「调用函数」类节点中。插件 type 无法创建函数引脚。
自 0.9.11 起:调用函数的黄色引脚每个引脚只能连一条线。
Summary 模板
Section titled “Summary 模板”用于规则列表中一行说明的模板。{字段名} 处会填入该字段的值。
| 模板 | 字段值 | 显示文字 |
|---|---|---|
퀘스트 {quest} 시작 | quest = q1 | 퀘스트 q1 시작 |
플레이어가 NPC {npc} 에게 말을 걸면 | npc = npc_guard(名称「경비병」) | 플레이어가 NPC 「경비병」 에게 말을 걸면 |
퀘스트 {quest} 가 {state} 일 때 | state 为空,Default = none | 퀘스트 q1 가 none 일 때 |
Actor字段会以「Actor 名称」代替 Actor id 显示。- 没有值的字段用
Default填充。
Simulate — 运行预览
Section titled “Simulate — 运行预览”Simulate 是在运行 · 模拟期间预先执行规则的函数。游戏服务器不使用它。
| 角色 | Simulate 的作用 | 返回值 | 没有时 |
|---|---|---|---|
| Trigger | 预览的每个 tick 中,对该 type 的每条规则各调用一次。要触发时调用 s.Fire(player) | 忽略 | 只能通过世界逻辑 · 运行面板的「触发」按钮触发 |
| Condition | 判定真 · 假 | true = 真 | 始终视为真 |
| Action | 执行动作(记录 · 更改自身状态) | 忽略 | 只留下记录 |
Simulate 中发生异常时,该插件被隔离。该插件的所有 type 都会被移除,预览继续运行。
NpSceneSim
Section titled “NpSceneSim”Simulate 接收的值。
| 成员 | 类型 | 说明 |
|---|---|---|
Role · Type | NpSceneEventRole · string | 当前处理的角色 · type |
RuleId | string | 规则 id |
Player | string? | 触发者。在触发器处理中为 null |
Players | IReadOnlyList<string> | 当前的玩家们 |
Now | double | 预览时刻(秒) |
Args | IReadOnlyDictionary<string, string> | 节点字段值(全部为字符串) |
Str(string key, string fallback = "") | string | 字段值字符串 |
Num(string key, double fallback = 0) | double | 字段值数值 |
Bool(string key, bool fallback = false) | bool | 字段值 "true" / "false" |
PositionOf(string player) | NpVec3? | 玩家脚下位置。没有时为 null |
Log(string text) | void | 一行预览记录([<id>] …) |
Fire(string? player) | void | (仅 Trigger 处理)触发此规则。在其他角色中不起作用 |
Args 值的字符串形式如下。
| 字段值 | Args 字符串 |
|---|---|
| 字符串 | 原样 |
| 数值 | "1.5"(点作小数点,与区域设置无关) |
| 真 · 假 | "true" / "false" |
数组 · 对象(Vec3 · ActorList) | JSON 字符串("[0,1,0]")。ActorList 若以逗号字符串保存,也按原样传入。 |
示例:自行触发的触发器
Section titled “示例:自行触发的触发器”host.AddSceneEvent(new NpSceneEvent(NpSceneEventRole.Trigger, "myquest_near_spawn", "스폰 가까이 올 때", "퀘스트", new[] { new NpSceneField("r", NpSceneFieldKind.Number, Default: "5", Label: "반경", Min: 1, Max: 64), }, Summary: "스폰 {r}칸 안에 들어오면", Simulate: s => { foreach (var p in s.Players) if (s.PositionOf(p) is { } pos && Math.Abs(pos.X) + Math.Abs(pos.Z) < s.Num("r", 5)) s.Fire(p); return true; }));- 此函数在每个预览 tick 调用,请写得轻量。
- 过滤(仅一次 · 冷却等)和条件由编辑器在
Fire之后检查。
示例:记住状态的条件 · 动作
Section titled “示例:记住状态的条件 · 动作”private readonly Dictionary<string, string> _quests = new(); // 玩家:任务 → 状态
// 条件:任务状态Simulate: s => Get(s.Player, s.Str("quest")) == s.Str("state", "none")
// 动作:开始任务Simulate: s => { _quests[(s.Player ?? "") + ":" + s.Str("quest")] = "active"; s.Log("시작"); return true; }
// 新世界中清空host.WorldChanged += _ => _quests.Clear();完整代码见示例: event_example。
| 规则 | 原因 |
|---|---|
在 Type 前加上自己的名称(myquest_start)。 | 同一角色中已存在相同 type 时(含内置)注册异常 → 该插件被隔离 |
使用小写字母 · 数字 · _。 | 便于与服务器插件 · JSON 保持一致。 |
用过的 Type 不要更改。 | 已保存的规则使用该名称。 |
不要使用内置 type 名称(cinematic · enter · sound …)。 | 与内置重名时插件无法启用。内置列表见世界逻辑 type 列表。 |
- 该插件的 type 从菜单 · 图的节点面板中移除。
- 使用该 type 的规则不会被删除,而是变为「未知 type」注意,字段值原样保留。
- 在预览中,未知触发器会跳过该规则,未知条件为假,未知动作只跳过该动作。
在游戏中执行规则的是服务器插件 NP Scene(Paper)。编辑器插件不在服务器上运行。
- 将相同 type 的处理器制作为 Paper 插件。
- 从 Bukkit
ServicesManager获取 NP Scene 的SceneEventsApi并注册该 type。 - 让字段名和值格式(JSON)与编辑器端的
NpSceneField完全一致。
服务器不认识的 type 会伴随一次警告被跳过(触发器 = 跳过规则,条件 = 假,动作 = 只跳过该动作)。因此即使没有服务器处理器,地图也能打开,但该规则不会运行。