Skip to content

API 与集成

EmakiStorageApi 是 EmakiStorage 对第三方插件开放的静态 API 门面。第三方插件只需要把 emaki-storage-api 作为编译依赖;运行时由服务器中安装的 EmakiStorage 插件本体提供实现。

服务器服主只安装 EmakiStorage-*.jar,不要把 emaki-storage-api-*.jar 放进 plugins/

Maven 依赖

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

Gradle 依赖

kotlin
dependencies {
    compileOnly("emaki.jiuwu.craft:emaki-storage-api:1.0.1")
}

可用性检查

java
import emaki.jiuwu.craft.storage.api.EmakiStorageApi;

if (!EmakiStorageApi.available() || !EmakiStorageApi.isReady()) {
    return;
}

available() 表示 bridge 已安装;isReady() 表示 bridge 已安装且插件完成初始化。isAvailable()isReady() 当前语义相同。

EmakiStorage 未安装或仍在启动时,所有方法降级为中性返回值,不抛异常。

EmakiStorageApi 静态方法

方法返回值未安装时的降级返回值
available()booleanfalse
isAvailable()booleanfalse
isReady()booleanfalse
apiVersion()String空字符串
pluginName()String空字符串
readSnapshot(UUID)CompletableFuture<StorageSnapshot>已完成的 StorageSnapshot.empty(playerId)
deposit(UUID, ItemStack, long)CompletableFuture<StorageResult>已完成的 StorageResult.unavailable()
withdraw(UUID, ItemStack, long)CompletableFuture<StorageResult>已完成的 StorageResult.unavailable()
countOf(UUID, ItemStack)CompletableFuture<Long>已完成的 0L
grantSlots(UUID, int)CompletableFuture<StorageResult>已完成的 StorageResult.unavailable()
setStackLimit(UUID, long)CompletableFuture<StorageResult>已完成的 StorageResult.unavailable()
setSlotStackLimit(UUID, int, long)CompletableFuture<StorageResult>已完成的 StorageResult.unavailable()

install(Bridge)uninstall(Bridge) 由 EmakiStorage 自己的生命周期调用,第三方插件不应使用。

方法语义

所有取 ItemStack 的方法都在内部归一化(clone()setAmount(1)),并按全量 ItemStack#equals 定位条目。调用方不需要预先计算 key。

所有写方法返回 CompletableFuture,因为条目表只能在归属实体线程上修改;实现会把工作调度到该线程再完成 future。普通的业务拒绝不会让 future 以异常完成,而是作为 StorageResult 状态返回。

方法语义
readSnapshot(UUID)读取分离快照,必要时从磁盘载入,离线玩家同样可读。快照不会观察到之后的存取。
deposit(UUID, ItemStack, long)存入,不触碰任何背包。达到单槽上限或槽位容量时会部分应用,需检查 appliedAmount()。会触发 StorageDepositEvent
withdraw(UUID, ItemStack, long)从仓库扣减,不把物品交给任何人。数量不足时以 PARTIAL 返回,reasonKeyinsufficient_stock
countOf(UUID, ItemStack)统计存量,不存在时为 0。走快照路径,离线玩家可用。
grantSlots(UUID, int)调整授予槽位池,负值回收。
setStackLimit(UUID, long)设置玩家级单槽上限,0 恢复继承 config.yml
setSlotStackLimit(UUID, int, long)设置某个逻辑槽位的条目级上限,0 恢复继承玩家级。该槽位没有条目时以 FAILED 返回,reasonKeyslot_empty

写方法要求玩家在线

depositwithdrawgrantSlotssetStackLimitsetSlotStackLimit 五个写方法在目标玩家离线时直接返回 StorageResult.unavailable(),不会静默改写未加载的存档。条目表只能与该玩家自己的 region 一同安全修改。玩家在线但数据尚未载入时同样返回 UNAVAILABLE

只读方法 readSnapshotcountOf 没有这个限制。

withdraw 不触发事件

EmakiStorageApi.withdraw 直接扣减条目,不触发 StorageWithdrawEvent;GUI、命令与 Action 路径走的事务实现会触发。这是当前实现的事实,与 deposit 不对称。若你的插件依赖取出事件做校验,请注意这条路径不会被拦截。

StorageApi 接口声明了除 available() / isAvailable() 之外的全部方法(apiVersion()pluginName()isReady()readSnapshotdepositwithdrawcountOfgrantSlotssetStackLimitsetSlotStackLimit)。EmakiStorageApi.Bridge 继承自 StorageApi

