简体中文
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, 第三方不应自行实现, 因此新增方法不影响调用方