Skip to content

JS Scripting ​

Since 2.8.5, EmakiItem supports registering custom item effect types from JavaScript. Entry point: drop .js files into plugins/EmakiItem/scripts/ and call items.registerEffectType({...}) from the script. Registered script effect types have the same standing as built-in ones, so item definitions can use effects: - type: <your id> right away

The host mechanism, directory layout, timeout handling, and fully open sandbox semantics match CoreLib exactly and are not repeated here; the contract lives in the safety notice and thread semantics sections of CoreLib JS Scripting

items Binding API ​

Payload fields ​

FieldTypeRequiredDefaultDescription
idstringYes—Effect type id, normalized with the same rules as built-in typeIds (lowercase, spaces to underscores); must not collide with the built-in effect types. Rejected built-in ids: variables, ea_attribute, es_skill, accessory_slot, name_action, lore_action
parsefunctionYes—parse(ctx): called on every item build to convert the raw effect entry into effect data
clearfunctionNo—clear(effect, itemCtx): called before apply to remove data written by the previous build
applyfunctionYes—apply(effect, itemCtx): writes the effect into the item being built

parse (ctx) ​

ctx propertyDescription
ctx.definitionIdId of the item definition that declares the effect
ctx.configRaw configuration of the effects entry (including the type field), deep-converted into a property-accessible JS object

Return contract: returning null / undefined skips the entry (aligned with the ItemEffectType.parse contract); any other return value is passed to clear / apply as effect

clear (effect, itemCtx) / apply (effect, itemCtx) ​

itemCtx propertyDescription
itemStackThe item being built (a Bukkit ItemStack host object; call its methods directly to modify it)
variablesExpression variables resolved from the definition
equipSlotEquipment slot

Registration & runtime semantics ​

  • Load-time validation: the payload must be an object; id is required, must be a string, and cannot be a built-in id; parse and apply must be functions; clear must be a function when provided. Violations emit console.scripts.register_error (with id and reason) and skip that registration
  • Runtime order: on every item build, parse → clear → apply run in order; clear is skipped when not provided
  • Exception isolation: exceptions/timeouts in parse skip the entry and log (EmakiItem script effect '<id>' parse for item '<definitionId>' failed: ...); exceptions in clear / apply are caught by the wrapper and logged, with the existing EmakiItemEffectApplier isolation as a second safety net — one failing effect does not break the rest of the item build
  • Threading: callbacks run on the item build thread, serialized through the host lock

Configuration & Reload ​

The scripts section in config.yml:

yaml
scripts:
  # 是否启用脚本扩展. 关闭后不扫描不加载脚本, 已注册的脚本效果随之注销
  enabled: true

  # 单次脚本求值或回调的超时 (毫秒). 超时中断该次调用, 超时后脚本宿主继续可用
  # 非正数回退默认值 5000
  timeout_ms: 5000

  # 调试模式; 开启后输出更详细的脚本加载信息
FieldTypeRequiredDefaultAllowed valuesDescription
scripts.enabledbooleanNotruetrue / falseWhen disabled, no scripts are scanned or loaded and script effect types 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 EmakiItem's existing reload flow — first all registration handles from this host are closed (the registration API clears the effect type prototype cache at the same time), then the context is rebuilt and all files are re-evaluated and re-registered in file order. Once the reload completes, modified scripts take effect immediately; effect types from deleted files are gone, and item definitions still referencing an unregistered type are treated as unknown effect types. 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 unavailable, script extension disabled; other features are unaffected."GraalJS runtime missingVerify your distribution ships the GraalJS runtime
console.scripts.disabled: "Script extension is disabled in the config, no scripts were loaded."scripts.enabled: falseEnable it if desired, then reload
console.scripts.loaded: "Script loading finished: %loaded% file (s), %errors% error (s)."Summary of every load/reloadCheck the following file_error / register_error entries when counts look wrong
console.scripts.file_error: "Script failed to load: %file% -> %error%"Syntax error, evaluation timeout, or runtime exception in one fileFix that file per %error%; other files are unaffected
console.scripts.register_error: "Script effect type registration failed: %id% -> %error%"Invalid payload (payload must be an effect type object, id is required, id must be a string, id is blank), built-in id clash (id '<id>' is a reserved built-in effect type), parse/apply not functions, clear not a function when provided, or registration rejectedFix the payload or choose another id per %error%
Log EmakiItem script effect '<id>' parse for item '<definitionId>' failed: ...parse threw or timed outFix per the detail; the entry is skipped
Log EmakiItem script effect '<id>' clear for item '<definitionId>' failed: ...clear threw or timed outFix per the detail; that clear is skipped
Log EmakiItem script effect '<id>' apply for item '<definitionId>' failed: ...apply threw or timed outFix per the detail; the effect was not written

