Skip to content

公开 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-item-api</artifactId><version>2.8.15</version><scope>provided</scope></dependency>
kotlin
repositories { maven("https://repo.crypticlib.com/repository/maven-public/") }
dependencies { compileOnly("emaki.jiuwu.craft:emaki-item-api:2.8.15") }

仅编译依赖

不要安装, bundle, shade 或 relocate API Jar. runtime 已内嵌未 relocate 的 API 类; 重复 ClassLoader 类型会破坏事件和 bridge

门面与六个入口 ​

前置条件: EmakiItemApi.status().usable() 返回 true. 通过后以下入口非 null:

  • catalog(): 定义, 别名, 身份与套装查询
  • operations(): 创建, 刷新, 套装同步与修复 GUI
  • repair(): 禁修标志, 经济修复报价与执行
  • migration() (experimental): 管理型文件/库存迁移
  • extensions(): 物品层预览 provider 与自定义 effect 类型注册
  • state(): 物品自定义持久状态的类型化读写

install, uninstall, Bridge 只属于 runtime; 第三方不得调用或实现

Operations ​

  • create(id,amount) 在 global-region owner thread 构建并触发可取消的 EmakiItemCreateEvent. 监听器可替换结果; 取消映射为 FailureKind.CANCELLED
  • refresh / forceRefresh 返回重建后的 stack; 实时库存物品须在 holder owner thread
  • refreshPlayer(player,trigger) 与 refreshEquippedSets(player,trigger) 在玩家 owner thread, compare-before-write 冲突可返回携带实际 ItemRefreshSummary 的 Partial
  • openRepairGui(player) 返回 EmakiResult<Unit>, 玩家 owner thread

Repair ​

repair().isDisabled, markDisabled, clearDisabled 管理 durability-disable 标志. quote(Player,ItemStack) 返回 RepairQuoteView; 不可负担仍是成功报价且 affordable()==false. repair(Player,ItemStack) 走真实经济修复服务: 事件, 扣费, 补偿, 耐久提交和 post actions 都在同一 runtime 流程中. 材料修复仍只走 GUI/库存事务, 不提供独立的简化 API

Migration ​

preview(oldId,newId) 与 apply(oldId,newId,replaceReferences,keepAlias) 会同步执行文件系统工作, 调用方必须选择非 tick worker; I/O 失败映射为 INTERNAL_ERROR, 部分文件已提交时返回 Partial<MigrationOutcome>. migrateInventory(Player) 需要玩家 owner thread; migrateAllOnline() 只处理当前线程拥有的在线玩家, Folia 所有权不完整时返回 Partial<Integer>

Extensions ​

registerLayerPreview(Plugin,ItemLayerPreviewProvider) 返回 owner-scoped registration; 关闭 handle 或按 owner 注销. provider 使用克隆的 request/result, 不得跨 owner 线程修改实时 stack

自定义 effects 类型 ​

registerEffectType(Plugin owner, ItemEffectType type) 返回 ItemEffectRegistration; unregisterEffectTypes(Plugin owner) 移除某插件的全部自定义效果类型. 注册后, 物品定义可以在六个内置 type (variables, ea_attribute, es_skill, accessory_slot, name_action, lore_action) 之外声明自己的 effects: - type: "<你的 id>". 内置 id 是保留的: 注册它们会被拒绝并返回不生效的 handle

ItemEffectType 只有两个方法:

  • typeId(): 与 effects[].type 匹配的 id; 空白 id 使注册不生效
  • parse(ItemEffectParseContext): 把一条原始 effect 条目转成可应用的效果; 返回 null 表示跳过该条目 (例如配置不适用于当前物品)

解析结果是 ItemEffect, 只有 apply(ItemEffectApplyContext) 是抽象方法, clear(ItemEffectApplyContext) 默认空实现. EmakiItem 在同一物品上一定先调用 clear 再调用 apply: clear 用于清除本效果在既往构建中写入的数据 (必须容忍物品本就不含该数据), apply 用于重新写入. 只写自己命名空间的数据, 不要改动或删除其他模块的持久化数据