示例

java
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import emaki.jiuwu.craft.storage.api.EmakiStorageApi;
import emaki.jiuwu.craft.storage.api.model.StorageResult;

UUID playerId = player.getUniqueId();
ItemStack template = new ItemStack(Material.DIAMOND);

EmakiStorageApi.deposit(playerId, template, 1000L).thenAccept(result -> {
    if (result.status() == StorageResult.Status.PARTIAL) {
        long left = result.remainingAmount();
        // 只存进去一部分,剩余 left 单位需要自行处理。
    }
});

回调线程不保证是主线程。回调中若需要操作 Bukkit / Paper / Folia 状态,请自行调度到对应 owner 线程。

StorageSnapshot

不可变快照,条目按逻辑槽位顺序排列。

字段 / 方法类型说明
playerId()UUID归属玩家。
entries()List<StorageEntrySnapshot>按槽位顺序的条目列表,已 List.copyOf
capacity()StorageCapacity快照时刻的容量拆解。
defaultStackLimit()long玩家级单槽上限,0 表示继承配置。
sortMode()String持久化的排序模式 id。
entryCount()int条目数量。
totalAmount()long所有条目数量之和,溢出时饱和为 Long.MAX_VALUE
empty(UUID)静态方法返回空快照,sortModeamount_desc

StorageEntrySnapshot

单个条目的只读视图。

字段 / 方法类型说明
slotIndex()int逻辑槽位索引,0 起且无空洞。
template()ItemStack存储的物品模板,amount 归一化为 1。每次调用返回新的 clone(),可安全修改。
amount()long存量。
stackLimit()long作用于该条目的有效单槽上限。
remainingCapacity()long距上限还能存多少;stackLimit <= 0 时为 Long.MAX_VALUE - amount
fillRatio()double占用比例 0..1stackLimit <= 0 时为 0

StorageCapacity

容量拆解。四个来源分别持久化,所以调低 base_slots 不会消耗玩家已授予或已购买的槽位。

字段 / 方法类型说明
baseSlots()int来自 capacity.base_slots
permissionSlots()int来自最高的 emakistorage.slots.<n> 权限档位。
grantedSlots()int来自命令或 API 的授予,可为负。
purchasedSlots()int通过 GUI 扩容流程购买的槽位。
effectiveSlots()int钳制后的实际可用总量,不为负。
maxSlots()int配置的硬上限,0 表示无限。
usedSlots()int当前占用的槽位数。
slotsPerPage()int一页 GUI 渲染多少展示格。
freeSlots()int剩余空槽,不为负。
overflowing()boolean占用是否已超出有效容量。
totalPages()int由有效容量推导的总页数,至少 1。
reachablePages()int实际持有条目的最后一页,至少 1。
empty()静态方法全零容量,slotsPerPage 为 1。

StorageResult

写操作的结果。部分成功是一等结果:向剩余空间 300 的槽位存入 1000 单位,会得到 status == PARTIALappliedAmount == 300remainingAmount() == 700。调用方必须检查 appliedAmount(),不能假定请求量已全额应用。

字段 / 方法类型说明
status()Status结果分类。
requestedAmount()long调用方请求的数量。
appliedAmount()long实际应用的数量。
reasonKey()String稳定的机器可读原因,完全成功时为 null
applied()boolean是否应用了任何数量(appliedAmount > 0)。
complete()boolean是否全额应用(status == SUCCESS)。
remainingAmount()long未能应用的数量,不为负。

StorageResult.Status 五种状态:

状态含义
SUCCESS请求量全额应用。
PARTIAL部分应用,其余被拒绝。
FAILED完全没有应用。
CANCELLED被监听器取消,reasonKeycancelled
UNAVAILABLE插件、玩家数据或归属线程不可用,reasonKeyunavailable

集成边界

  • API 只暴露静态门面与 StorageApi 接口,不暴露内部服务、缓存、loader 或配置 parser。
  • 写方法要求目标玩家在线且数据已载入;不满足时以 UNAVAILABLE 返回而不是抛异常。
  • 如果只是在配置动作链中存取物品或授予槽位,优先使用 CoreLib 动作;第三方插件代码集成再使用 EmakiStorageApi
  • 需要拦截存入、取出与付费扩容流程时使用 事件 API:这三个事件属于 emaki-storage-api,用标准 @EventHandler 监听。
  • 只需要在文本模板中读取容量与存量时使用 PlaceholderAPI 占位符,不必接入代码。