Skip to content

API 与集成 ​

第三方插件通过 emaki-accessory-api 访问 EmakiAccessory, 依赖坐标, 门面与查询层契约如下

Maven 坐标 ​

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

禁止把 emaki-accessory-api-*.jar 放进服务器 plugins/; 它只是编译期构件

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

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

门面 ​

emaki.jiuwu.craft.accessory.api.EmakiAccessoryApi 是静态门面:

方法说明
status()可用性与身份元数据, 返回 CoreLib 的 ApiStatus
catalog()查询层, 见下文 AccessoryCatalog
install(Bridge) / uninstall(Bridge)仅供 EmakiAccessory 自身生命周期调用

访问器永不返回 null. EmakiAccessory 缺失时 catalog() 返回空答案, 因此调用方不得把 NullPointerException 当作可用性信号

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

没有操作层与扩展层 ​

这是刻意的设计. 饰品通过 EmakiAttribute 与 EmakiSkills 各自已有的 owner 作用域扩展点推送贡献, 不存在饰品专属的战斗或技能管线供第三方挂钩, 因此本模块只暴露查询层

要影响饰品带来的属性或技能, 应接入 EmakiAttribute / EmakiSkills 的对应扩展点, 不应向 EmakiAccessory 索要接口

不要缓存层对象 ​

在使用点解析 catalog(), 禁止将其存入字段: backing bridge 在 reload 时会被替换

java
// 正确
EmakiAccessoryApi.catalog().parts();

// 错误: reload 后这个引用会指向旧 bridge
private final AccessoryCatalog cached = EmakiAccessoryApi.catalog();

AccessoryCatalog 查询层 ​

标注 @ApiStatus.NonExtendable

方法线程说明
List<AccessoryPartView> parts()任意线程列出所有部位定义
Optional<AccessoryPartView> part(String partId)任意线程按 id 查部位
List<String> slotInstanceIds()任意线程列出所有槽位实例 id
List<String> pageIds()任意线程列出所有饰品页 id, 按页顺序. 1.0.3 起
String enabledPage(UUID playerId)该玩家的 owner 线程当前生效页 id; 未知或该页当前不可用时返回空字符串. 1.0.3 起
Map<String, EquippedAccessoryView> equipped(UUID playerId)该玩家的 owner 线程该玩家在生效页上装备的饰品, 键为槽位实例 id
Map<String, EquippedAccessoryView> equippedOnPage(UUID playerId, String pageId)该玩家的 owner 线程指定页上存放的饰品. 1.0.3 起
int equippedSetPieces(UUID playerId, String setId)该玩家的 owner 线程该玩家某套装的已装备件数, 只统计生效页; 未知时返回 0

部位, 槽位与页 id 查询读取配置事实, 可在任意线程调用

玩家查询读取内存会话缓存, 必须在该玩家的 owner 线程调用. 玩家数据尚未加载完成时返回空结果而不是阻塞 -- 因此空结果不能推断为"该玩家未装备任何饰品"

多页模型有以下五点要注意:

  • equipped() 只报告生效页. 要看其他页用 equippedOnPage(); 需要知道哪一页生效用 enabledPage()
  • equipped() 行为已收窄 (见下节): 它包含孤儿槽位, 方便调用方看到待取回的物品; 不需要时用 EquippedAccessoryView#orphaned() 过滤. 孤儿槽位不参与套装件数
  • 玩家缺少生效页权限时, enabledPage() 返回空字符串, equipped() 返回空 Map, 但存储物品不受影响, 仍可被玩家取回
  • 传入 null 的 playerId 一律返回空结果 (equippedSetPieces 返回 0)
  • equippedSetPieces() 读的是最近一次上报的件数缓存, 该玩家尚未产生过一次上报, 或刚 reload 过 (reload 会重置缓存) 时返回 0

这些方法都是纯查询, 没有就绪门禁. reload 期间会读到陈旧数据; 需要在 reload 后失效自己缓存的调用方, 使用 CoreLib 的长期就绪监听:

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

模型类型 ​

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

AccessoryPartView ​

java
public record AccessoryPartView(String partId, int count, String displayName, List<String> slotInstanceIds)
成员说明
partId()部位 id
count()展开出的槽位数, 至少为 1
displayName()MiniMessage 显示名, 可能为空字符串
slotInstanceIds()展开后的槽位实例 id, 按序号顺序, 格式 <partId>_<序号>, 序号从 1 开始. 列表不可变

EquippedAccessoryView ​

java
public record EquippedAccessoryView(String pageId, String slotInstanceId, String partId, ItemStack item, boolean orphaned)
成员说明
pageId()该饰品所在的页 id. 1.0.3 起, 是 record 的首个组件
slotInstanceId()槽位实例 id
partId()所属部位 id; 部位已不存在时是原始前缀 (可能为空字符串)
item()存储物品的防御性副本. 查询层不会产出空槽位的条目, 因此调用方拿到的视图里它非 null; record 本身允许 null (此时返回 null)
orphaned()该槽位是否已不被所属页声明

item() 每次调用都返回一份克隆, 调用方无法通过视图改动存储状态

版本兼容与破坏性变更 ​

多页模型 (@since 1.0.3) 引入了两处需要开发者改代码的变更:

变更兼容性迁移写法
AccessoryCatalog#equipped(UUID) 行为收窄: 返回条目从"该玩家全部槽位"变为"仅启用页", 无权限或无启用页时返回空 map签名不变 (源码与二进制兼容), 但行为不兼容需要看其他页时改用 equippedOnPage(playerId, pageId); 需要知道哪一页生效时用 enabledPage(playerId)
EquippedAccessoryView 增加首位组件 pageId, record 组件由 4 个变为 5 个源码与二进制不兼容: 规范构造器 arity 由 4 变为 5, 直接 new 的调用点编译失败, 已编译的旧调用点在运行期抛 NoSuchMethodError升级依赖后重新编译, 并按 5 参构造 (pageId 在最前) 补齐调用

pageIds(), enabledPage(), equippedOnPage() 是同批新增的查询方法; 接口标注 @ApiStatus.NonExtendable, 第三方不应自行实现, 因此新增方法不影响调用方