简体中文
JS 脚本扩展
EmakiSkills 自 2.8.3 起支持以 JavaScript 注册技能定义. 将 .js 文件放入 plugins/EmakiSkills/scripts/ 目录后, 通过 skills.register({...}) 注册的技能与 skills/*.yml 中的 YAML 技能同权参与解析, 槽位装配, 施法, 升级查询与 catalog; 同 id 时 JS 定义覆盖 YAML
宿主机制, 目录加载顺序, 超时与全开放沙箱规则与 CoreLib 一致, 线程与安全约束见 CoreLib JS 脚本扩展 的安全声明与线程约束章节
skills 绑定 API
payload 字段
字段与外部技能定义 (ExternalSkillDefinition) 对齐, 命名采用驼峰:
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
id | string | 是 | — | 技能 ID (经 ID 规范化); 空白或缺失时注册失败并报错 |
displayName | string | 否 | 回退为 id | 显示名, 支持 MiniMessage |
description | string 或 string 数组 | 否 | [] | 技能描述, 逐行显示在技能界面; 单字符串按单行处理 |
iconMaterial | string | 否 | "" | GUI 图标材质 (小写材质名) |
activationType | string | 否 | "ACTIVE" | ACTIVE / PASSIVE; 只有 passive (大小写不敏感, - 视为 _) 识别为 PASSIVE, 其余一律按 ACTIVE 处理 |
passiveTriggers | string 数组 | 否 | [] | 被动触发器 id 列表 (自动去重, - 转 _) |
cooldownTicks | integer | 否 | 0 | 冷却 (tick); 负值按 0 处理 |
globalCooldownTicks | integer | 否 | 0 | 释放后所有技能共享的全局冷却 (tick); 负值按 0 处理 |
tags | string 数组 | 否 | [] | 标签 (用于筛选与装备上限, 自动规范化去重) |
loreAliases | string 数组 | 否 | [] | Lore 别名 |
pdcSkillId | string | 否 | "" | PDC 技能 ID |
uiCategory | string | 否 | "default" | GUI 分类 |
sortOrder | integer | 否 | 0 | GUI 排序, 越小越靠前 |
showInSlots | boolean | 否 | true | 是否进入槽位装配 |
enabled | boolean | 否 | true | 是否启用; 载入期定值, 不触发回调 |
scriptLines | object | 否 | {} | 各阶段动作行列表, 见下文 |
scriptConditions | object | 否 | {} | 各阶段前置条件, 见下文 |
mythicSkill | string | 否 | "" | MythicMobs 技能名 |
scriptLines 与 scriptConditions
两个对象均以阶段名为键, 仅识别 cast / hit / miss / fail (大小写不敏感), 其余键被忽略; 处理时剔除空行, 修剪行首尾空白
scriptLines.<阶段>: 动作行列表, 语法与 YAML 技能的script.actions完全一致 (源 | gate | 动作), 可使用内置动作段, 也可使用 EmakiCoreLib 注册的任意 JS 动作段scriptConditions.<阶段>: 条件表达式列表, 与 YAML 技能的 phase 条件同语法; 条件不通过时跳过该阶段
快照的生成规则
payload 在注册时一次性读取为稳定 Java 值: 注册完成后再修改 JS 对象 (包括其中的函数, 数组) 不改变已注册技能的任何字段
注册与覆盖规则
- 技能定义经公开 API 注册进外部技能定义注册表, 参与 overlay 合并视图: 同 id 时 JS 定义覆盖 YAML 定义
- 注册失败 (id 空白, 注册表未就绪, 注册被拒绝) 时输出
console.scripts.register_error(含 id 与原因), 该次注册不生效, 其余注册不受影响
线程与异常隔离
- 注册发生在脚本载入期 (经宿主锁串行化); 施法期
scriptLines进入既有动作管线执行, 与 YAML 技能完全相同, JS 不驻留施法热路径 - 单文件编译/求值失败由宿主隔离: 该文件跳过, 其余文件继续加载; 单条注册失败逐条报错并跳过该条
配置与重载
config.yml 中的 scripts 节:
yaml
scripts:
# false 时关闭脚本扫描与加载
enabled: true
# 单次求值超时, 单位毫秒; 非正数回退 5000
timeout_ms: 5000| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
scripts.enabled | boolean | 否 | true | true / false | 关闭后不扫描不加载脚本, 已注册的脚本技能随之注销 |
scripts.timeout_ms | integer | 否 | 5000 | 正整数 | 单次脚本求值超时毫秒数; 非正数回退 5000 |
重载规则: 脚本随 EmakiSkills 既有 reload 流程重载 -- 先关闭本次加载的全部注册句柄 (对应技能从合并视图移除并触发管线失效), 再重建 Context, 按文件顺序重新求值与注册. 重载完成后, 修改后的定义立即生效, 已删除文件对应的技能不复存在. GraalJS 运行库缺失时输出一条告警并跳过脚本加载, 其余功能不受影响
错误排查
| 控制台输出 | 原因 | 处置 |
|---|---|---|
console.scripts.engine_unavailable: "未检测到 GraalJS 运行库, JS 技能脚本扩展不可用, 其余功能不受影响" | GraalJS 运行库缺失 | 确认发行版包含 GraalJS 运行库 |
console.scripts.disabled: "JS 技能脚本扩展未启用 (scripts.enabled: false)" | scripts.enabled: false | 需要时开启后 reload |
console.scripts.loaded: "JS 技能脚本加载完成: %files% 个文件成功, %errors% 个错误" | 每次加载/重载的汇总 (错误数含文件错误与注册失败) | 错误数异常时查看随后的 file_error / register_error |
console.scripts.file_error: "JS 技能脚本 %file% 加载失败: %error%" | 单文件语法错误, 求值超时或运行时异常 | 按 %error% 修复该文件; 其余文件不受影响 |
console.scripts.register_error: "JS 技能注册失败 [%id%]: %error%" | id 空白或缺失 (blank or missing id), 注册表未就绪 (EmakiSkills registry is not active), 注册被拒绝 (registration was rejected) | 检查 id 字段; 注册表未就绪时检查插件加载顺序 |
console.scripts.example_release_failed: "释放 JS 技能示例脚本失败: %path% (%error%)" | 示例脚本首次释放失败 | 检查数据目录权限后重试 |
安全声明
脚本环境默认全开放 (可访问任意 Java 类与服务器 API), 权限与服务器进程同级, 仅加载你审查过的脚本; 重载会重建 Context 并清空脚本顶层状态. 完整声明见 CoreLib JS 脚本扩展
示例脚本
以下示例随插件首次释放到 scripts/skills/example_script_skill.js, 可直接修改或删除
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"]
}
});相关页面
- 原生脚本系统: 技能 YAML 内置
script块的语法与script_engine配置 - 定义文件:
skills/*.yml技能定义字段 - CoreLib JS 脚本扩展: 宿主机制, 目录结构, 线程约束与安全声明