Safety Notice ​

The script environment is a fully open sandbox (any Java class and server API reachable, including direct ItemStack modification) 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 file ships with the plugin and is released to scripts/items/example_effect.js only when that file does not exist; an existing file is never overwritten, and you may modify or delete it freely

js
// ============================================================================
// EmakiItem 脚本扩展示例: 自定义物品效果类型 (items 绑定)
//
// 这是演示脚本: 注册一个 id 为 script_example_glow 的物品效果类型,
// 物品定义中这样使用:
//   effects:
//     - type: script_example_glow
//       level: 2
//
// parse / clear / apply 的完整契约请参考 EmakiItemApi 的 ItemEffectType /
// ItemEffect 接口文档, 或 docs 站 EmakiItem 模块的 scripts 页面
//
// 安全声明: 脚本环境默认全开放 (可访问任意 Java 类与服务器 API),
// 具备与服务器同级权限, 请只加载你自己审查过的脚本
//
// 释放与覆盖: 此文件仅在本文件不存在时首次释放, 已存在的文件不会被覆盖
// ============================================================================

// 演示效果写入 Lore 的行前缀, clear 按它识别并移除旧行
var LINE_PREFIX = "§7灵光 ";

items.registerEffectType({
    // 效果类型 id (必填): 会被规范化为小写 + 空格转下划线;
    // 不得与内置效果类型 (variables / ea_attribute / es_skill / accessory_slot /
    // name_action / lore_action) 重名
    id: "script_example_glow",

    // parse (必填): 物品每次构建时调用, 把原始效果条目转换为 effect 数据
    // ctx.definitionId: 声明该效果的物品定义 id
    // ctx.config: effects 条目的原始配置 (含 type 字段), 可按属性访问
    // 返回 null / undefined 表示跳过该条目 (本例: level 非法时跳过)
    // 返回值会作为 effect 传给下面的 clear / apply
    parse: function (ctx) {
        var level = 1;
        if (ctx.config && ctx.config.level != null) {
            level = Number(ctx.config.level);
        }
        if (isNaN(level) || level <= 0) {
            return null;
        }
        return { level: Math.min(Math.floor(level), 10) };
    },

    // clear (可选): 在 apply 之前调用, 移除上次构建写入的数据
    // itemCtx.itemStack: 正在构建的物品 (Bukkit ItemStack, 可直接调用其方法)
    // 本示例把效果写入物品 Lore, 所以按前缀行移除
    clear: function (effect, itemCtx) {
        var meta = itemCtx.itemStack.getItemMeta();
        if (meta == null || !meta.hasLore()) {
            return;
        }
        var lore = meta.getLore();
        var kept = [];
        for (var i = 0; i < lore.size(); i++) {
            var line = String(lore.get(i));
            if (line.indexOf(LINE_PREFIX) === 0) {
                continue;
            }
            kept.push(line);
        }
        meta.setLore(kept);
        itemCtx.itemStack.setItemMeta(meta);
    },

    // apply (必填): 把效果写入正在构建的物品
    // itemCtx.variables: 定义解析后的表达式变量; itemCtx.equipSlot: 槽位
    // 本示例按 level 在 Lore 末尾追加一行演示文本 (仅演示, 真实用法参考契约文档)
    apply: function (effect, itemCtx) {
        var meta = itemCtx.itemStack.getItemMeta();
        if (meta == null) {
            return;
        }
        var lore = meta.hasLore() ? meta.getLore() : [];
        lore.add(LINE_PREFIX + new Array(effect.level + 1).join("★"));
        meta.setLore(lore);
        itemCtx.itemStack.setItemMeta(meta);
    }
});
  • Definitions: item definitions and the effects entry syntax
  • API: the ItemEffectType / ItemEffect Java contract (the payloads used by these scripts are its JS mirror)
  • CoreLib JS Scripting: host mechanism, directory layout, thread semantics, and the safety notice