触发器与自动化
触发器是消息、定时任务和服务器事件自动化的统一入口。默认使用模块化 Web UI;切换到 XFE Script 后,脚本仍会编译为同一份“事件 → 顶层条件 → 有序、可嵌套的条件/操作树”模型。XFE Script 是有界 DSL,不是 Java/JVM 脚本,也不能访问文件、网络或宿主类。
消息发送者名称可在 Web“设置”页全局配置,默认是 XFEServerManager,保存后立即生效。普通文本和未显式设置发送者的富文本会继承该值;富文本也可为单条消息覆盖发送者或隐藏发送者。
组、版本与迁移
- 一个触发器组可整体启停,并包含多个独立触发器。
- 单服最多 256 个触发器组、1,024 个触发器;单组最多 256 个触发器。上限在 SQLite 写事务内强制,不能通过并发创建绕过。
- 触发器和组都使用 revision compare-and-swap;旧页面保存时返回
409 Conflict,不会覆盖新修改。 - 首次升级会把每次入服欢迎、首次入服欢迎、按玩家每日公告、规则提醒和维护放行提醒,按原投递顺序迁移到“
Migrated messages / 已迁移消息”组。旧投递路径在所有命中类别合并后最多发送 32 条,因此迁移只保留每个类别原本可达的前 32 条,并在每次入服匹配后继续按原类别顺序执行全局 32 条裁剪;迁移是幂等的。 - 迁移后触发器数据库是消息的唯一事实源。旧
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 |
| 崩服防护 | 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 使用保存时校验的保守正则
子集;回溯组、反向引用、环视和过量可变重复会被拒绝,以免玩家输入造成正则拒绝服务。
每个触发器最多 32 个条件。
操作
执行区域既可添加普通操作,也可添加条件容器;条件通过后才会按从上到下的顺序执行其子节点。 条件容器可以继续包含条件或操作。每个触发器的条件容器与普通操作合计最多 32 个节点,条件 最多嵌套 8 层,且每个条件容器至少包含一个可执行后代:
| 类别 | 操作 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 个字符。可视化模式切到代码模式会生成等价脚本;代码模式切回可视化模式时,服务端先编译并校验,再更新模块,编译失败不会覆盖当前草稿。
执行边界
事件总线先检查不可变订阅索引;没有订阅者时不会排入后台队列。Minecraft 状态在服务器线程复制为有界不可变快照,脚本条件与调度数据库随后在独立的有界触发器执行器工作;玩家、世界与 Brigadier 操作只在服务器线程执行。一次事件最多匹配 128 个已启用触发器,每 tick 最多执行 64 个动作。普通高频事件在队列饱和时会被丢弃并限速告警,不会占满 HTTP/策略控制执行器;定时任务的结算使用独立有界通道并保留待重试状态。
旧首次入服/每日消息的投递状态会在加入事件进入内存执行队列前提交;若进程恰好在两者之间崩溃,该次消息可能被标记为已消费而未实际发送。彻底消除此窗口需要后续引入持久化的 join claim → acknowledgement 流程;当前实现不宣称入服消息恰好一次投递。