# 触发器与自动化 触发器是消息、定时任务和服务器事件自动化的统一入口。旧可视化文档和 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 的内置集合。第三方模组或版本特有事件可从服务端代码发布为命名空间事件: ```java ForgeManagementRuntime.fireCustomTrigger( "custom.example.boss_defeated", Map.of("boss.id", "example:ancient_guardian", "party.size", 4)); ``` `custom` 只是 UI 中的选择占位符,真正保存的 ID 必须匹配 `custom..`。这使任何模组事件都能接入,同时避免把没有实际 Forge 回调的事件伪装成内置支持。 `protection.*` 事件由[崩服防护](crash-protection.md)的有界扫描器和即时熔断器产生。事件阈值采用严格“超过”语义,并支持冷却;其上下文包含数量、阈值、范围、维度/区块、模组命名空间和实际采取的动作。 ### 定时配置 - `schedule.daily`:`time=HH:mm`,并提供 IANA `timezone`,例如 `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 `。指令根和参数名 必须匹配 `[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 模式: ```text {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 ```text # 每天上海时间 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`: ```text 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 `:唯一的事件声明; - `set =`:定时事件配置; - `match all|any`:条件组合方式; - `when [value]`:条件; - `if [value]` / `end`:包围一组仅在条件成立时执行的子条件或操作; - `do 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 流程;当前实现不宣称入服消息恰好一次投递。