[MD]
### 动作写法整体换新
- **所有 Emaki 插件的动作写法都换了, 但你不需要自己动手改。** CoreLib 启用到最后会自动把 `plugins/` 目录下所有以 `Emaki` 开头的插件文件夹里的 `.yml` 配置改写成新写法, 只做一次。做完会在 `plugins/EmakiCoreLib/` 里留一个隐藏的 `.action-v2-migrated` 标记文件, 之后启动不再重复转换。每个被改过的文件旁边都会留一份 `.legacy-backup` 后缀的原文件备份, 这份备份只写一次, 之后不会被覆盖。
- **改写之前每一行都会先试着编译一遍, 不通过的行不会装进你的配置。** 通过的行照常改写, 没通过的行原样保留, 控制台会逐条列出是哪个文件的哪一行、为什么没过。如果整个文件一行都没通过, 原文件完全不动, 转换结果单独写到一个 `.v2-failed` 文件里给你自己看。只要还有没过的行或读写失败的文件, 标记就不写, 下次更新后会再试一遍。
- 控制台会打印一份转换报告: 改了多少个文件、多少行, 以及逐条的跳过与失败原因。全部已经是新写法的服务器不会输出这份摘要。
- 转换只碰 Emaki 自己的插件文件夹, 不会去改其他插件的配置。语言文件 (`lang/` 目录) 不参与转换。
- 新旧写法的完整对照如下, 以 CoreLib 自己 `config.yml` 里的 `action.templates` 示例为例:
```yaml
# 旧写法
action:
templates:
reward_success_common:
- 'sendmessage text="<green>成功获得 %result_item_name%</green>"'
- '@delay=10t playsound sound=minecraft:entity.experience_orb.pickup volume=0.8 pitch=1.2'
# 新写法
action:
templates:
reward_success_common:
- 'self | send_message text="<green>成功获得 %var.result_item_name%</green>"'
- 'self | after 10t | play_sound sound=minecraft:entity.experience_orb.pickup volume=0.8 pitch=1.2'
```
- 逐项说明:
- 动作名从连写改成下划线分词, 例 `sendmessage` → `send_message`, `playsound` → `play_sound`, `spawnparticle` → `spawn_particle`, `givepotioneffect` → `give_potion_effect`, `runcommandasconsole` → `run_command_as_console`。
- 每一行现在由「对谁生效」的段开头, 常用的有 `self` (自己)、`inherited` (沿用上一段选中的目标)、`looking_at` (视线指向的目标)、`nearby` (附近的实体)、`trigger` (触发者)。不写就默认按 `self` 算。一行只能有一个这样的段, 而且必须写在最前面。
- 段与段之间用 `|` 隔开, 从左到右依次执行。
- 想把这一行选中的目标交给后面的阶段用, 加一个 `keep` 段, 后面的阶段用 `inherited` 读回来。
- 行首的旧标记换成了段: `@delay=10t` → `after 10t |`, `@chance=0.3` → `chance 0.3 |`, `@if='条件'` → `where 条件 |`。`@ignore_failure` 不再需要, 某一段失败后继续往下走本来就是默认行为。
- 想写带分支的判断, 用 `if <条件> [ ... ]` 块写法, 方括号里放这一支要执行的段, 还可以接 `else [ ... ]`。自动转换不会生成方括号, 它只会把 `@if` 转成 `where`, 因为旧写法本来就没有 else 这一支。
- 时间量都带单位后缀, 例 `ticks=200` → `duration=200t`。可用的单位是 `t` (刻)、`s` (秒)、`ms` (毫秒), 不写单位按刻算。
- 内置占位符换了名字: `%player%` 与 `%player_name%` → `%caster.name%`, `%player_uuid%` → `%caster.uuid%`, `%player_world%` → `%caster.world%`, `%player_x%` `%player_y%` `%player_z%` → `%caster.x%` `%caster.y%` `%caster.z%`, `%target_name%` → `%target.name%`, `%target_uuid%` → `%target.uuid%`, `%has_target%` → `%target.present%`。
- 你自己定义的变量统一写成 `%var.名称%`, 裸写 `%名称%` 会在加载时被拒绝并提示应该写成什么。用 `set 名称=值` 段可以在一行动作执行到中途时写变量。
- 变量定义里的 `expression` 字段改叫 `value`。
- 引用命名序列从 `@template=名称` 改成 `run 名称 参数=值`。
- **有四个旧动作自动转换不了, 需要你手工改。** 转换器认得它们, 但会跳过并在控制台说明原因:
- `loopsync` 与 `loopasync` → 改用 `start_task`
- `cancelloop` → 改用 `stop_task`
- `usetemplate` → 改用 `run <序列名>`
- 循环相关的参数名也跟着变了: `mode=replace` → `on_conflict=replace`, `stop_if_offline` → `stop_when_offline`。`start_task` 还支持 `stop_when_dead`、`stop_when`、`stop_on_failure`、`initial_delay`、`key`。
- 原先的 `action.loop.templates` 现在是「命名序列」, 用 `run 名称` 调用并可以带 `参数=值`。序列里某一行编译不过时, 控制台会点出是哪个序列的哪一行, 该序列整体不可用, 其余序列照常。
- 新增配置块 `action.pipeline`, 三个上限都是在加载配置时就检查, 超了直接拒绝这条配置并保留上一份能用的, 不会悄悄改小:
- `action.pipeline.max_repeat_times` (默认 `100`) —— `every <间隔> times <次数>` 的次数上限, 也是 `start_task` 的 `times` 上限。想让循环跑更久就把这个值调大。
- `action.pipeline.max_sequence_depth` (默认 `8`) —— `run` 互相调用的最大层数。
- `action.pipeline.max_branch_depth` (默认 `16`) —— `if ... [ ... ]` 的最大嵌套层数。
- 配置写错时的提示全部改成了看得懂的中文, 会指出是第几行第几列、少了什么、可以填哪些值, 同一行有多处问题时会告诉你还剩几处。以前那种直接甩一串英文键名的情况没有了。
- `/corelib action run` 的用法跟着变了: 以前是 `run <动作id> key=value ...`, 现在直接写一整行动作, 例 `/corelib action run self | send_message text="hi"`。`/corelib action list` 列出的是现在可用的段, 带类别与提供它的插件。
- `/corelib debug loops` 保留, 现在看的是 `start_task` 建立的长期任务, 多了一条「没有匹配该 key 的任务」的提示。
### JavaScript 脚本功能取消
- **JS 脚本子系统整体取消了, 你自己写的 `.js` 脚本会直接失效, 而且没有自动转换的办法**, 必须改用配置文件里的动作重做同样的逻辑。
- `config.yml` 里整个 `script:` 段删除, 包含 `script.enabled`、`script.engine`(`type: graaljs`、超时与各项 `allow_*` 开关)、`script.paths`(`root: scripts` 与自动创建的子目录)、`script.action`(`runjs` 动作 id)、`script.context`、`script.security`(含 `denied_actions_from_script`)、`script.server_api`、`script.debug`(`log_script_load`、`log_script_execute`)。你的配置里还留着这一段也不会报错, 直接删掉即可。
- `runjs` 动作不再存在。
- 随包的示例脚本 `scripts/examples/hello.js`、`js_broadcast_action.js`、`js_event_examples.js`、`js_placeholders.js` 一并删除。
- `/corelib script [list|inspect|reload]` 子命令移除。
- 语言文件里所有 `script_*` 与 `js_*` 开头的键移除, 自定义语言文件如仍保留这些条目, 它们不会再被用到。
- `release_default_data` 这个开关保留下来兼容既有配置文件, 但随着脚本示例一起删除, 目前已经没有受它控制的示例资源了。
### 界面与展示
- 新增菜单点击最小间隔 `gui.click_interval_ms` (默认 `100` 毫秒), 用来挡住连点与宏点击造成的重复结算。正常手动点击不会触发, 被挡下的点击会被安全丢掉, 菜单不会错乱, 玩家也不会收到提示。填 `0` 表示不限制, 想更严格可以上调, 建议不超过 `500`。两种菜单后端都生效。
- 新增 `display` 配置块, 是悬浮文字与展示物品的公共底层, 伤害飘字、烹饪工位文字与物品展示都走它:
- `display.backend` (默认 `auto`) —— `bukkit` 用真实体, 对附近所有玩家可见, 不需要前置插件; `packet` 用发包虚拟实体, 不进存档、不占服务端实体, 支持只给指定玩家看, 需要装 PacketEvents 且服务端不低于 1.19.4; `auto` 是装了 PacketEvents 就用发包, 否则用真实体; `inherit` 跟随 `gui.backend`。发包后端不可用时一律退回 `bukkit`, 功能不会整体失效。
- `display.view_distance_blocks` (默认 `48`) 与 `display.refresh_interval_ticks` (默认 `20`) —— 只对发包后端有效, 分别是虚拟实体的可见距离和可见性重算间隔。
- 新增 `minimessage.default_no_italic` (默认 `true`)。原版对物品名称与 Lore 默认套斜体, 开启后 Emaki 系列所有文本 (菜单物品名与 Lore、菜单标题、聊天消息、对话框) 在没有显式写斜体时一律不倾斜。这是默认值不是强制值, 文本里自己写 `<i>` 或 `<italic>` 仍然生效。
- 物品 Lore 支持按数据量展开成多行: 一行里只写一个占位符、而这个占位符的值是一组数据时, 会展开成多行而不是挤在一行。只对 `minecraft:lore` 生效, 其他组件保持一对一。
### 原版对话框
- 新增原版对话框支持, 基于服务端自带的对话框能力, 可以给玩家弹出带说明文字、输入框与按钮的界面。需要客户端 1.21.6 及以上, 更低版本客户端的表现没有验证过。
- 新增配置块 `dialog`: `dialog.enabled` (默认 `true`) 是总开关, 关掉后不加载对话框定义; `dialog.directory` (默认 `dialogs`) 是定义文件目录, 相对 `plugins/EmakiCoreLib/`, 目录里所有 `.yml` 都会被加载。
- 插件 jar 里带了一份写满注释的示例定义 `dialogs/example_notice.yml`, 讲了三种对话框类型 (`notice` 单按钮、`confirmation` 是/否两按钮、`multi_action` 多按钮)、正文与输入框写法、四种按钮动作 (`none`、`command_template`、`run_command`、`open_url`), 以及 `can_close_with_escape`、`pause`、`after_action` 这些开关。**这份示例不会自动生成到数据目录**, 想用对话框需要你自己在 `dialog.directory` 指定的目录 (默认 `plugins/EmakiCoreLib/dialogs/`) 下新建 `.yml` 文件, 可以照着 jar 里那份示例写。
- 对话框定义少了必需的输入项 `key` 或按钮时, 加载时就会警告并跳过这一份, 不会等到玩家点开才出问题。
- 业务插件也可以把对话框直接写在自己的配置文件里, 由 CoreLib 统一解析, 写法与独立文件一致。
### 原版物品名称查询
- 新增 `vanilla_language` 配置块。原版物品与方块的名字是客户端翻译的, 服务端手里只有 `block.minecraft.campfire` 这样的翻译键, 所以需要在服务端拿到本地化名称的功能 (例如按中文名搜索仓库) 得自备一份语言表。
- `vanilla_language.enabled` 默认 `false`。开启后首次启用会联网从 Mojang 官方资源索引下载语言文件, 结果缓存在 `plugins/EmakiCoreLib/lang-cache/`, 之后启动不再联网。下载失败不影响开服, 只是相关功能保持不可用。
- `vanilla_language.locale` (默认 `zh_cn`) 是要取的原版语言 id, 与客户端语言文件同名, 例如 `zh_cn`、`en_us`、`zh_tw`。
### 表达式与文本配置只认一个键名
- **表达式与文本类配置以前一个字段能有好几种写法, 现在只认一个。** 以前 CoreLib 读这类配置时会依次去找一串别名, 比如 `value` 找不到就去找 `text`、`template`、`expression`、`formula`。这种多写法并存导致同一份配置换个键名也能跑, 出问题时很难判断插件到底读了哪个。现在每个位置只认下面这一个键名, 用别名写的会被当成没写:
- 取值一律写 `value` (原先还接受 `text`、`template`、`expression`、`formula`)
- 判断条件一律写 `condition` (原先还接受 `when`、`if`、`expression`、`formula`)
- 分支结果写 `true_value` 与 `false_value` (原先还接受 `true`/`then`/`value`/`char` 与 `false`/`else`/`fallback`/`default`)
- 兜底值一律写 `fallback` (原先还接受 `default`、`else`、`false_value`)
- 多分支列表一律写 `cases` (原先还接受 `conditions`)
- 连接符一律写 `separator` (原先还接受 `joiner`)
- 抽取次数一律写 `count` (原先还接受 `rolls`、`times`、`random_times`、`amount`)
- 是否允许重复一律写 `allow_duplicates` (原先还接受 `allow_duplicate`、`allow_repeat`、`allow_repeats`、`repeat`、`repeatable`、`with_replacement`)
- 候选文本一律写 `lines` (原先还接受 `values`、`options`、`texts`、`value`)
- 候选字符一律写 `chars` (原先还接受 `characters`、`alphabet`、`values`)
- 权重一律写 `weights` (原先还接受 `weight`)
- 随包的示例配置都已经是新键名。如果你自己写过这类配置并且用的是别名, 升级后要改成上面的写法。
### 控制台与配置检查
- 配置预检的问题行现在按严重程度整行着色: 错误级整行红色、警告级整行黄色、提示级保持原来的灰色。失败计数只统计真正拦住加载的问题, 不再把提示也算进去。
- 各模块重载配置时改成「先试装再启用」: 新配置要先通过预检才会生效, 没通过就保留上一份仍在用的配置。以前的做法是先换上再报错, 一个拼写错误可能把整个模块的设置退回默认值。
- 补齐了部分模块缺失的预检提示键, 统一了若干条控制台日志的配色, 不会再出现直接打印键名的情况。
- 接入外部物品插件时的日志说得更清楚了: 插件装了但物品还没注册完时, 会分别说明 ItemsAdder、CraftEngine、NeigeItems、Nexo、Oraxen、EcoItems 各自在等什么; 检测到插件但接口对不上时也会单独提示, 不再只是静默不接。
### 修复
- **修复部分服务器随机报 `NoSuchMethodError` 或文本渲染异常。** 原因是 CoreLib 自动下载的运行库版本不齐: Adventure 家族的十几个组件里有一部分固定在 `4.21.0`、另一部分在 `4.26.1`, 混着用会让某些方法调用在运行时找不到; 同时下载的 gson 是 `2.8.0`, 比 Paper 自带的还旧, 会把服务端已有的实现挤到后面。现在 Adventure 全组统一同一个版本, gson 与 Paper 声明的 `2.11.0` 对齐。
- 修复改了物品定义的 lore 之后触发更新时, 旧的 Lore 行被当成外部内容留下并追加在新行后面, 每更新一次就多积一轮历史。现在以新生成的物品为唯一基准, 失效的旧记录会被清掉。
### 开发者相关
- `EmakiCoreLibApiProvider` 已移除, 直接使用 `EmakiCoreLibApi` 的静态方法。
- `EmakiCoreLibApi.available()`、`apiVersion()`、`pluginName()`、`isReady()` 全部移除, 由 `EmakiCoreLibApi.status()` 统一替代 (返回 `ApiStatus`, 提供 `usable()`、`ready()`、版本与插件名字段)。`CompatibilityReport` 与 `compatibilityReport()` 一并移除。
- 旧动作注册面整体移除: `registerAction`、`unregisterAction`、`unregisterActions`、`unregisterActionsBySource`、`actionRegistered`、`action(String)`、`actions()`、`actionsByOwner`、`actionsBySource`, 以及 `CoreAction`、`CoreActionContext`、`CoreActionDescriptor`、`CoreActionErrorType`、`CoreActionExecutionMode`、`CoreActionParameter`、`CoreActionParameterType`、`CoreActionPlanningContext`、`CoreActionRegistration`、`CoreActionResult`。
- 新的第三方注册入口是 `registerActionStage`、`registerActionSource`、`registerActionGate` (均返回 `CoreStageRegistration`), 配合 `onStageRegistryRebuilt(Plugin, Runnable)` 在注册表重建后重新注册。对应契约类型为 `CoreActionStage`/`CoreActionSource`/`CoreActionGate`、`CoreStageContext`、`CoreStageParameter`、`CoreStageParameterType`、`CoreStageKind`、`CoreResolvedArguments`、`CoreActionSubject`、`CoreActionOutcome`、`CoreSourceResult`、`CoreGateResult`、`CoreGateThread`、`CoreTargetRequirement`、`CoreCancellationToken`、`CoreActionKey(s)`、`CoreActionFailureKind`、`PhaseContract`。
- 新增统一结果契约 `EmakiResult` 与 `FailureKind`、`Unit`; `itemDisplayName(String)` 与 `itemDisplayName(ItemStack)` 返回类型由 `String` 改为 `EmakiResult<String>`; `itemComponentCapability(String)` 由可空返回改为 `Optional<ItemComponentCapability>`。
- 新增能力注册表: `publishCapabilities(Plugin, ...)`、`revokeCapabilities(Plugin)`、`hasCapability(ApiCapability)`、`capabilities()`、`capabilitiesOf(String)`。
- 新增跨模块就绪契约: `whenReady(Plugin, ...)`、`isModuleReady(String)`、`addModuleListener(Plugin, ...)`, 类型为 `ReadinessRegistration`、`ModuleReadinessPhase`、`ModuleReadinessListener`。各模块在重载前后与关闭时发布 loading / ready / absent 三态; `status().ready()` 的判据统一改为「数据已加载」而不是「组件非空」, API 实现层在首次读到已加载数据前返回 unavailable。`whenReady` 的回调内不要再次注册, 该写法会命中已就绪分支并同步递归; 需要在目标模块每次重载时重建缓存请用 `addModuleListener`。
- 新增 `dialogs()` 返回 `CoreLibDialogs` (配合 `DialogDefinition`、公开的对话框解析入口) 与 `scheduling()` 返回 `EmakiScheduling` (配合 `TaskToken`); 插件未加载时返回空实现而不是抛异常 (`UnavailableDialogs`、`UnavailableScheduling`)。
- 物品源体系提升为公开契约: 新增 `ItemSourceProvider`、`ItemSourceRef`、`ItemSourceKind`、`ItemSourceRegistration`、`ItemSourceProbeResult`、`ItemSourceProbeState`、`LifecycleState`、`LifecycleStatus`, 第三方插件可以自行注册物品源类型。原先的 `ItemSourceType` 是 final enum, 外部无法扩展。
- 脚本 API 面整体移除: `EmakiScriptApi`、`ScriptActionApi`、`ScriptContextApi`、`ScriptItemApi`、`ScriptLoggerApi` 等不再存在。
- `EmakiAttributeBridge`、`PdcAttributeApi`、`PdcAttributePayloadSnapshot` 移除; 新增 `MythicMobBridge` 收敛 Mythic 元数据查询。
- 大批共享基础设施迁入 `corelib-api` 供各模块与第三方直接依赖, 包括 `Texts`、`MiniMessages`、`ConsoleOutputs`、`ConfigNodes`、`Numbers`、`Jsons`、`SafePaths`、`SlotParser`、`EquipmentSlotMatcher`、`ItemTextBridge`、`ConditionContext`、`CommandTabHelper`、`AsyncFailures`、`SignatureUtil`、`Anchors`、`EntityPhysicsSupport`、`ConfigPrecheckSeverity`, 以及 `YamlSection`、`YamlFiles`、`MapYamlSection`、`BoostedYamlSection`、`BoostedYamlSupport`、`VersionedYamlFile`、`YamlLoadException` 一整套 YAML 访问层。
- `FoliaSchedulerAdapter` 与 `corelib.async.TaskHandle` 标记 `@Deprecated(forRemoval = true)` 并保留过渡实现, 请改用 `EmakiScheduling`。
- `EmakiCoreLibApi.Bridge` 仍为内部实现入口, 第三方插件不要实现它。
### 升级说明
- **升级前请备份 `plugins/EmakiCoreLib/` 整个目录。** 由于本次会自动改写其他 Emaki 插件的配置, 建议把整个 `plugins/` 下的 Emaki 目录一起备份。
- 先升级 CoreLib, 再升级依赖它的其它 Emaki 插件。
- 大部分动作写法由插件自动改写, 但下面几处要你自己动手:
- `loopsync`、`loopasync`、`cancelloop`、`usetemplate` 四个旧动作改成 `start_task`、`stop_task`、`run`。
- 用 `.js` 脚本实现的逻辑改成用动作实现。
- 表达式与文本配置里用了别名键名的地方改成唯一键名。
- 配置里的 `script:` 整段可以删掉, 留着也不会报错。`release_default_data` 保留但目前没有受它控制的示例资源。
- 想开启原版物品名称查询请自己把 `vanilla_language.enabled` 改成 `true`, 它默认关闭且首次启用会联网。
- 想让悬浮文字与飘字走发包后端, 需要额外安装 PacketEvents 且服务端不低于 1.19.4, 否则会自动用真实体。
- 服务端要求没有变化, 仍是 Paper 系 1.21.8 及以上, `folia-supported: true`, Java 目标 25。命令与权限没有增删, 仍是 `emakicorelib.admin` 与 `emakicorelib.reload`。
- 升级后建议实际验证一次: 看控制台的动作转换报告有没有失败行、随手点几个菜单确认点击间隔没有影响正常操作、跑一次 `/corelib check` 看预检报告。
[/MD]