Skip to content

JS 脚本扩展 ​

EmakiCodex 自 1.1.1 起支持用 JavaScript 注册图鉴/成就触发器. 把 .js 文件放进 plugins/EmakiCodex/scripts/ 目录, 通过 triggers.register({...}) 注册的触发器与 Java 注册的触发器等价. 分发语义: 每次图鉴/成就触发事件进入分发时, advancements(ctx) 被同步调用, 其返回的图鉴/成就 id 与配置内触发规则命中的 id 合并后授予, 重复 id 自动折叠

宿主机制, 目录加载顺序, 超时与全开放沙箱规则与 CoreLib 相同; 安全声明与线程模型见 CoreLib JS 脚本扩展

triggers 绑定 API ​

payload 字段 ​

字段类型必填默认说明
idstring是—非空字符串, EmakiCodex 内唯一; 空白或缺失时注册失败并报错
priorityinteger否100数值越小越先求值; 必须是整数 (数字或整数字符串), 否则注册失败并报错
advancementsfunction是—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.enabledboolean否truetrue / false关闭后不扫描不加载脚本, 已注册的脚本触发器随之注销
scripts.timeout_msinteger否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"];
  }
});

相关页面 ​