Skip to content

CoreLib 公开 API ​

编译依赖 ​

xml
<repositories><repository><id>jiuwu-releases</id><url>https://repo.crypticlib.com/repository/maven-public/</url></repository></repositories>
<dependency><groupId>emaki.jiuwu.craft</groupId><artifactId>emaki-corelib-api</artifactId><version>4.8.28</version><scope>provided</scope></dependency>
kotlin
repositories { maven("https://repo.crypticlib.com/repository/maven-public/") }
dependencies { compileOnly("emaki.jiuwu.craft:emaki-corelib-api:4.8.28") }

仅编译依赖

API Jar 不能放入 plugins/, 也不得 bundle, shade 或 relocate. EmakiCoreLib-4.8.36.jar 已内嵌未 relocate 的 API 类; 同名类由不同 ClassLoader 定义后, Bukkit 事件类型和静态 bridge 都会失配

路线 A: 窄的第三方门面 ​

EmakiCoreLibApi 仅面向第三方插件. 其余 Emaki 业务模块通过共享 runtime classpath 直接连接 CoreLib 实现类; 本门面不是完整隔离层, 也不会扩展为 CoreLib 全部内部服务的入口

公开范围包括: dialogs(), Folia-safe scheduling(), 统一物品显示名, configured item 构建/patch, 组件 capability 元数据, 以及 owner-scoped 自定义 action 注册. 文本/YAML, GUI 基础设施, 表达式与条件引擎, 语言/生命周期/PDC/经济/占位符, 内部事件总线与 assembly 均保持内部

使用前先检查 EmakiCoreLibApi.status().usable(). install, uninstall 标注为 @ApiStatus.Internal, Bridge 是 runtime-owned @ApiStatus.NonExtendable; 第三方不得调用, 缓存或实现

线程与窄门 ​

  • itemDisplayName, configured item build/patch, component capability 与 action registry 查询可从任意线程调用; 实时 stack 不得并发修改
  • EmakiScheduling 提供 global/entity/location/async 的 owner-aware 调度; 异步 body 不得访问 Bukkit
  • CoreLibDialogs.show/close 要求位于目标玩家的 owner thread; 线程转移经 scheduling() 完成
  • ItemBuildResult 是独立完整结果模型 (success, 克隆 item 与 issues), 不再包装为 EmakiResult
  • 自定义段按种类分三个方法注册: registerActionStage(Plugin,CoreActionStage), registerActionSource(Plugin,CoreActionSource), registerActionGate(Plugin,CoreActionGate), 都返回可关闭的 CoreStageRegistration. 没有 source 参数, 也没有按 id 注销的方法: 段只能通过返回的句柄撤销, 或在 owner 插件禁用时由 CoreLib 自动撤销, 因此一个插件无法注销另一个插件的段
  • executeActionLineAsync(Plugin, String, CoreActionExecutionContext) 可从任意线程调用. future 的完成线程为调用线程或最后 stage 的执行域之一, 不固定; continuation 访问 Bukkit 前必须通过 scheduling() 调度. 编译失败与业务失败都会正常完成为结构化结果, 不依赖异常
  • 结果是 CoreActionExecutionResult, 包括 status, reasonKey/reasonArguments, diagnostics, 有序 stages 与 keptTargets; 状态为 SUCCESS, SKIPPED, PARTIAL, COMPILE_FAILED, EXECUTION_FAILED, INVALID_REQUEST, UNAVAILABLE
  • 注册表查询为 actionStages() / actionStage(id) 与 actionTriggers() / actionTrigger(id). onStageRegistryRebuilt(owner, callback) 保留兼容的"同 owner 后注册替换前一个"行为; 需要同一插件注册多个相互独立回调时使用 addStageRegistryRebuildListener(owner, callback), 各回调独立追加, 返回可关闭的 CoreStageRebuildRegistration

动画入口 ​

CoreLib 的动画时序基础设施 (定义注册, K帧调度, 优先级抢占与冲突裁决) 通过同一门面暴露, 接口约定类在 emaki.jiuwu.craft.corelib.api.animation: AnimationDefinition, AnimationKeyframe, AnimationPriority, AnimationConflictPolicy, AnimationPlaybackHandle, AnimationListener, AnimationRegistration

  • registerAnimation(Plugin, AnimationDefinition): 注册一条 owner-scoped 定义, 返回可关闭的 AnimationRegistration; 同一 owner 重注册同一 id 会替换旧定义, 并阻止旧定义的后续播放
  • playAnimationAsync(Plugin, Entity, String, CoreActionExecutionContext): 返回 CompletableFuture<EmakiResult<AnimationPlaybackHandle>>, 任意线程可调, future 完成线程不固定. 未知定义, owner 已禁用, 或命中 AnimationConflictPolicy.REJECT 冲突都以结构化失败完成. 高优先级播放会抢占当前播放, 同优先级按定义自身的冲突策略裁决. K帧动作行以传入的 context 作为执行输入 (context 无 caster 时把播放实体补为 caster), 其中的 Bukkit 访问在实体 owner 线程执行
  • stopAnimation(Plugin, Entity, String): 停止该实体上由该 owner 发起的指定定义播放; definitionId 为 null 或空白时停止该 owner 在该实体上的全部播放. 返回是否实际终止了在飞播放
  • addAnimationListener(Plugin, AnimationListener): 注册播放监听, 返回可关闭的 AnimationRegistration; 同一 owner 重复注册是替换. 回调 (onPlay / onKeyframe / onStop) 运行在实体 owner 线程
  • AnimationPlaybackHandle 三态为 PLAYING / FINISHED / CANCELLED; playing() 判断是否仍在推进, cancel() 可重复调用且结果不变, 播放已结束或已取消后再调用不会抛异常
  • 注册句柄必须在 onDisable 关闭; owner 插件被禁用时, 其定义, 监听器与在飞播放都会被 CoreLib 自动撤销
  • 动画定义注册表在 CoreLib reload 后存活, 无需重建回调; 这与动作段表不同 -- 段表在 reload 时重建, 必须通过 onStageRegistryRebuilt / addStageRegistryRebuildListener 重放注册 (见上节)
  • CoreLib 不可用时, 注册与监听入口返回稳定的 inactive 句柄, playAnimationAsync 以 EmakiResult.unavailable() 完成, stopAnimation 返回 false, 均不抛异常

