Skip to content

JS Scripting ​

Since 1.1.1, EmakiCodex supports registering codex/advancement triggers from JavaScript. Drop .js files into plugins/EmakiCodex/scripts/; a trigger registered via triggers.register({...}) is equivalent to a Java-registered one. Dispatch semantics: whenever a codex/advancement trigger event is dispatched, advancements(ctx) is called synchronously, and the codex/advancement ids it returns are merged with the ids matched by in-config trigger rules and granted; duplicate ids collapse automatically

The host mechanism, directory layout, timeout handling, and the fully open sandbox semantics match CoreLib; see the safety notice and thread semantics sections of CoreLib JS Scripting

triggers Binding API ​

Payload fields ​

FieldTypeRequiredDefaultDescription
idstringYes—Non-blank string, unique within EmakiCodex; blank or missing ids fail registration with an error
priorityintegerNo100Lower values are evaluated first; must be an integer (number or integer string), otherwise registration fails with an error
advancementsfunctionYes—advancements(ctx): called synchronously on every trigger dispatch; missing or non-callable values fail registration with an error

advancements (ctx) ​

ctx propertyDescription
ctx.playerTriggering player (an org.bukkit.entity.Player host object; call its methods directly)
ctx.triggerIdNormalized id of the gameplay event being dispatched (string)
ctx.variablesVariables carried by the event (plain object, keys and values as passed by the event)

Return value:

  • A single codex/advancement id (string), an id array, or any iterable all work; null / undefined is treated as an empty result
  • Non-string elements inside the returned collection are skipped, with a warning containing the id and the skipped count
  • Returned ids should reference entries already defined in Advancements or a codex/ category; unknown ids have no effect

Registration & runtime semantics ​

  • Load-time validation: the payload must be an object; id must be non-blank; advancements must be an executable function; priority must be an integer. Violations emit the matching warning (see the troubleshooting table) and skip that registration
  • Threading: callbacks run synchronously on the player's owner thread (the existing trigger contract), with JS calls serialized through the host lock
  • Exception isolation: if advancements throws or times out (scripts.timeout_ms), that dispatch is treated as an empty result and a warning containing the id is logged; other triggers and later evaluations of the same script are unaffected

Configuration & Reload ​

The scripts section in config.yml:

yaml
scripts:
  # 是否启用 JS 脚本扩展; 关闭后不扫描不加载 scripts/ 目录下的脚本
  enabled: true
  # 单次脚本求值或回调的超时毫秒数; 非正数回退为默认 5000
  timeout_ms: 5000
  # 调试模式; 开启后输出更详细的脚本加载信息
FieldTypeRequiredDefaultAllowed valuesDescription
scripts.enabledbooleanNotruetrue / falseWhen disabled, no scripts are scanned or loaded and script triggers registered so far are unregistered
scripts.timeout_msintegerNo5000Positive integerTimeout in milliseconds for a single evaluation or callback; non-positive values fall back to 5000

Reload semantics: scripts reload with EmakiCodex's existing reload flow — first all trigger handles from this host are closed, then the context is rebuilt and all files are re-evaluated and re-registered in file order. Modified triggers take effect immediately; triggers from deleted files are gone. When the GraalJS runtime is missing, a single warning is logged and script loading is skipped; the rest of the plugin is unaffected

Troubleshooting ​

Console outputCauseFix
console.script_engine_unavailable: "GraalJS runtime not detected; the codex script extension is unavailable"GraalJS runtime missingVerify your distribution ships the GraalJS runtime
console.script_disabled: "The codex script extension is disabled in config.yml (scripts.enabled=false)"scripts.enabled: falseEnable it if desired, then reload
console.script_loaded: "Codex scripts loaded: %loaded% file (s) succeeded, %skipped% failed"Summary of every load/reloadCheck the following file_error entries when counts look wrong
console.script_file_error: "Script file %file% failed to load: %reason%"Syntax error, evaluation timeout, or runtime exception in one fileFix that file per %reason%; other files are unaffected
console.script_release_failed: "Failed to release the example trigger script: %reason%"Example script release failedCheck data folder permissions and retry
console.script_register_invalid_payload: "payload must be an object"triggers.register(...) was not passed an objectPass an object literal
console.script_register_id_blank: "id must be a non-blank string"id missing or blankAdd id
console.script_register_advancements_missing: "%id% registration failed: advancements must be an executable function"advancements missing or not callableProvide a function
console.script_register_priority_invalid: "%id% registration failed: priority must be an integer"priority is not an integerUse a number or an integer string
console.script_register_failed: "Script trigger %id% registration failed: %reason%"Registration rejected or threw during registrationInspect %reason%
console.script_callback_failed: "Script trigger %id% execution failed: %reason%"advancements threw or timed outFix per %reason%; that dispatch is treated as an empty result
console.script_callback_invalid_element: "Script trigger %id% returned %count% non-string element (s); skipped"Returned collection contains non-string elementsReturn string ids only

Safety Notice ​

The script environment is a fully open sandbox (ctx.player is a complete Bukkit Player host object whose methods can all be called) with the same privileges as the server process — only load scripts you have reviewed yourself; reload rebuilds the context and clears top-level script state. See CoreLib JS Scripting for the full statement

Example Script ​

The example below ships with the plugin into scripts/triggers/example_trigger.js on first release; modify or delete it freely

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"];
  }
});
  • Triggers: YAML-side trigger rules (triggers.entries / unlock.triggers) and available event variables
  • Advancements: entry ids the return value should point to
  • CoreLib JS Scripting: host mechanism, directory layout, thread semantics, and the safety notice