Skip to content

API 与集成 ​

第三方插件通过 emaki-station-api 访问 EmakiStation

Maven 坐标 ​

xml
<dependency>
    <groupId>emaki.jiuwu.craft</groupId>
    <artifactId>emaki-station-api</artifactId>
    <version>1.1.7</version>
    <scope>provided</scope>
</dependency>

不要把 emaki-station-api-*.jar 放进服务器 plugins/. 它只是编译期构件

在 paper-plugin.yml 里把 EmakiStation 声明为依赖:

yaml
dependencies:
  server:
    EmakiStation:
      load: BEFORE
      required: false
      join-classpath: true

门面 ​

emaki.jiuwu.craft.station.api.EmakiStationApi 是静态门面:

方法说明
status()可用性与身份元数据, 返回 CoreLib 的 ApiStatus
catalog()查询层, 见下文 StationCatalog
operations()操作层, 见下文 StationOperations
extensions()预留扩展面, 当前是空接口
install(Bridge) / uninstall(Bridge)仅供 EmakiStation 自身生命周期调用, 标注 @ApiStatus.Internal

访问器永不返回 null: EmakiStation 缺失时, 各层返回显式的 FailureKind.UNAVAILABLE 结果. 调用方必须以该结果判定可用性, 不得把 NullPointerException 当作可用性信号

status() 的三个文本字段来自 bridge: pluginName 是 EmakiStation, pluginVersion 是描述符里的模块版本 (1.1.8), apiVersion 是 bridge 内置的接口约定常量 "1.0.0". 该值不等于 emaki-station-api 构件的版本号, 调用方不得用它判断 API 依赖版本

Bridge 标注 @ApiStatus.NonExtendable, 第三方插件不得实现

就绪判据 ​

status().ready() 的判据是模块的 contentReady: 配置, 工作站与配方数据已加载完成, 而不是"服务对象非空". reload 期间该判据为 false, 操作层返回 UNAVAILABLE, 不把时序问题伪装成"数据不存在"

配合 CoreLib 的跨模块就绪接口约定使用:

java
EmakiCoreLibApi.whenReady(myPlugin, "EmakiStation", () -> {
    // EmakiStation 的配方与工作站已加载
});

需要在 EmakiStation 每次重载后失效自己的缓存时, 用长期监听而不是在回调里重新注册 whenReady:

java
EmakiCoreLibApi.addModuleListener(myPlugin, "EmakiStation", phase -> {
    if (phase == ModuleReadinessPhase.READY) {
        rebuildMyCache();
    }
});

StationCatalog 查询层 ​

StationCatalog 标注 @ApiStatus.NonExtendable. 其方法为同步调用, 读取已加载的配置, 可在任意线程调用

方法说明
List<StationView> stations()列出所有已加载工作站, 按稳定 id 顺序; 运行时不可用时返回空
Optional<StationView> station(String stationId)按 id 查工作站
List<RecipeView> recipes()列出所有配方
Optional<RecipeView> recipe(String recipeId)按 id 查配方
List<RecipeView> recipesOf(String stationId)列出某工作站开放的配方
CompletableFuture<EmakiResult<QueueSnapshot>> queueSnapshotAsync(UUID playerId, String stationId)读取某玩家在某工作站的队列快照

queueSnapshotAsync 之所以异步, 是因为未缓存的队列需要从磁盘读取. 该方法对离线玩家同样有效: 队列数据按玩家的文件状态读取, 不要求 owner 在场. 玩家没有条目时返回空队列, 不返回失败

调用方不得假设该 future 在 owner 线程上完成

StationOperations 操作层 ​

标注 @ApiStatus.NonExtendable

方法线程说明
CompletableFuture<EmakiResult<SubmitOutcome>> submitAsync(UUID playerId, String stationId, String recipeId, long batch, MaterialChannel channel)任意线程, 要求目标玩家在线提交合成. batch 必须为正
CompletableFuture<EmakiResult<Unit>> cancelAsync(UUID playerId, String stationId, int index)任意线程, 要求目标玩家在线取消一条队列条目, index 从 0 开始
CompletableFuture<EmakiResult<Integer>> claimAsync(UUID playerId)任意线程, 要求目标玩家在线领取该玩家在所有工作站的可投递待领取产物, 载荷是实际清掉的条目数
EmakiResult<Unit> openGui(Player player, String stationId)必须在该玩家的 owner 线程打开工作站界面

submitAsync 的 channel 参数已失效 ​

材料现在来自"背包 + 仓库"的单一合并池并优先扣背包, 因此 channel 不再选择任何东西, 会被忽略. 该参数保留只为源码兼容

对已有调用方有两个后果: 传 MaterialChannel.BACKPACK 不再被拒绝 (历史上返回的 station.api_backpack_unsupported 已不会产生); 传 MaterialChannel.STORAGE 时, 提交会按合并池规则扣料, 背包材料会被动用

操作层的失败原因键 ​

submitAsync 在参数为空, batch <= 0 时分别返回 station.submit_bad_request / station.bad_batch; 模块未就绪返回 UNAVAILABLE. cancelAsync 与 claimAsync 在目标玩家离线时返回 targetOffline(), cancelAsync 还会在未知工作站时返回 station.unknown_station

openGui 的失败原因键为 station.open_bad_request, UNAVAILABLE, station.unknown_station, station.queue_not_loaded, station.missing_layout 或 FailureKind.WRONG_THREAD

cancelAsync 的退还规则 ​

退还总是回到材料原本来自的通道, 与工作站的产物路由无关. 部分退还仍报告成功, 缺口作为 Partial 原因键携带

openGui 刻意同步 ​

该方法立即触及观察者与其背包窗口, 因此必须在该玩家的 owner 线程执行. Folia 上是实体调度器 owner, Paper 上是主服务器线程. 从其他线程调用返回 FailureKind.WRONG_THREAD, 不会代为安排一次稍后的打开

模型类型 ​

emaki.jiuwu.craft.station.api.model 包:

类型说明
StationView工作站视图
RecipeView配方视图
MaterialRequirementView材料需求视图
QueueSnapshot队列快照
QueueEntryView队列条目视图
QueueEntryState条目状态: WAITING, RUNNING, PENDING_CLAIM
ConsumedMaterial已消耗材料记录
PendingOutput待投递产物
SubmitOutcome提交结果载荷
MaterialChannel材料通道枚举, submitAsync 已忽略该参数
OutputRouting产物路由
ProgressMode推进方式