简体中文
JS 脚本扩展
EmakiCodex 自 1.1.1 起支持用 JavaScript 注册图鉴/成就触发器. 把 .js 文件放进 plugins/EmakiCodex/scripts/ 目录, 通过 triggers.register({...}) 注册的触发器与 Java 注册的触发器等价. 分发语义: 每次图鉴/成就触发事件进入分发时, advancements(ctx) 被同步调用, 其返回的图鉴/成就 id 与配置内触发规则命中的 id 合并后授予, 重复 id 自动折叠
宿主机制, 目录加载顺序, 超时与全开放沙箱规则与 CoreLib 相同; 安全声明与线程模型见 CoreLib JS 脚本扩展
triggers 绑定 API
payload 字段
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
id | string | 是 | — | 非空字符串, EmakiCodex 内唯一; 空白或缺失时注册失败并报错 |
priority | integer | 否 | 100 | 数值越小越先求值; 必须是整数 (数字或整数字符串), 否则注册失败并报错 |
advancements | function | 是 | — | advancements(ctx): 每次触发分发时同步调用; 缺失或不可调用时注册失败并报错 |
advancements (ctx)
| ctx 属性 | 说明 |
|---|---|
ctx.player | 触发玩家 (org.bukkit.entity.Player 宿主对象, 可直接调用其方法) |
ctx.triggerId | 本次游戏事件的规范化 id (字符串) |
ctx.variables | 事件携带的变量 (普通对象, 键值与事件传入一致) |
返回值:
- 单个图鉴/成就 id (字符串), id 数组或可迭代对象均可;
null/undefined按空集合处理 - 返回集合中的非字符串元素会被跳过, 并输出含 id 与跳过数量的告警
- 返回的 id 应是你在 成就页定义 或
codex/分类中已定义的条目 id; 未定义的 id 不会产生任何效果
注册与运行规则
- 注册期校验: payload 必须是对象;
id非空;advancements必须是可执行函数;priority必须是整数. 校验失败按具体原因输出告警 (见错误排查表), 该次注册跳过 - 线程: 回调在玩家 owner 线程同步调用 (既有触发器接口约定), JS 调用经宿主锁串行化
- 异常隔离:
advancements抛异常或执行超时 (scripts.timeout_ms) 时, 该次触发按空集合处理并输出含 id 的告警, 不影响其余触发器与本脚本后续求值
配置与重载
config.yml 中的 scripts 节:
yaml
scripts:
# 是否启用 JS 脚本扩展; 关闭后不扫描不加载 scripts/ 目录下的脚本
enabled: true
# 单次脚本求值或回调的超时毫秒数; 非正数回退为默认 5000
timeout_ms: 5000
# 调试模式; 开启后输出更详细的脚本加载信息| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
scripts.enabled | boolean | 否 | true | true / false | 关闭后不扫描不加载脚本, 已注册的脚本触发器随之注销 |
scripts.timeout_ms | integer | 否 | 5000 | 正整数 | 单次脚本求值或回调的超时毫秒数; 非正数回退 5000 |
重载行为: 脚本随 EmakiCodex 既有 reload 流程重载 -- 先关闭本次加载的全部触发器句柄, 再重建 Context, 按文件顺序重新求值与注册. 修改后的触发器立即生效, 已删除文件对应的触发器不复存在. GraalJS 运行库缺失时输出一条告警并跳过脚本加载, 插件其余功能不受影响
错误排查
| 控制台输出 | 原因 | 处置 |
|---|---|---|
console.script_engine_unavailable: "未检测到 GraalJS 运行库, 图鉴脚本扩展不可用" | GraalJS 运行库缺失 | 确认发行版包含 GraalJS 运行库 |
console.script_disabled: "图鉴脚本扩展已在 config.yml 中关闭 (scripts.enabled=false)" | scripts.enabled: false | 需要时开启后 reload |
console.script_loaded: "图鉴脚本加载完成: 成功 %loaded% 个文件, 失败 %skipped% 个" | 每次加载/重载的汇总 | 失败数异常时查看随后的 file_error |
console.script_file_error: "脚本文件 %file% 加载失败: %reason%" | 单文件语法错误, 求值超时或运行时异常 | 按 %reason% 修复该文件; 其余文件不受影响 |
console.script_release_failed: "释放示例触发器脚本失败: %reason%" | 示例脚本首次释放失败 | 检查数据目录权限后重试 |
console.script_register_invalid_payload: "payload 必须是对象" | triggers.register(...) 未传对象 | 传入对象字面量 |
console.script_register_id_blank: "id 必须是非空字符串" | id 缺失或空白 | 补充 id |
console.script_register_advancements_missing: "%id% 注册失败: advancements 必须是可执行函数" | advancements 缺失或不可调用 | 提供函数 |
console.script_register_priority_invalid: "%id% 注册失败: priority 必须是整数" | priority 不是整数 | 改为数字或整数字符串 |
console.script_register_failed: "脚本触发器 %id% 注册失败: %reason%" | 注册被拒绝或注册过程抛出异常 | 按 %reason% 排查 |
console.script_callback_failed: "脚本触发器 %id% 执行失败: %reason%" | advancements 抛异常或超时 | 按 %reason% 修复; 该次触发按空集合处理 |
console.script_callback_invalid_element: "脚本触发器 %id% 返回了 %count% 个非字符串元素, 已跳过" | 返回集合混入非字符串元素 | 只返回字符串 id |
安全声明
脚本环境默认全开放 (ctx.player 是完整的 Bukkit Player 宿主对象, 可调用其任意方法), 具备与服务器同级权限, 请只加载你自己审查过的脚本; 重载会重建 Context 并清空脚本顶层状态. 完整声明见 CoreLib JS 脚本扩展
示例脚本
以下示例随插件首次释放到 scripts/triggers/example_trigger.js, 可直接修改或删除
js
// EmakiCodex 图鉴触发器示例脚本 (scripts/triggers/ 仅作组织目录, 无隔离作用)
//
// 通过 triggers 绑定注册触发器: 每次图鉴/成就触发事件进入分发时,
// EmakiCodex 会同步调用这里注册的 advancements (ctx) 函数, 把返回的
// 图鉴/成就 id 与配置内触发规则命中的 id 合并后授予 (重复 id 自动折叠)
//
// payload 契约:
// id 必填, 非空字符串, EmakiCodex 内唯一, 重复注册会报错跳过
// priority 可选, 整数, 默认 100, 数值越小越先求值
// advancements 必填, 可执行函数; 参数 ctx 含:
// ctx.player 触发玩家, org.bukkit.entity.Player 宿主对象, 可直接调用其方法
// ctx.triggerId 本次游戏事件的规范化 id (字符串)
// ctx.variables 事件携带的变量 (普通对象, 键值与事件传入一致)
// 返回值: 单个图鉴/成就 id (字符串), id 数组/可迭代, 或 null/undefined (按空处理)
// 函数抛出异常或执行超时 (scripts.timeout_ms) 时按空集合处理并输出告警,
// 不影响其余触发器与本脚本后续求值
//
// 真实用法: 把返回值替换为你在 advancements/ 页面或 codex/ 分类中已定义的
// 条目 id; ctx.triggerId 会等于游戏事件分发时使用的规范化触发名
// 下面的 example_event 并非本插件内置事件, 因此本示例默认不会被任何玩法
// 事件命中, 仅作演示, 不会在运行期产生授予或告警
triggers.register({
id: "script_example_hunter",
priority: 100,
advancements: function (ctx) {
if (ctx.triggerId !== "example_event") {
return null;
}
var count = ctx.variables.count;
if (typeof count !== "number" || count < 10) {
return null;
}
return ["codex/example_entry"];
}
});相关页面
- 触发器: YAML 侧触发规则 (
triggers.entries/unlock.triggers) 与可用事件变量 - 成就页定义: 返回值应指向的条目 id
- CoreLib JS 脚本扩展: 宿主机制, 目录结构, 线程模型与安全声明