Skip to content

动作管道扩展 SDK ​

第三方插件把自身的 source / gate / action 注册进 Emaki 动作管道后, 服主即可在任意动作行中调用这些段. 注册规范覆盖注册口, 域声明, 约定声明, Folia 注意事项与验证方式

同一份规范随 API jar 以 javadoc 形式发布 (emaki.jiuwu.craft.corelib.api.action 包的 package-info 与 CoreActionExecutionDomain 枚举), 在 IDE 中悬停即可查看 -- 页面内容与该 javadoc 保持同步

注册口一览 ​

六个注册口全部位于 EmakiCoreLibApi 门面, 按 owner (调用方插件) 作用域划分, 均返回可关闭句柄:

注册口段角色管道里怎么用
registerActionSource(Plugin, CoreActionSource)产生目标流行首, 如 <你的段> radius=8
registerActionGate(Plugin, CoreActionGate)过滤/重排/控制流... | <你的段> ...
registerActionStage(Plugin, CoreActionStage)产生效果... | <你的段> amount=3
registerActionTrigger(Plugin, CoreActionTrigger)触发入口由触发器机制调用
registerTargetCondition(Plugin, CoreTargetCondition)目标条件类型filter '<你的条件>=值'
registerTargetIdentityProvider(Plugin, CoreTargetIdentityProvider)外部系统身份身份识别与 <系统 id>_level 条件

无 source 参数, 无按 id 注销

段只能经返回句柄的 close() 撤销, 或在 owner 插件被禁用时由 CoreLib 自动撤销. 插件无法注销其他插件注册的段. 以相同 id 重复注册 (同 owner) 会替换旧段

域声明清单 (必须) ​

每个 source/action 都必须实现 executionTarget(CoreStagePlanningContext), 返回声明了调度域的 CoreActionExecutionTarget.未声明域 (UNDECLARED) 的段在注册期即被拒绝 -- 该类段在 Folia 上会直接导致崩溃, 而非抛出可处理错误

CoreActionExecutionDomain含义适用场景
SERVER_GLOBAL服务器全局 (主全局线程)查全服玩家, 改游戏规则, 写配置
CONTEXT_ENTITY当前目标实体的 owner 线程改实体背包/传送/发光, 发消息给玩家
LOCATION_REGION位置/区域域附近实体搜索, 在原点生成粒子/方块
ASYNC_COMPUTE纯异步计算不允许同时要求目标 (targetRequirement 必须为 NONE, 否则注册被拒)
UNDECLARED未声明禁止

gate 经 CoreGateThread 调度: PURE (不触碰任何 Bukkit 状态, 在 Folia 上始终安全, 亦为默认值) 或 NEEDS_ENTITY_READ (读取实体状态)

约定声明清单 ​

注册前对照这份清单, 每一项都在注册期校验, 写错会被拒绝并返回 reasonKey:

  1. id: 非空, 小写, 无 =; 与既有段同 id 注册会返回 duplicate_id (含既有 owner 名)
  2. 域: 见上节; 异步域 + 要求目标 = async_needs_target
  3. 参数表: CoreStageParameter.required(name, type, desc) / optional(name, type, default, desc) / positional(name, type, desc). 编译期对未声明参数报 unknown_argument, 缺必填报 missing_required_argument
  4. 目标需求: targetRequirement() 取 NONE (不要目标, 如广播) / OPTIONAL / REQUIRED_ENTITY / REQUIRED_LOCATION / REQUIRED_ANY. 声明 REQUIRED_* 后无目标的调用会被跳过 (action.run.no_target), 不必自己判空
  5. 上下文键: requiredContext() 声明本段读取的 CoreActionKey, 编译期对照触发阶段的 PhaseContract 检查, 缺键在加载期报错而非运行期 NPE
  6. 参数类型: CoreStageParameterType (STRING/INTEGER/DOUBLE/BOOLEAN/DURATION/ENTITY_TYPE/MATERIAL/PERCENTAGE/EXPRESSION/SOUND). 非占位符字面量在编译期按类型校验 (invalid_argument_type)

Folia 注意事项 ​

  • 15 个 Emaki runtime 全部声明 folia-supported: true. 段的执行域由声明决定, 不得假定始终位于主线程: 声明 CONTEXT_ENTITY 的段在实体 owner 线程执行, 声明 LOCATION_REGION 的段在位置域线程执行
  • 调度域是收紧而非放宽: 声明越窄, 可调用的 Bukkit API 越受限; 声明过宽 (如把修改实体背包的段声明为 SERVER_GLOBAL) 在 Folia 上会抛错
  • 需要跨域转移时使用 EmakiCoreLibApi.scheduling() 提供的 global/entity/location/async 四类 owner-aware 调度; 异步 body 内不得访问 Bukkit
  • 段内部发起的异步任务不受 CoreLib 管理, owner 插件禁用时须由注册方自行取消

