触发器与自动化
触发器是消息、定时任务和服务器事件自动化的统一入口。旧可视化文档和 XFE Script v1 会在内存中自动编译为 Trigger Program V2;只有用户保存时才持久化 V2 修订,并保留上一个修订作为回滚窗口。V2 使用稳定节点 ID、强类型表达式、语句树和确定性随机种子。XFE Script 是有界 DSL,不是 Java/JVM 脚本,也不能访问文件、网络或宿主类。
消息发送者名称可在 Web“设置”页全局配置,默认是 XFEServerManager,保存后立即生效。普通文本和未显式设置发送者的富文本会继承该值;富文本也可为单条消息覆盖发送者或隐藏发送者。
组、版本与迁移
- 一个触发器组可整体启停,并包含多个独立触发器。
- 单服最多 256 个触发器组、1,024 个触发器;单组最多 256 个触发器。上限在 SQLite 写事务内强制,不能通过并发创建绕过。
- 触发器和组都使用 revision compare-and-swap;旧页面保存时返回
409 Conflict,不会覆盖新修改。 - 首次升级会把每次入服欢迎、首次入服欢迎、按玩家每日公告、规则提醒和维护放行提醒,按原投递顺序迁移到“
Migrated messages / 已迁移消息”组;迁移是幂等的,所有原本可达的非空消息都会保留。 - 迁移后触发器数据库是消息的唯一事实源。旧
joinExperienceEnabled总开关及各消息配置字段仅保留为迁移输入,不能再通过/settings修改;请改为启停迁移组或其中的触发器。
administrator 可创建、编辑、停用和删除普通触发器;所有写入要求 CSRF、TOTP 和近期认证。server_command 与 player_command 等价于控制台能力,因此只允许具有 CONSOLE_EXECUTE 的 owner 保存。高风险操作在执行前必须先进入安全审计队列,审计不可用时故障关闭。
内置事件
| 类别 | 事件 ID |
|---|---|
| 定时 | schedule.daily, schedule.interval |
| 服务器 | server.started, server.stopping |
| 玩家会话 | player.join, player.leave, player.respawn, player.dimension_change |
| 玩家输入与交互 | player.chat, player.command, player.command_trigger, player.interact, player.entity_interact, player.attack |
| 玩家状态 | player.death, player.hurt, player.heal, player.sleep, player.wake, player.advancement |
| 物品 | player.item_pickup, player.item_drop, player.item_use, player.item_use_finish, player.item_craft |
| 方块 | block.break, block.place, block.change, block.grow, block.tool_modify |
| 实体 | entity.spawn, entity.remove, entity.death |
| 世界与区块 | world.explosion, world.weather_change, world.load, world.unload, chunk.load, chunk.unload |
| 区域 | region.enter, region.leave, region.stay |
| 崩服防护 | protection.item_overflow, protection.mob_overflow, protection.entity_overflow, protection.mod_entity_overflow, protection.spawn_burst, protection.command_block_rate, protection.slow_tick, protection.loaded_chunk_overflow, protection.memory_pressure |
这些是四个目标 Forge 版本上具备稳定事件 API 的内置集合。第三方模组或版本特有事件可从服务端代码发布为命名空间事件:
ForgeManagementRuntime.fireCustomTrigger(
"custom.example.boss_defeated",
Map.of("boss.id", "example:ancient_guardian", "party.size", 4));
custom 只是 UI 中的选择占位符,真正保存的 ID 必须匹配 custom.<namespace>.<event>。这使任何模组事件都能接入,同时避免把没有实际 Forge 回调的事件伪装成内置支持。
protection.* 事件由崩服防护的有界扫描器和即时熔断器产生。事件阈值采用严格“超过”语义,并支持冷却;其上下文包含数量、阈值、范围、维度/区块、模组命名空间和实际采取的动作。
定时配置
schedule.daily:time=HH:mm,并提供 IANAtimezone,例如Asia/Shanghai。schedule.interval:总间隔为 1 秒到 31,536,000 秒。模块化 UI 提供小时、分钟、秒输入, 保存时规范化为总seconds;API/XFE Script 既可继续使用旧格式seconds=<总秒数>,也可同时 使用hours、minutes、seconds分量(分和秒为 0..59)。执行点按服务器本地墙钟边界对齐; 例如服务器本地时间为 12:58、间隔为 15 分钟时,下一次执行是 13:00,之后依次为 13:15、13:30、13:45,而不是从编辑或启动时刻顺延。
玩家自定义指令
player.command_trigger 会把一个启用的触发器实时注册成游戏指令。command 是不带 / 的
小写指令根;arguments 是按空格分隔、从左到右依次声明的参数名。例如
command=reward、arguments=target amount 会注册 /reward <target> <amount>。指令根和参数名
必须匹配 [a-z][a-z0-9_-]{0,31},参数不能重名且最多 16 个;每个参数读取一个不含空格的
Brigadier word。只有玩家可以执行这种触发指令。
命令保存、启停、删除或所属组启停后,服务端会立即重建自己拥有的指令节点,并向在线玩家
重新发送命令树;数据包重载时也会恢复注册。指令根不能覆盖 Minecraft、其他模组或另一个
已启用触发器的指令。执行上下文提供 command.name、command.raw 和每个 args.<参数名>;
例如条件可读取 args.amount,消息或其他操作可插入 {args.target}。一次输入只会定向触发
注册该指令的那一个触发器,不会广播给所有 player.command_trigger 订阅者。
调度使用带 token 的短租约。过期租约会先重放它原有的 occurrence,再计算当天或下一间隔,因此跨日重启不会覆盖尚未结算的任务。若在第一个动作前失败,本次任务会延迟重试;一旦至少一个动作已经成功,本 occurrence 会在其余动作失败时记为“部分成功”并完成,以免重复先前副作用。进程恰好在动作生效后、完成状态落库前中断时仍可能重复,因此动作序列既不是数据库事务,也不承诺恰好一次。
条件
条件字段使用点分路径,并从事件快照读取,例如 player.name、player.uuid、player.dimension、chat.message、command.name、command.raw、args.<参数名>、item.id、entity.type、block.id、world.dimension、server.online、server.maxPlayers、event.type 与 event.time。UI 提供常用字段建议,但 custom.* 可以使用发布者定义的其他字段。
一个触发器可选择全部条件成立(all)或任意条件成立(any):
| 类别 | 运算符 |
|---|---|
| 相等 | eq, neq |
| 文本 | contains, not_contains, starts_with, not_starts_with, ends_with, not_ends_with |
| 安全正则 | matches, not_matches |
| 数值 | gt, gte, lt, lte, between, not_between |
| 集合 | in, not_in(逗号分隔) |
| 空值 | empty, not_empty |
| 布尔 | true, false |
| 存在性 | exists, not_exists |
between 和 not_between 使用 最小值,最大值,边界包含在区间内。empty 和 not_empty
可判断空字符串、空集合、空映射与空数组;true 和 false 判断布尔值。empty、not_empty、
true、false、exists 和 not_exists 不需要填写比较值。matches 与 not_matches 使用保存时校验的保守正则
子集;回溯组、反向引用、环视和过量可变重复会被拒绝,以免玩家输入造成正则拒绝服务。
顶层条件和 V2 表达式都计入统一节点预算;每个程序最多 4,096 个节点、32 层嵌套。
操作
执行区域既可添加普通操作,也可添加条件容器;条件通过后才会按从上到下的顺序执行其子节点。 条件容器可以继续包含条件或操作。每个触发器最多 4,096 个节点、32 层嵌套,且每个条件容器至少包含一个可执行后代。V2 另外限制函数调用 16 层、单循环 10,000 次、单实例 100,000 条 VM 指令;每 tick 最多派发 64 个副作用,最多保留 256 个等待实例:
| 类别 | 操作 ID |
|---|---|
| 消息 | send_player, broadcast, title, actionbar, sound, log |
| 命令 | server_command, player_command(owner-only;showFeedback=false 默认静默) |
| 玩家管理 | kick, teleport, give_item, clear_inventory, set_gamemode |
| 玩家状态 | add_effect, remove_effects, heal, feed |
| 世界 | set_time, set_weather |
| 身份列表 | whitelist_add, whitelist_remove, ban, pardon |
命令反馈
server_command 和 player_command 的 showFeedback 默认为 false,触发器执行指令时不会把
[Server: ...] 成功反馈广播给 OP;确实需要查看原版反馈时可在该操作中选择“显示”。发放物品、
添加效果、传送等由触发器内部调用的辅助指令始终静默,但实际操作结果不受影响。
旧版本正在运行的服务器可用 /gamerule sendCommandFeedback false 立即关闭这类管理员广播;
commandBlockOutput 只控制命令方块,并不控制服务器命令源。若只想关闭控制台向 OP 的广播,
也可在 server.properties 设置 broadcast-console-to-ops=false 后重启服务器。
消息参数使用一个所见即所得富文本输入框;框选任意文字即可设置颜色、粗体、斜体、下划线、
删除线、随机字符、点击动作、悬停说明和 Shift+点击插入内容。点击“插入变量”会打开可搜索的
变量目录;目录由服务端 /triggers/catalog 下发,包含 100 项以上实际可解析的变量,并显示
中英文名称、说明、数据类型、适用事件、插入示例和时间格式提示。目录按当前事件过滤条件字段;
玩家、物品、方块、伤害、指令参数等上下文不存在时,相应模板会保留原占位符,而不会被悄悄
替换为空字符串。旧消息仍兼容 {player}、{online}、{maxPlayers} 和 {date}。
常用变量包括 {player.name}、{server.online}、{server.maxPlayers}、
{server.defaultMessageSender}、{server.tickRate}(目标游戏速度)、{server.tps}(实际 TPS)、
{server.mspt.average}、{world.gameTime}(世界总刻)、{world.timeOfDay}(当日 0..23999 刻)、
{world.day}(游戏内天数)以及命令触发器动态生成的 {args.<参数名>}。完整清单以运行中服务端
返回的目录为准;出于安全和性能边界,目录不提供玩家 IP、世界种子、服务端文件路径、认证信息、
完整 NBT/Data Components,也不展开无限长度的玩家、实体或区块集合。
服务器时间可直接使用 {server.time},也可在冒号后使用受限的 Java DateTimeFormatter 模式:
{server.time:uuuu-MM-dd HH:mm:ss}
{server.time:uuuu年MM月dd日 HH:mm:ss}
{event.time:HH:mm:ss}
推荐使用 uuuu 表示公历年、MM 表示月、dd 表示月内日期、HH 表示 24 小时、mm 表示分、
ss 表示秒。yyyy 也可用于常见公元年份;不要使用 YYYY(周历年)或 DD(年内日序),
它们在跨年附近或表达月内日期时容易得到意外结果。模式最多 64 个字符,只允许日期时间格式字段
与普通分隔文字;非法模式会在校验/保存时拒绝。event.time 的原值是事件捕获时刻的 UTC
ISO-8601 文本,格式化后按服务器时区显示;server.timezone 可查看所用 IANA 时区。
模板在富文本 JSON 解析后逐字段替换,玩家输入不能注入颜色、点击事件或新的富文本文档;
粘贴也只接受纯文本。富文本最多 256 个样式段、
每段 2,048 字符且可见文本合计不超过 8,192 字符;URL、点击命令和各辅助字段在保存时校验。
定时、服务器、世界和区块生命周期事件没有玩家上下文,不能搭配玩家专用操作;
player.leave 发生后玩家已离线,只允许广播、日志或基于身份列表的操作。上述检查会递归覆盖
条件容器内的所有操作;保存与脚本校验阶段会直接拒绝不兼容组合、未知参数、非法资源 ID、
越界数值和多行命令。
XFE Script
# 每天上海时间 08:30 广播一次
on schedule.daily
set time="08:30"
set timezone="Asia/Shanghai"
match all
when server.online gt 0
do broadcast message="早上好,当前在线 {server.online} 人"
do log message="daily greeting sent" level=info
嵌套执行条件使用 if / end:
on player.command_trigger
set command="reward"
set arguments="target amount"
match all
if args.amount between "1,64"
do server_command command="give {args.target} minecraft:diamond {args.amount}"
end
语句包括:
on <event-id>:唯一的事件声明;set <name>=<value>:定时事件配置;match all|any:条件组合方式;when <field> <operator> [value]:条件;if <field> <operator> [value]/end:包围一组仅在条件成立时执行的子条件或操作;do <action> key="value" ...:有序操作。
脚本最多 65,536 个字符。可视化模式切到代码模式会生成等价脚本;代码模式切回可视化模式时,服务端先编译并校验,再更新模块,编译失败不会覆盖当前草稿。
Trigger Program V2
服务端会把现有可视化文档和 XFE Script v1 确定性编译为 schemaVersion: 2 的内存文档;同一旧版
修订会得到相同的节点 UUID。V2 文档包含 events、declarations、functions 和 statements,
表达式支持常量、事件/变量引用、算术与逻辑、比较、索引、空值合并、显式转换和确定性随机。
语句支持分支、switch、定次/条件/集合循环、break、continue、函数调用、返回和错误分支;
函数参数支持 IN、OUT、INOUT。保存旧格式触发器时,SQLite 同时保留当前和上一份 V2 修订。
POST /triggers/simulate 只运行只读 V2 虚拟机,不调用 Forge 副作用适配器;响应逐节点给出表达式结果、
变量写入、预算和计划动作,并写入可查询的 /trigger-executions 历史。相同事件快照、变量、时间和
随机种子会得到相同业务结果。在线执行也使用同一 V2 VM,每个副作用带
executionId:nodeId:invocation 幂等键,并在等待、每 tick 64 个副作用切片和完成点持久化动作游标、
循环、变量及调用栈。重启只恢复已经提交为 WAITING 的实例;停机前仍为 RUNNING/QUEUED 的
不确定非幂等实例转入 NEEDS_REVIEW,不会自动重放。运行实例固定创建时的触发器修订,更新和停用
只影响新实例;删除会取消尚未完成的实例。
变量声明支持 session、ttl 和 persistent 生命周期,以及 server、player、dimension、
trigger、execution、chunk、entity 作用域。持久值通过 GET/PATCH /triggers/{id}/state
读取和写入;写入必须提供 expectedRevision,冲突返回 HTTP 409。TTL 范围为 1 秒至 365 天,
过期值按不存在处理。高频运行时写入有界批量刷盘,正常停服会执行最终刷新。
V2 的硬限制为 4,096 个节点、32 层嵌套、16 层函数调用、单循环 10,000 次和单次模拟
100,000 条 VM 指令。目录通过六类描述符(事件、事件响应、值函数、条件函数、动作、类型)下发,
每项包含参数模式、返回类型、纯度、线程归属、风险等级和适用 Forge 版本;扩展只能提高内置风险,
不能降低它。TriggerExtension 可为命名空间内的值/条件函数绑定后台快照处理器,也可为动作绑定
服务器线程处理器;处理器只接收可传输快照,动作调用同时收到 executionId、nodeId 和
idempotencyKey。描述符与处理器不匹配、返回宿主对象或扩展抛出异常时会隔离该扩展/执行实例。
Web 触发器页使用全宽布局,不受普通页面的 1500px 上限限制。触发器组与组内触发器合并到一个可滚动导航栏,
组设置默认折叠。V2 预设库固定为 260px,其余宽度留给主工作台;主编辑文字为 14px,面板高度随窗口增长。
“专注编辑”可收起应用导航、触发器导航和预设库,宽屏下程序树与节点属性按约 3:1 分配整屏工作区;
退出专注后保留当前草稿和选中节点。窄窗口按编辑器实际可用宽度自动堆叠面板。
事件、响应、函数、动作、参数、类型、语句、生命周期和存储域均显示本地化名称、
用途说明与稳定技术 ID;每日/间隔计划、防护、自定义命令和区域事件提供专用表单,JSON 仅作为高级入口。节点 ID 在保存后保持稳定;
支持拖放动作、撤销/重做、节点复制、高级 JSON、变量引用安全重命名、引用计数和函数/子触发器依赖图。
语句树直接显示具体条件、比较值和动作参数(包括旧 legacy_condition 条件、嵌套表达式和富文本正文);
变量/事件引用保留名称,预览不会执行表达式。长内容自动换行。条件成立、否则、循环体、各个 switch 匹配值及错误分支
分别显示标签、子语句数量和缩进连接线,控制流节点可折叠/展开。
选中事件、变量、函数或语句后,可按 Ctrl/Cmd+C 复制、Ctrl/Cmd+V 粘贴,也可使用工具栏按钮。
复制包含整棵子树;粘贴时重新生成结构节点 ID,不改写常量记录中的 nodeId 数据字段。
选中语句时在其同级下一条插入,选中分支标签时插入该分支末尾,选中函数时将语句插入函数体末尾;
其他情况下,语句加入主程序末尾。事件、变量和函数进入各自分类;同名变量/函数自动添加副本后缀。
支持跨触发器粘贴和撤销/重做,不接管输入框、富文本编辑器中的文字复制粘贴。
粘贴前检查剪贴板结构、节点/嵌套预算及 Owner 动作权限;只读页面不会通过快捷键修改程序。
系统剪贴板不可用时,仍可使用同一页面的复制/粘贴按钮。
XFE Script v1 继续用于兼容入口;切换包含 V2 高级结构的文档时,界面会明确提示其不能无损表示函数、
循环和多事件。
区域、模板与对象预设
region.* 支持 cuboid、sphere 和 cylinder。配置包含可移植 regionId、维度、形状坐标和
frequencyTicks;服务器线程每 10 tick 更新玩家成员关系,region.stay 再按区域频率限流。
持久对象引用只存 UUID、资源 ID 或“维度 + 坐标”,使用时重新解析和验证,不持久化 Minecraft/Java 对象。
目录覆盖玩家权限、位置/旋转、生命/饥饿/经验/效果、装备/背包/末影箱、模式/重生点、计分板、队伍、 进度;实体类型/标签/属性/生命/装备/主人/目标/乘客;方块状态/标签/容器、群系、光照、红石;世界时间、 天气、难度、规则和边界。内置动作包含相应的玩家状态、实体生成/伤害/移动/移除/属性、方块/批量方块、 爆炸、雷击、难度、游戏规则与边界操作。官方模板包括欢迎消息、区域任务、击杀计数、定时 Boss、经济奖励、 菜单交互和性能告警。
执行历史、事件采集与库
/trigger-executions保留 7 天或 50,000 条摘要;手动模拟及在线动作/失败详细步骤保留 24 小时或 5,000 条,并在 Web“运行与扩展中心”展示逐节点结果。- 事件采集默认关闭。管理员可显式创建最长 1,800 秒、最多 500 条的采集会话;上下文会递归脱敏, 重启后仍在有效期内的会话会恢复。样本回放固定走只读模拟器,不调用真实副作用。
.xfelib是最大 2 MiB 的纯声明 JSON 包,包含清单、依赖版本、变量、函数、触发器和模板,不能携带 可执行字节码或脚本入口。导入、升级和回滚都会验证引擎范围、全局依赖 DAG、反向版本约束、类型与风险权限; 同一命名空间只有一个活动版本。
OpenAPI 3.1 是 HTTP 契约事实源;执行 web-ui 的 npm run generate:api 会使用隔离的 TypeScript 5
生成 src/generated/openapi.ts。这避免当前 Web 编译器 TypeScript 7 与生成器编译器 API 的版本耦合。
执行边界
事件总线先检查不可变订阅索引;没有订阅者时不会排入后台队列。Minecraft 状态在服务器线程复制为有界不可变快照,脚本条件与调度数据库随后在独立的有界触发器执行器工作;玩家、世界与 Brigadier 操作只在服务器线程执行。一次事件最多匹配 128 个已启用触发器,每 tick 最多执行 64 个动作。普通高频事件在队列饱和时会被丢弃并限速告警,不会占满 HTTP/策略控制执行器;定时任务的结算使用独立有界通道并保留待重试状态。
旧首次入服/每日消息的投递状态会在加入事件进入内存执行队列前提交;若进程恰好在两者之间崩溃,该次消息可能被标记为已消费而未实际发送。彻底消除此窗口需要后续引入持久化的 join claim → acknowledgement 流程;当前实现不宣称入服消息恰好一次投递。