Skip to content

API

EmakiSkills 对外公开的是静态门面 EmakiSkillsApi。第三方插件可以通过它获取脚本动作注册表,并注册自定义脚本动作来扩展技能效果。

EmakiSkillsApi

方法返回值说明
available()boolean判断 API 是否已安装。
scriptActionRegistry()SkillScriptActionRegistry获取脚本动作注册表。
java
if (EmakiSkillsApi.available()) {
    SkillScriptActionRegistry registry = EmakiSkillsApi.scriptActionRegistry();
}

如果你更习惯先做可用性检查,也可以使用 provider 辅助类:

java
if (!EmakiSkillsApiProvider.available()) {
    return;
}

EmakiSkillsApiProvider.requireAvailable();
SkillScriptActionRegistry registry = EmakiSkillsApi.scriptActionRegistry();

SkillScriptActionRegistry

脚本动作注册表,用于注册和管理自定义脚本动作。

方法说明
register(Plugin owner, SkillScriptAction action)注册自定义脚本动作,返回 SkillActionResult
unregister(String actionId)注销指定动作。
unregisterAll(Plugin owner)注销某插件注册的所有动作。
get(String actionId)获取动作实例。
ownerOf(String actionId)获取动作所属插件。
all()获取所有已注册动作。
byOwner(Plugin owner)获取某插件的所有动作。

SkillScriptAction 接口

第三方插件实现此接口来定义自定义脚本动作:

java
public interface SkillScriptAction {
    // 必须实现
    String id();
    CompletableFuture<SkillActionResult> execute(SkillScriptContext context, Map<String, String> arguments);

    // 可选覆盖
    default String category() { return "skill"; }
    default String description() { return id(); }
    default List<SkillActionParameter> parameters() { return List.of(); }
    default boolean acceptsDynamicParameter(String name) { return false; }
    default SkillActionExecutionMode executionMode() { return SkillActionExecutionMode.SYNC; }
    default long timeoutMillis() { return 30_000L; }
    default SkillActionResult validate(Map<String, String> arguments) { return SkillActionResult.ok(); }

    // 运行时实际调用的入口,默认委托到 execute(...)
    default CompletionStage<SkillActionResult> executeAsync(SkillScriptContext context,
            Map<String, String> arguments);
    default CompletionStage<SkillActionResult> executeAsync(SkillScriptContext context,
            Map<String, String> arguments,
            CancellationToken cancellationToken);
}

validate(...) 的默认实现会按 parameters() 检查必填项缺失与类型合法性。

执行模式

SkillActionExecutionMode 只有两个取值:

模式说明
SYNC在脚本所属的 Bukkit/Paper/Folia 调度域上调用。适合需要调用 Bukkit API 的动作。
ASYNC_IO在 CoreLib 的异步任务调度器上调用;Bukkit 的线程归属规则仍然适用。

返回 future 本身不会让方法调用脱离当前调度域:运行时按 executionMode() 选择调用域,实现方必须自行把后续的 Bukkit/Paper/Folia 工作切回正确的调度器。timeoutMillis() 作用于等待返回的 stage,默认 30000 毫秒。

参数类型

SkillActionParameterType 支持 STRINGINTEGERDOUBLEBOOLEANTIMETIME 接受 msst 后缀,无后缀按 tick 解析。

取消令牌

CancellationTokenexecuteAsync 重载由运行时调用。动作超时后运行时会取消该令牌,长耗时动作可通过 token.isCancelled() 主动放弃提交结果。

注册示例

java
@Override
public void onEnable() {
    if (!EmakiSkillsApi.available()) {
        return;
    }

    SkillScriptActionRegistry registry = EmakiSkillsApi.scriptActionRegistry();
    if (registry != null) {
        registry.register(this, new MyCustomAction());
    }
}

注册后,技能配置中即可使用自定义动作 ID:

yaml
script:
  actions:
    cast:
      - 'my_custom_action param1=value1 param2=value2'

装备技能 PDC 协议

装备技能的三个 PDC key 由独立协议模块 EmakiSkillsProtocol(Maven 坐标 emaki.jiuwu.craft:emaki-skills-protocol,版本 2.6.0)中的 EquipmentSkillPdcCodec 唯一持有。

EmakiSkillsProtocol 是纯协议模块,不是服务器插件。不要把它放进 plugins/ 目录。各运行时模块会把该协议类嵌入并 relocate 进自身 Jar。

EquipmentSkillPdcCodec 提供的静态语义:

方法说明
normalize(skillIds, activeSlot, boundTriggers)规范化并返回 EquipmentSkillPayload。技能 ID 去重排序,槽位名归一。
read(itemStack)读取并解码为 EquipmentSkillPayload
readRaw(itemStack)读取未解码的 RawSnapshot
write(itemStack, ...)写入载荷;载荷为空时等价于 clear。返回 SkillPdcMutation
clear(itemStack)移除三个 PDC key。返回 SkillPdcMutation
copy(original, rebuilt)把原物品的技能载荷复制到重建后的物品。
hasPayload(itemStack)判断物品是否带有任一技能 PDC key。
matchesSlot(actualSlot, requiredSlot)判断实际槽位是否满足要求槽位;all 匹配任意,hand 匹配主手与副手。

槽位常量:allhandmain_handoff_handhelmetchestplateleggingsboots

CoreLib 的 SkillPdcGateway 目前只是标注 @Deprecated(forRemoval = true) 的委托适配器,仅作遗留兼容面保留,新代码请直接使用 EquipmentSkillPdcCodec

接入建议

  • paper-plugin.yml 中通过 dependencies.server.EmakiSkills 声明可选依赖。
  • 在自己插件的 onDisable() 中调用 registry.unregisterAll(this) 注销已注册动作。注册表不监听 PluginDisableEvent,只有在 byOwner(owner) 查询到 owner 已禁用时才会顺带清理。
  • 动作 ID 建议使用插件前缀避免冲突,如 myplugin_lightning
  • ASYNC_IO 动作中不要直接调用 Bukkit API;本套件支持 Folia,请使用实体 / 区域调度器把工作切回正确的归属线程,而不是 Bukkit.getScheduler().runTask()
  • scriptActionRegistry() 在 EmakiSkills 未安装时返回 null,取用后需判空。

JavaScript 调用

CoreLib JavaScript 脚本中可通过 emaki.module("skills") 查询 Skills API 和技能脚本动作注册表。扩展脚本还可以在 scripts/extensions/skills/ 中注册完整的 JavaScript 技能动作。

方法说明
available()判断 Skills API 是否可用。
hasScriptAction(actionId)判断技能脚本动作是否已注册。
registeredScriptActions()返回当前注册的技能脚本动作 ID 列表。
js
function main(ctx) {
  if (emaki.module("skills").available() && emaki.module("skills").hasScriptAction("damage")) {
    emaki.logger.info("Skills damage action is available");
  }
  return true;
}

注册 JavaScript 技能动作

文件位置:plugins/EmakiCoreLib/scripts/extensions/skills/js_lightning_strike.js。该示例由 Skills 插件通过 CoreLib 脚本仓库释放,目标文件不存在时会自动释放。

js
function register(skills) {
  skills.registerAction({
    id: "js_lightning_strike",
    category: "javascript",
    description: "完全由 JavaScript 执行的雷击技能",
    executionMode: "SYNC",
    timeoutMillis: 1000,
    parameters: [
      { name: "damage", type: "DOUBLE", required: false, defaultValue: "10" }
    ],
    execute: "executeLightning"
  });
}

function executeLightning(ctx, args) {
  const caster = ctx.caster();
  const target = ctx.target();
  if (!target.exists()) {
    return { skipped: true, message: "没有目标" };
  }

  const loc = target.location();
  target.world().strikeLightningEffect(loc.x, loc.y, loc.z);

  emaki.module("attribute").applyDamage(caster, target, "lightning", Number(args.damage || 10), {
    skill_id: ctx.skillId(),
    source: "js_lightning_strike"
  });

  return { success: true, message: "JS 雷击技能已执行" };
}

技能配置中可直接使用动作 ID:

yaml
script:
  actions:
    cast:
      - 'js_lightning_strike damage=18'

JS 技能动作执行函数的 ctx 还提供常用串联能力:

方法说明
ctx.runAction(id, args)在当前技能上下文中调用 CoreLib 全局 Action。
ctx.runActionLine(line)执行一行 CoreLib Action 文本。
ctx.castMythic(skill, params)以技能施法者继续释放 MythicMobs 技能。
ctx.applyDamage(target, damageType, baseDamage, context)通过 Attribute 伤害管线对目标造成伤害。

示例:

js
function execute(ctx, args) {
  ctx.runAction("js_broadcast", { text: "技能 " + ctx.skillId() + " 触发" });
  ctx.castMythic("SomeMythicSkill", { power: "2" });
  ctx.applyDamage(ctx.target(), "fire", 12, { source: "skills_js" });
  return true;
}