reload 后必须重放注册 ​

动作段表在 CoreLib reload 时重建, 已注册的段随之失效. 必须在注册后登记重建回调:

java
EmakiCoreLibApi.onStageRegistryRebuilt(this, () -> {
    registerMyStages();
});

同一插件需要注册多个相互独立的回调时, 使用 addStageRegistryRebuildListener(owner, callback) (各回调独立追加, 返回可关闭句柄). 动画定义注册表不随 reload 重建, 无需重建回调

onEnable 的注册时序: 先检查 EmakiCoreLibApi.status().usable(); CoreLib 不可用时各注册口返回 inactive 句柄, 不抛异常

验证方式 ​

  1. 列出已注册段: <任意插件根命令> debug action registry (如 /emakicorelib debug action registry) -- 按 source/gate/action 分组列出全部段与 owner 插件名, 第三方段可直接识别
  2. 静态体检: debug action check "<你的段> 参数" -- parse + 校验, 输出全部诊断 (含列号与候选建议)
  3. 干跑: debug action run shadow "<管道行>" -- source/gate 真实求值, action 只回显不落地, 逐步观察该段在管道中的行为

完整示例 ​

最小第三方扩展插件骨架 (注册 1 source + 1 gate + 1 action):

java
public final class ExampleExtensionPlugin extends JavaPlugin {

    private CoreStageRegistration sourceHandle;
    private CoreStageRegistration gateHandle;
    private CoreStageRegistration actionHandle;

    @Override
    public void onEnable() {
        if (!EmakiCoreLibApi.status().usable()) {
            getLogger().warning("EmakiCoreLib 不可用,跳过动作段注册");
            return;
        }
        registerStages();
        EmakiCoreLibApi.onStageRegistryRebuilt(this, this::registerStages);
    }

    private void registerStages() {
        sourceHandle = EmakiCoreLibApi.registerActionSource(this, new HighLevelPlayersSource());
        gateHandle = EmakiCoreLibApi.registerActionGate(this, new FreshSpawnGate());
        actionHandle = EmakiCoreLibApi.registerActionStage(this, new BroadcastConsoleAction());
    }

    @Override
    public void onDisable() {
        close(sourceHandle);
        close(gateHandle);
        close(actionHandle);
    }

    private static void close(@Nullable CoreStageRegistration handle) {
        if (handle != null) {
            handle.close();
        }
    }
}

三段实现的要点:

java
final class HighLevelPlayersSource implements CoreActionSource {
    @Override public String id() { return "example_high_level_players"; }
    @Override public String description() { return "等级不低于 level 的在线玩家"; }
    @Override public List<CoreStageParameter> parameters() {
        return List.of(CoreStageParameter.optional("level", CoreStageParameterType.INTEGER, "10", "最低等级"));
    }
    @Override public CoreActionExecutionTarget executionTarget(CoreStagePlanningContext context) {
        return CoreActionExecutionTarget.global();
    }
    @Override public CoreSourceResult select(CoreStageContext context, CoreResolvedArguments arguments) {
        int min = arguments.getInt("level", 10);
        List<CoreActionSubject> matched = new ArrayList<>();
        for (Player player : Bukkit.getOnlinePlayers()) {
            if (player.getLevel() >= min) matched.add(CoreActionSubject.of(player));
        }
        return matched.isEmpty()
                ? CoreSourceResult.empty("example.source.no_match")
                : CoreSourceResult.selected(matched);
    }
}
  • gate: 实现 apply(context, inbound, arguments), 返回 CoreGateResult.passed(保留的流) / halted(reasonKey) / invalid(reasonKey); threadNeed() 按是否读实体选 PURE / NEEDS_ENTITY_READ
  • action: 实现 execute(context, arguments), 操作 context.currentTarget(), 返回 CoreActionOutcome.success() / skipped(reasonKey) / failure(kind, reasonKey); executionTarget 按实际改动的 Bukkit 状态声明域
  • 自定义 reasonKey (如 example.source.no_match) 的文案在注册方插件自身的 lang/ 中配置; 未配置时 DiagnosticRenderer 回退输出键本身, 不抛异常

参数类型速查 ​

类型编译期校验 (无占位符时)
STRING / SOUND恒通过
INTEGER / DOUBLE数字解析
BOOLEANtrue / false
DURATIONtick/时间格式 (1s, 20t...)
ENTITY_TYPE / MATERIAL原版枚举/材质存在
PERCENTAGE0~1 小数或 x/y 分数
EXPRESSION表达式引擎可求值

读取参数经 CoreResolvedArguments: getString / getInt / getDouble / getBoolean / getDurationTicks / getChance / getEntityType / getMaterial / getExpression, 各方法均带 fallback; 声明了 defaultValue 的可选参数在省略时回落到默认值