上下文类型由 EmakiItem 提供, 第三方不得实现:

  • ItemEffectParseContext: definitionId() 返回声明该效果的物品定义 id; effect() 返回原始 effect 条目 (含 type), 是只含纯数据的防御性副本, 不可修改
  • ItemEffectApplyContext 继承前者, 并加 itemStack() (正在构建的物品, 直接写它, 不要保留引用), variables() (定义已解析的表达式变量, 可直接代入模板), equipSlot() (定义归一化后的装备槽, 例如 main_hand)

行为边界:

  • type 与 typeId() 比较前都会被归一化: 去除首尾空白, 按 Locale.ROOT 小写, 空格替换为下划线
  • 某个 type 没有已注册处理器时, 该条目被跳过且不输出任何提示, 物品定义不会被拒绝; 因此可以引用尚未安装的 provider
  • 注册与注销都会丢弃 EmakiItem 已构建物品的原型缓存, 受影响定义会在下次构建时重新解析
  • 注册内部已同步, 可从任意线程调用; typeId() 在注册时读取, parse 与 ItemEffect 的回调都在构建该物品的线程上运行, 必须非阻塞, 不得从中调度任务, 不得保留传入的 stack
  • 原始 effect 配置只在定义至少含一个自定义效果时进入物品定义签名, 因此不含自定义效果的物品签名不变; 改写自定义效果的配置即可触发物品更新, 无需额外签名钩子

ItemState ​

state() 是物品自定义持久状态的类型化读写层, 字段由 ItemStateKey<T> (namespace + partition + 字段名 + ItemStateType) 标识:

  • snapshot(item) 返回不可变的 ItemStateSnapshot: 可用 get(key) 取类型化值, contains(key) 判断字段是否存在, repaired() 判断读取时是否顺带修复了元数据. item() 返回克隆件
  • repair(item) 修复元数据并回读快照
  • get(item,key) 返回 Optional<T>, 类型不匹配时为空
  • set(item,key,value), add(item,key,amount), remove(item,key) 返回 ItemStateMutation<T>: 含 oldValue, newValue, delta, 以及 committed, changed, clamped, rejected 与 reason, 被 clamp 或拒绝都能从返回值判断, 不抛异常

结果说明 ​

无负载成功使用 EmakiResult<Unit>; 通用可选负载访问器为 optionalValue(). FailureKind 只有 UNAVAILABLE, NOT_FOUND, INVALID_INPUT, REJECTED, CANCELLED, TARGET_OFFLINE, WRONG_THREAD, INTERNAL_ERROR

就绪与 reload ​

status().ready() 的判据是"定义已加载", 不是"组件已构造". 插件启用后组件立即非 null, 但定义表在 reload 期间会被重建, 因此:

  • reload 窗口内 status() 报 loading, catalog(), operations(), repair(), migration() 中返回 EmakiResult 的方法一律返回 unavailable() -- 不是 NOT_FOUND. 把 unavailable() 当作"稍后重试", 不要当作"该物品不存在"
  • definitionIds(), exists(id), typeIds() 这类纯查询无法表达不可用, reload 期间返回的是重载前的陈旧结果或空结果. 需要"定义确实加载好了"再动作时, 用 EmakiCoreLibApi.whenReady(plugin, "EmakiItem", callback), 不要用空结果推断
  • 缓存了 EmakiItem 定义的插件, 要在每次重载后更新缓存, 用 EmakiCoreLibApi.addModuleListener(plugin, "EmakiItem", phase -> ...): LOADING 时失效, READY 时重建. whenReady 只响一次, 不适合这个用途

启动期依赖 EmakiItem 定义的插件应挂 whenReady 而不是在自己的 onEnable 里直接查表: 模块加载顺序不保证 EmakiItem 的首次加载先于你的 onEnable. 回调只触发一次, 且运行线程不保证是 owner thread, 触碰玩家或背包前需显式调度