English
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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | Non-blank string, unique within EmakiCodex; blank or missing ids fail registration with an error |
priority | integer | No | 100 | Lower values are evaluated first; must be an integer (number or integer string), otherwise registration fails with an error |
advancements | function | Yes | — | advancements(ctx): called synchronously on every trigger dispatch; missing or non-callable values fail registration with an error |
advancements (ctx)
| ctx property | Description |
|---|---|
ctx.player | Triggering player (an org.bukkit.entity.Player host object; call its methods directly) |
ctx.triggerId | Normalized id of the gameplay event being dispatched (string) |
ctx.variables | Variables 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/undefinedis 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;
idmust be non-blank;advancementsmust be an executable function;prioritymust 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
advancementsthrows 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
# 调试模式; 开启后输出更详细的脚本加载信息| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
scripts.enabled | boolean | No | true | true / false | When disabled, no scripts are scanned or loaded and script triggers registered so far are unregistered |
scripts.timeout_ms | integer | No | 5000 | Positive integer | Timeout 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 output | Cause | Fix |
|---|---|---|
console.script_engine_unavailable: "GraalJS runtime not detected; the codex script extension is unavailable" | GraalJS runtime missing | Verify your distribution ships the GraalJS runtime |
console.script_disabled: "The codex script extension is disabled in config.yml (scripts.enabled=false)" | scripts.enabled: false | Enable it if desired, then reload |
console.script_loaded: "Codex scripts loaded: %loaded% file (s) succeeded, %skipped% failed" | Summary of every load/reload | Check 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 file | Fix that file per %reason%; other files are unaffected |
console.script_release_failed: "Failed to release the example trigger script: %reason%" | Example script release failed | Check data folder permissions and retry |
console.script_register_invalid_payload: "payload must be an object" | triggers.register(...) was not passed an object | Pass an object literal |
console.script_register_id_blank: "id must be a non-blank string" | id missing or blank | Add id |
console.script_register_advancements_missing: "%id% registration failed: advancements must be an executable function" | advancements missing or not callable | Provide a function |
console.script_register_priority_invalid: "%id% registration failed: priority must be an integer" | priority is not an integer | Use a number or an integer string |
console.script_register_failed: "Script trigger %id% registration failed: %reason%" | Registration rejected or threw during registration | Inspect %reason% |
console.script_callback_failed: "Script trigger %id% execution failed: %reason%" | advancements threw or timed out | Fix 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 elements | Return 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"];
}
});Related Pages
- 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