English
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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | 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 |
parse | function | Yes | — | parse(ctx): called on every item build to convert the raw effect entry into effect data |
clear | function | No | — | clear(effect, itemCtx): called before apply to remove data written by the previous build |
apply | function | Yes | — | apply(effect, itemCtx): writes the effect into the item being built |
parse (ctx)
| ctx property | Description |
|---|---|
ctx.definitionId | Id of the item definition that declares the effect |
ctx.config | Raw 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 property | Description |
|---|---|
itemStack | The item being built (a Bukkit ItemStack host object; call its methods directly to modify it) |
variables | Expression variables resolved from the definition |
equipSlot | Equipment slot |
Registration & runtime semantics
- Load-time validation: the payload must be an object;
idis required, must be a string, and cannot be a built-in id;parseandapplymust be functions;clearmust be a function when provided. Violations emitconsole.scripts.register_error(with id and reason) and skip that registration - Runtime order: on every item build,
parse→clear→applyrun in order;clearis skipped when not provided - Exception isolation: exceptions/timeouts in
parseskip the entry and log (EmakiItem script effect '<id>' parse for item '<definitionId>' failed: ...); exceptions inclear/applyare caught by the wrapper and logged, with the existingEmakiItemEffectApplierisolation 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
# 调试模式; 开启后输出更详细的脚本加载信息| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
scripts.enabled | boolean | No | true | true / false | When disabled, no scripts are scanned or loaded and script effect types 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 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 output | Cause | Fix |
|---|---|---|
console.scripts.engine_unavailable: "GraalJS runtime unavailable, script extension disabled; other features are unaffected." | GraalJS runtime missing | Verify your distribution ships the GraalJS runtime |
console.scripts.disabled: "Script extension is disabled in the config, no scripts were loaded." | scripts.enabled: false | Enable it if desired, then reload |
console.scripts.loaded: "Script loading finished: %loaded% file (s), %errors% error (s)." | Summary of every load/reload | Check 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 file | Fix 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 rejected | Fix the payload or choose another id per %error% |
Log EmakiItem script effect '<id>' parse for item '<definitionId>' failed: ... | parse threw or timed out | Fix per the detail; the entry is skipped |
Log EmakiItem script effect '<id>' clear for item '<definitionId>' failed: ... | clear threw or timed out | Fix per the detail; that clear is skipped |
Log EmakiItem script effect '<id>' apply for item '<definitionId>' failed: ... | apply threw or timed out | Fix 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);
}
});Related Pages
- Definitions: item definitions and the
effectsentry syntax - API: the
ItemEffectType/ItemEffectJava contract (the payloads used by these scripts are its JS mirror) - CoreLib JS Scripting: host mechanism, directory layout, thread semantics, and the safety notice