Skip to content

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:

FieldTypeRequiredDefaultDescription
idstringYes—Skill id (ID-normalized); blank or missing ids fail registration with an error
displayNamestringNofalls back to idDisplay name, supports MiniMessage
descriptionstring or string arrayNo[]Skill description, shown line by line in the skill UI; a single string is treated as one line
iconMaterialstringNo""GUI icon material (lowercase material name)
activationTypestringNo"ACTIVE"ACTIVE / PASSIVE; only passive (case-insensitive, - treated as _) becomes PASSIVE, everything else is treated as ACTIVE
passiveTriggersstring arrayNo[]Passive trigger id list (deduplicated, - converted to _)
cooldownTicksintegerNo0Cooldown in ticks; negative values are clamped to 0
globalCooldownTicksintegerNo0Shared global cooldown in ticks applied to all skills after casting; negative values are clamped to 0
tagsstring arrayNo[]Tags (used for filtering and equipment limits, normalized and deduplicated)
loreAliasesstring arrayNo[]Lore aliases
pdcSkillIdstringNo""PDC skill id
uiCategorystringNo"default"GUI category
sortOrderintegerNo0GUI sort order, lower comes first
showInSlotsbooleanNotrueWhether the skill participates in slot assembly
enabledbooleanNotrueWhether the skill is enabled; a load-time constant with no callback semantics
scriptLinesobjectNo{}Action lines per phase, see below
scriptConditionsobjectNo{}Phase gate conditions, see below
mythicSkillstringNo""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's script.actions (source | gates | actions); built-in stages and any JS stage registered through EmakiCoreLib can be used
  • scriptConditions.<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 scriptLines execute 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
FieldTypeRequiredDefaultAllowed valuesDescription
scripts.enabledbooleanNotruetrue / falseWhen disabled, no scripts are scanned or loaded and script skills registered so far are unregistered
scripts.timeout_msintegerNo5000Positive integerTimeout 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 outputCauseFix
console.scripts.engine_unavailable: "GraalJS runtime not detected; JS skill script extension is unavailable. Other features are unaffected."GraalJS runtime missingVerify your distribution ships the GraalJS runtime
console.scripts.disabled: "JS skill script extension disabled (scripts.enabled: false)"scripts.enabled: falseEnable 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 fileFix 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 failedCheck 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"]
  }
});
  • Script System: the syntax of the built-in script block and the script_engine config
  • Definitions: fields of skills/*.yml skill definitions
  • CoreLib JS Scripting: host mechanism, directory layout, thread semantics, and the safety notice