跳转到内容

添加世界逻辑 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" } ]
}
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 之后。

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);
字段说明
NameJSON 键
Kind字段类型(见下表)
Required若为必填,为空时检查报错
Default默认值字符串。格式见下表的「Default 写法」
OptionsEnum 的选项
ActorKindsActor 字段接受的 Actor kind(trigger · blocker · display · text · spawn · camera · path …)。留空时接受任意 kind
Label · Help表单中显示的名称 · 说明
Min · MaxNumber 的范围(检查)
类型表单输入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 id1.2
Anim动画片段片段 id 字符串片段 id1.2
SoundEvent声音事件"ns:path" 字符串"minecraft:block.note_block.bell"1.2
Block方块状态字符串(minecraft:stone)方块状态字符串1.2
Item物品字符串(minecraft:diamond_sword)物品 id1.2
Particle粒子字符串(minecraft:happy_villager)粒子 id1.2
Effect药水效果字符串(minecraft:speed)效果 id1.2
Vec3x · y · z[x, y, z]"[0,1,0]"1.2
Signal信号名称字符串信号名称1.2
Flag场景标志名称字符串标志名称1.2
ActorList多个 ActorActor id 数组"cam_a,cam_b"1.3

还有一些仅内置 type 使用的字段类型:标志值 · 函数(调用逻辑函数) · 方块格列表(Cells)。这些类型尚未加入 SDK。字段显示条件 · 仅限整数 · 允许 #标签 等字段选项也尚未加入 SDK。

插件 type 也会作为节点出现在逻辑图中。引脚规则与内置 type 相同。

引脚可连接数量
执行引脚(白色箭头)一条链。一个节点后接一个节点
函数引脚(黄色菱形)一个引脚一条线。对已连接的引脚再次连接时会替换。
数据引脚(紫色圆形,Actor 引用)一个 Actor 可以连接到多个字段。
  • Actor 字段接收数据引脚。在图中连接 Actor 时会检查 ActorKinds。
  • 函数引脚只存在于内置的「调用函数」类节点中。插件 type 无法创建函数引脚。

自 0.9.11 起:调用函数的黄色引脚每个引脚只能连一条线。

用于规则列表中一行说明的模板。{字段名} 处会填入该字段的值。

模板字段值显示文字
퀘스트 {quest} 시작quest = q1퀘스트 q1 시작
플레이어가 NPC {npc} 에게 말을 걸면npc = npc_guard(名称「경비병」)플레이어가 NPC 「경비병」 에게 말을 걸면
퀘스트 {quest} 가 {state} 일 때state 为空,Default = none퀘스트 q1 가 none 일 때
  • Actor 字段会以「Actor 名称」代替 Actor id 显示。
  • 没有值的字段用 Default 填充。

Simulate 是在运行 · 模拟期间预先执行规则的函数。游戏服务器不使用它。

角色Simulate 的作用返回值没有时
Trigger预览的每个 tick 中,对该 type 的每条规则各调用一次。要触发时调用 s.Fire(player)忽略只能通过世界逻辑 · 运行面板的「触发」按钮触发
Condition判定真 · 假true = 真始终视为真
Action执行动作(记录 · 更改自身状态)忽略只留下记录

Simulate 中发生异常时,该插件被隔离。该插件的所有 type 都会被移除,预览继续运行。

Simulate 接收的值。

成员类型说明
Role · TypeNpSceneEventRole · string当前处理的角色 · type
RuleIdstring规则 id
Playerstring?触发者。在触发器处理中为 null
PlayersIReadOnlyList<string>当前的玩家们
Nowdouble预览时刻(秒)
ArgsIReadOnlyDictionary<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 若以逗号字符串保存,也按原样传入。
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 之后检查。
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)。编辑器插件不在服务器上运行。

  1. 将相同 type 的处理器制作为 Paper 插件。
  2. 从 Bukkit ServicesManager 获取 NP Scene 的 SceneEventsApi 并注册该 type。
  3. 让字段名和值格式(JSON)与编辑器端的 NpSceneField 完全一致。

服务器不认识的 type 会伴随一次警告被跳过(触发器 = 跳过规则,条件 = 假,动作 = 只跳过该动作)。因此即使没有服务器处理器,地图也能打开,但该规则不会运行。