English
JS Scripting
Since 2.8.3, EmakiSkills supports registering skill definitions from JavaScript. Place .js files under plugins/EmakiSkills/scripts/; definitions registered via skills.register({...}) participate in parsing, slot assembly, casting, upgrade queries, and the catalog with equal standing to YAML skills from skills/*.yml, and a JS definition with the same id overrides the YAML one
The host mechanism, directory layout, timeout handling, and the fully open sandbox semantics match CoreLib; thread and safety constraints are stated in the safety notice and thread semantics sections of CoreLib JS Scripting
skills Binding API
Payload fields
Fields align with the external skill definition (ExternalSkillDefinition) and are named in camelCase:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | Skill id (ID-normalized); blank or missing ids fail registration with an error |
displayName | string | No | falls back to id | Display name, supports MiniMessage |
description | string or string array | No | [] | Skill description, shown line by line in the skill UI; a single string is treated as one line |
iconMaterial | string | No | "" | GUI icon material (lowercase material name) |
activationType | string | No | "ACTIVE" | ACTIVE / PASSIVE; only passive (case-insensitive, - treated as _) becomes PASSIVE, everything else is treated as ACTIVE |
passiveTriggers | string array | No | [] | Passive trigger id list (deduplicated, - converted to _) |
cooldownTicks | integer | No | 0 | Cooldown in ticks; negative values are clamped to 0 |
globalCooldownTicks | integer | No | 0 | Shared global cooldown in ticks applied to all skills after casting; negative values are clamped to 0 |
tags | string array | No | [] | Tags (used for filtering and equipment limits, normalized and deduplicated) |
loreAliases | string array | No | [] | Lore aliases |
pdcSkillId | string | No | "" | PDC skill id |
uiCategory | string | No | "default" | GUI category |
sortOrder | integer | No | 0 | GUI sort order, lower comes first |
showInSlots | boolean | No | true | Whether the skill participates in slot assembly |
enabled | boolean | No | true | Whether the skill is enabled; a load-time constant with no callback semantics |
scriptLines | object | No | {} | Action lines per phase, see below |
scriptConditions | object | No | {} | Phase gate conditions, see below |
mythicSkill | string | No | "" | MythicMobs skill name |
scriptLines & scriptConditions
Both objects use phase names as keys; only cast / hit / miss / fail are recognized (case-insensitive), other keys are ignored; processing drops blank lines and trims leading/trailing whitespace
scriptLines.<phase>: action line list, syntactically identical to a YAML skill'sscript.actions(source | gates | actions); built-in stages and any JS stage registered through EmakiCoreLib can be usedscriptConditions.<phase>: condition expression list, same syntax as a YAML skill's phase conditions; when a condition fails, the phase is skipped
Snapshot semantics
The payload is read once at registration into stable Java values: modifying the JS object after registration (including nested functions and arrays) changes no field of the registered skill
Registration & override semantics
- Definitions are registered through the public API into the external skill definition registry and participate in the overlay merged view: a JS definition with the same id overrides the YAML definition
- Failed registrations (blank id, registry not active, registration rejected) emit
console.scripts.register_error(with id and reason); that registration has no effect and other registrations are unaffected
Threading & Exception Isolation
- Registration happens during script loading (serialized through the host lock); at cast time
scriptLinesexecute through the existing action pipeline exactly like YAML skills — JS does not sit on the casting hot path - Per-file compile/evaluation failures are isolated by the host: that file is skipped and the rest continue; per-entry registration failures are reported and skipped individually
Configuration & Reload
The scripts section in config.yml:
yaml
scripts:
# false 时关闭脚本扫描与加载
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 skills registered so far are unregistered |
scripts.timeout_ms | integer | No | 5000 | Positive integer | Timeout in milliseconds for a single evaluation; non-positive values fall back to 5000 |
Reload semantics: scripts reload with EmakiSkills' existing reload flow — first all registration handles from this host are closed (removing the skills from the merged view and invalidating the pipeline), then the context is rebuilt and all files are re-evaluated and re-registered in file order. Once the reload completes, modified definitions take effect immediately and skills from deleted files are gone. When the GraalJS runtime is missing, a single warning is logged and script loading is skipped; other features are unaffected
Troubleshooting
| Console output | Cause | Fix |
|---|---|---|
console.scripts.engine_unavailable: "GraalJS runtime not detected; JS skill script extension is unavailable. Other features are unaffected." | GraalJS runtime missing | Verify your distribution ships the GraalJS runtime |
console.scripts.disabled: "JS skill script extension disabled (scripts.enabled: false)" | scripts.enabled: false | Enable it if desired, then reload |
console.scripts.loaded: "JS skill scripts loaded: %files% file (s) succeeded, %errors% error (s)" | Summary of every load/reload (errors include file errors and registration failures) | Check the following file_error / register_error entries when counts look wrong |
console.scripts.file_error: "Failed to load JS skill script %file%: %error%" | Syntax error, evaluation timeout, or runtime exception in one file | Fix that file per %error%; other files are unaffected |
console.scripts.register_error: "Failed to register JS skill [%id%]: %error%" | Blank or missing id (blank or missing id), registry not ready (EmakiSkills registry is not active), registration rejected (registration was rejected) | Check the id field; for an inactive registry check plugin load order |
console.scripts.example_release_failed: "Failed to release example skill script: %path% (%error%)" | Example script release failed | Check data folder permissions and retry |
Safety Notice
The script environment is a fully open sandbox (any Java class and server API reachable) with the same privileges as the server process — load only 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/skills/example_script_skill.js on first release; modify or delete it freely
js
// ============================================================
// EmakiSkills JS 技能定义示例
// ============================================================
// skills.register (payload) 把 JS 对象注册为技能定义, 与 skills/*.yml 中的
// YAML 技能同权参与解析, 槽位装配, 施法, 升级查询与 catalog; 同 id 时覆盖 YAML
// payload 字段在注册时一次性快照为稳定 Java 值, 注册后再修改 JS 对象不影响技能
// scriptLines / scriptConditions 与 YAML 技能的 script.actions 走同一条动作
// 管线: 可使用内置动作段, 也可使用 EmakiCoreLib 注册的任意 JS 动作段
// ============================================================
skills.register({
id: "script_example_burst",
// 显示名称, 支持 MiniMessage; 留空时回退为 id
displayName: "<gradient:#FFB75E:#ED8F03>烈焰爆发</gradient>",
// 技能描述, 逐行显示在技能界面
description: [
"<gray>由 JS 脚本注册的示例主动技能</gray>",
"<gray>施放火焰冲击并灼烧目标</gray>"
],
// GUI 图标材质; 小写材质名
iconMaterial: "blaze_powder",
// 触发方式: ACTIVE / PASSIVE; 无法识别的值按 ACTIVE 处理
activationType: "ACTIVE",
// 冷却 (tick) 与释放后所有技能共享的全局冷却 (tick); 负值按 0 处理
cooldownTicks: 100,
globalCooldownTicks: 20,
// 标签 (用于筛选与装备上限) 与 Lore 别名
tags: ["attack"],
loreAliases: ["烈焰爆发"],
// GUI 分类与排序; sortOrder 越小越靠前
uiCategory: "attack",
sortOrder: 30,
showInSlots: true,
enabled: true,
// 各阶段的动作行列表; 只识别 cast/hit/miss/fail (大小写不敏感), 其余键忽略
// 语法与 YAML 技能的 script.actions 完全一致: 源 | 闸门 | 动作
scriptLines: {
cast: [
"self | play_sound sound=entity.blaze.shoot volume=1 pitch=1.1",
"self | spawn_particle particle=flame count=24 extra=0.05",
"looking_at range=12 width=1.5 | keep"
],
hit: [
"inherited | damage amount=10",
"inherited | ignite duration=40t",
"inherited | spawn_particle particle=flame count=16 extra=0.1",
'send_message text="<gold>烈焰爆发命中目标!</gold>"'
],
miss: [
"self | spawn_particle particle=smoke count=8 extra=0.02",
'send_message text="<gray>烈焰爆发没有命中目标。</gray>"'
]
},
// 阶段闸门条件, 与 YAML 技能的 phase 条件同语法; 条件不通过时跳过该阶段
scriptConditions: {
cast: ["%level% >= 1"]
}
});Related Pages
- Script System: the syntax of the built-in
scriptblock and thescript_engineconfig - Definitions: fields of
skills/*.ymlskill definitions - CoreLib JS Scripting: host mechanism, directory layout, thread semantics, and the safety notice