Skip to content

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) 对齐, 命名采用驼峰:

字段类型必填默认说明
idstring是—技能 ID (经 ID 规范化); 空白或缺失时注册失败并报错
displayNamestring否回退为 id显示名, 支持 MiniMessage
descriptionstring 或 string 数组否[]技能描述, 逐行显示在技能界面; 单字符串按单行处理
iconMaterialstring否""GUI 图标材质 (小写材质名)
activationTypestring否"ACTIVE"ACTIVE / PASSIVE; 只有 passive (大小写不敏感, - 视为 _) 识别为 PASSIVE, 其余一律按 ACTIVE 处理
passiveTriggersstring 数组否[]被动触发器 id 列表 (自动去重, - 转 _)
cooldownTicksinteger否0冷却 (tick); 负值按 0 处理
globalCooldownTicksinteger否0释放后所有技能共享的全局冷却 (tick); 负值按 0 处理
tagsstring 数组否[]标签 (用于筛选与装备上限, 自动规范化去重)
loreAliasesstring 数组否[]Lore 别名
pdcSkillIdstring否""PDC 技能 ID
uiCategorystring否"default"GUI 分类
sortOrderinteger否0GUI 排序, 越小越靠前
showInSlotsboolean否true是否进入槽位装配
enabledboolean否true是否启用; 载入期定值, 不触发回调
scriptLinesobject否{}各阶段动作行列表, 见下文
scriptConditionsobject否{}各阶段前置条件, 见下文
mythicSkillstring否""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.enabledboolean否truetrue / false关闭后不扫描不加载脚本, 已注册的脚本技能随之注销
scripts.timeout_msinteger否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"]
  }
});

相关页面 ​