XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8

触发器与自动化

触发器是消息、定时任务和服务器事件自动化的统一入口。默认使用模块化 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_commandplayer_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.dailytime=HH:mm,并提供 IANA timezone,例如 Asia/Shanghai
  • schedule.interval:总间隔为 1 秒到 31,536,000 秒。模块化 UI 提供小时、分钟、秒输入, 保存时规范化为总 seconds;API/XFE Script 既可继续使用旧格式 seconds=<总秒数>,也可同时 使用 hoursminutesseconds 分量(分和秒为 0..59)。执行点按服务器本地墙钟边界对齐; 例如服务器本地时间为 12:58、间隔为 15 分钟时,下一次执行是 13:00,之后依次为 13:15、13:30、13:45,而不是从编辑或启动时刻顺延。

玩家自定义指令

player.command_trigger 会把一个启用的触发器实时注册成游戏指令。command 是不带 / 的 小写指令根;arguments 是按空格分隔、从左到右依次声明的参数名。例如 command=rewardarguments=target amount 会注册 /reward <target> <amount>。指令根和参数名 必须匹配 [a-z][a-z0-9_-]{0,31},参数不能重名且最多 16 个;每个参数读取一个不含空格的 Brigadier word。只有玩家可以执行这种触发指令。

命令保存、启停、删除或所属组启停后,服务端会立即重建自己拥有的指令节点,并向在线玩家 重新发送命令树;数据包重载时也会恢复注册。指令根不能覆盖 Minecraft、其他模组或另一个 已启用触发器的指令。执行上下文提供 command.namecommand.raw 和每个 args.<参数名>; 例如条件可读取 args.amount,消息或其他操作可插入 {args.target}。一次输入只会定向触发 注册该指令的那一个触发器,不会广播给所有 player.command_trigger 订阅者。

调度使用带 token 的短租约。过期租约会先重放它原有的 occurrence,再计算当天或下一间隔,因此跨日重启不会覆盖尚未结算的任务。若在第一个动作前失败,本次任务会延迟重试;一旦至少一个动作已经成功,本 occurrence 会在其余动作失败时记为“部分成功”并完成,以免重复先前副作用。进程恰好在动作生效后、完成状态落库前中断时仍可能重复,因此动作序列既不是数据库事务,也不承诺恰好一次。

条件

条件字段使用点分路径,并从事件快照读取,例如 player.nameplayer.uuidplayer.dimensionchat.messagecommand.namecommand.rawargs.<参数名>item.identity.typeblock.idworld.dimensionserver.onlineserver.maxPlayersevent.typeevent.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

betweennot_between 使用 最小值,最大值,边界包含在区间内。emptynot_empty 可判断空字符串、空集合、空映射与空数组;truefalse 判断布尔值。emptynot_emptytruefalseexistsnot_exists 不需要填写比较值。matchesnot_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_commandplayer_commandshowFeedback 默认为 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 流程;当前实现不宣称入服消息恰好一次投递。