API
EmakiSkills 对外公开的是静态门面 EmakiSkillsApi。第三方插件可以通过它获取脚本动作注册表,并注册自定义脚本动作来扩展技能效果。
EmakiSkillsApi
| 方法 | 返回值 | 说明 |
|---|---|---|
available() | boolean | 判断 API 是否已安装。 |
scriptActionRegistry() | SkillScriptActionRegistry | 获取脚本动作注册表。 |
if (EmakiSkillsApi.available()) {
SkillScriptActionRegistry registry = EmakiSkillsApi.scriptActionRegistry();
}如果你更习惯先做可用性检查,也可以使用 provider 辅助类:
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 接口
第三方插件实现此接口来定义自定义脚本动作:
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 支持 STRING、INTEGER、DOUBLE、BOOLEAN、TIME。TIME 接受 ms、s、t 后缀,无后缀按 tick 解析。
取消令牌
带 CancellationToken 的 executeAsync 重载由运行时调用。动作超时后运行时会取消该令牌,长耗时动作可通过 token.isCancelled() 主动放弃提交结果。
注册示例
@Override
public void onEnable() {
if (!EmakiSkillsApi.available()) {
return;
}
SkillScriptActionRegistry registry = EmakiSkillsApi.scriptActionRegistry();
if (registry != null) {
registry.register(this, new MyCustomAction());
}
}注册后,技能配置中即可使用自定义动作 ID:
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 匹配主手与副手。 |
槽位常量:all、hand、main_hand、off_hand、helmet、chestplate、leggings、boots。
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 列表。 |
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 脚本仓库释放,目标文件不存在时会自动释放。
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:
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 伤害管线对目标造成伤害。 |
示例:
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;
}