就绪状态约定 ​

每个 Emaki 模块在自己的数据加载完成, 进入 reload, 以及关闭后, 向 CoreLib 集中发布就绪状态. 消费方据此区分"数据尚未就绪"与"该条目不存在"

whenReady(Plugin owner, String moduleName, Runnable callback) 在目标模块数据就绪后执行回调, 返回可关闭的 ReadinessRegistration. 要点:

  • 回调仅执行一次. 模块 reload 会重新经历 not-ready → ready, 但已触发的回调不会重放. 需要跟随每次 reload 用 addModuleListener (见下节), 或在使用点重新判断
  • 禁止在回调内重新注册 whenReady 以模拟长期监听: 回调运行时模块已被标记为就绪, 重新注册会进入"已就绪"分支同步执行, 再注册再执行, 直至 StackOverflowError
  • 同一 owner 注册两次为追加第二个回调, 而非替换
  • moduleName 传字面量 (如 "EmakiItem") 而非目标模块 API jar 中的常量, 类加载原因与 ApiCapability.of(String) 相同
  • 目标模块已就绪时回调同步执行, 此时返回的句柄是 inactive. CoreLib 不可用或参数不可用时同样返回 inactive 句柄
  • 线程: 任意线程可调用; 回调运行在将模块标记为就绪的线程上, 不保证是 Bukkit owner thread. 访问玩家, 背包, 世界或 GUI 前必须显式调度
  • owner 插件被禁用时, 它注册的回调会被丢弃

isModuleReady(String moduleName) 是轮询接口, 适用于诊断或未就绪即可跳过的调用点; 需要就绪后必然执行一次时使用 whenReady

监听重载 ​

whenReady 只回答是否完成首次加载, 不回答其后是否发生过 reload. 缓存其他模块内容的消费方需要后者:

java
EmakiCoreLibApi.addModuleListener(this, "EmakiItem", phase -> {
    switch (phase) {
        case LOADING, ABSENT -> myCache.invalidate();
        case READY -> myCache.rebuild();
    }
});

ModuleReadinessPhase 三个相位:

相位含义消费方该做什么
LOADING模块开始替换数据立即失效缓存
READY数据已加载可用重建缓存. 首次加载与每次 reload 后都会触发
ABSENT模块已停用丢弃缓存. 同一会话内可能再次启用, 届时会依次收到 LOADING, READY

与 whenReady 的差异:

  • 持续有效, 每次状态跃变都通知, 直到句柄被关闭
  • 同一 owner 对同一模块重复注册是替换而非追加, onEnable 执行两次不会导致缓存重建两次
  • 注册时若模块已就绪, 不会立即回调. 立即回调是 whenReady 用来补漏信号窗口的手段, 长期监听没有这个窗口; 需要注册时的当前状态就查 isModuleReady
  • 状态被重复发布时不会重复通知 (相位只在真正跃变时触发). 多个模块在同步与异步 reload 两条路径上都会发布就绪, 去重收在 CoreLib 内部
  • 同一次 READY 里, 长期监听先于 whenReady 的一次性回调执行. 这样同时用了两者的消费方, 其一次性初始化读到的是已重建的缓存
  • 线程同 whenReady: 运行在发布状态的那个线程上, 不保证是 Bukkit owner thread
  • owner 插件被禁用时监听器被丢弃; ABSENT 相位不丢弃监听器, 因为消费方无从得知自己需要重新注册

只需感知 reload 已发生时使用本接口. CoreLib 不提供让第三方触发其他模块重载的入口

LOADING 到数据真正被替换之间存在窗口: 异步 reload 的模块 (Item, Attribute, Cooking, Gem, Skills, Strengthen) 在 LOADING 之后仍会短暂返回旧数据. 这是既有时序特性, 不是保证

模块自身的 status().ready() 判据是"数据已加载", 不是"组件已构造". 因此 reload 窗口内 status() 会报 loading, 返回 EmakiResult 的方法会返回 unavailable(); 而返回 Set, List, boolean 等无法表达不可用的纯查询方法, 在 reload 期间返回陈旧或空结果 -- 不要把空结果读成"不存在该条目"

EmakiResult ​

EmakiResult.ok() 返回 EmakiResult<Unit>. 穷尽处理三种变体: Success(value), Partial(value,reasonKey), Failure(kind,reasonKey,placeholders). 在 EmakiResult<T> 接口层统一读取可选负载时使用 optionalValue(); 具体 Success/Partial record 仍有直接 value() 访问器

FailureKind 精确为 UNAVAILABLE, NOT_FOUND, INVALID_INPUT, REJECTED, CANCELLED, TARGET_OFFLINE, WRONG_THREAD, INTERNAL_ERROR. reasonKey 是机器键而非玩家文本