API and Integration
EmakiStorageApi is the static API facade EmakiStorage exposes to third-party plugins. A third-party plugin only needs emaki-storage-api as a compile dependency; at runtime the implementation is provided by the EmakiStorage plugin installed on the server.
Server owners install only EmakiStorage-*.jar; do not place emaki-storage-api-*.jar into plugins/.
Maven Dependency
<dependency>
<groupId>emaki.jiuwu.craft</groupId>
<artifactId>emaki-storage-api</artifactId>
<version>1.0.1</version>
<scope>provided</scope>
</dependency>Gradle Dependency
dependencies {
compileOnly("emaki.jiuwu.craft:emaki-storage-api:1.0.1")
}Availability Check
import emaki.jiuwu.craft.storage.api.EmakiStorageApi;
if (!EmakiStorageApi.available() || !EmakiStorageApi.isReady()) {
return;
}available() reports whether the bridge is installed; isReady() reports that the bridge is installed and the plugin has finished initialising. isAvailable() currently has the same semantics as isReady().
When EmakiStorage is absent or still starting, every method degrades to a neutral return value instead of throwing.
EmakiStorageApi Static Methods
| Method | Return type | Degraded value when unavailable |
|---|---|---|
available() | boolean | false |
isAvailable() | boolean | false |
isReady() | boolean | false |
apiVersion() | String | Empty string |
pluginName() | String | Empty string |
readSnapshot(UUID) | CompletableFuture<StorageSnapshot> | Completed StorageSnapshot.empty(playerId) |
deposit(UUID, ItemStack, long) | CompletableFuture<StorageResult> | Completed StorageResult.unavailable() |
withdraw(UUID, ItemStack, long) | CompletableFuture<StorageResult> | Completed StorageResult.unavailable() |
countOf(UUID, ItemStack) | CompletableFuture<Long> | Completed 0L |
grantSlots(UUID, int) | CompletableFuture<StorageResult> | Completed StorageResult.unavailable() |
setStackLimit(UUID, long) | CompletableFuture<StorageResult> | Completed StorageResult.unavailable() |
setSlotStackLimit(UUID, int, long) | CompletableFuture<StorageResult> | Completed StorageResult.unavailable() |
install(Bridge) and uninstall(Bridge) are called by EmakiStorage's own lifecycle and should not be used by third-party plugins.
Method Semantics
Every method taking an ItemStack normalises it internally (clone(), setAmount(1)) and locates the entry by full ItemStack#equals. Callers never need to pre-compute a key.
All mutating methods return a CompletableFuture because the entry table may only be touched on the owning entity thread; the implementation schedules the work there and completes the future afterwards. Ordinary business rejection does not complete the future exceptionally — it arrives as a StorageResult status instead.
| Method | Semantics |
|---|---|
readSnapshot(UUID) | Reads a detached snapshot, loading from disk when necessary, so offline players can be read. The snapshot never observes later deposits or withdrawals. |
deposit(UUID, ItemStack, long) | Stores items without touching any inventory. Partial application is expected once the per-slot ceiling or slot capacity is reached, so check appliedAmount(). Fires StorageDepositEvent. |
withdraw(UUID, ItemStack, long) | Debits the warehouse without giving the items to anyone. Insufficient stock returns PARTIAL with the reasonKey insufficient_stock. |
countOf(UUID, ItemStack) | Counts stored units, 0 when absent. Uses the snapshot path, so it works for offline players. |
grantSlots(UUID, int) | Adjusts the granted slot pool; negative values reclaim slots. |
setStackLimit(UUID, long) | Sets the player-level ceiling; 0 restores inheritance from config.yml. |
setSlotStackLimit(UUID, int, long) | Sets the entry-level ceiling of one logical slot; 0 restores player-default inheritance. When that slot holds no entry the result is FAILED with the reasonKey slot_empty. |
Mutating Methods Require the Player Online
deposit, withdraw, grantSlots, setStackLimit and setSlotStackLimit return StorageResult.unavailable() immediately when the target player is offline; an unloaded save is never mutated silently. The entry table is only safe to touch alongside the player's own region. A player who is online but whose data has not finished loading also yields UNAVAILABLE.
The read-only methods readSnapshot and countOf do not share this restriction.
withdraw Does Not Fire an Event
EmakiStorageApi.withdraw debits the entry directly and does not fire StorageWithdrawEvent; the GUI, command and action paths go through the transaction implementation, which does. This is a fact of the current implementation and is asymmetric with deposit. If your plugin relies on the withdraw event for validation, note that this path is not intercepted.
Example
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();
// Only part was stored; the remaining `left` units need handling.
}
});The callback thread is not guaranteed to be the main thread. If a callback needs to touch Bukkit / Paper / Folia state, schedule it onto the appropriate owner thread yourself.
StorageSnapshot
An immutable snapshot with entries ordered by logical slot index.
| Field / method | Type | Description |
|---|---|---|
playerId() | UUID | The owning player. |
entries() | List<StorageEntrySnapshot> | Entries in slot order, already List.copyOf. |
capacity() | StorageCapacity | The capacity breakdown at snapshot time. |
defaultStackLimit() | long | The player-level per-slot ceiling; 0 means inherit config. |
sortMode() | String | The persisted sort mode id. |
entryCount() | int | How many distinct entries are stored. |
totalAmount() | long | Summed amount across every entry, saturating at Long.MAX_VALUE. |
empty(UUID) | static method | Returns an empty snapshot whose sortMode is amount_desc. |
StorageEntrySnapshot
A read-only view of a single entry.
| Field / method | Type | Description |
|---|---|---|
slotIndex() | int | Logical slot index, 0-based and gap-free. |
template() | ItemStack | The stored item template with amount normalised to 1. Each call returns a fresh clone(), safe to mutate. |
amount() | long | How many units are stored. |
stackLimit() | long | The effective per-slot ceiling applied to this entry. |
remainingCapacity() | long | Room left before the ceiling; Long.MAX_VALUE - amount when stackLimit <= 0. |
fillRatio() | double | Occupancy ratio in 0..1; 0 when stackLimit <= 0. |
StorageCapacity
The capacity breakdown. The four sources are persisted separately, so lowering base_slots never consumes slots a player was granted or purchased.
| Field / method | Type | Description |
|---|---|---|
baseSlots() | int | From capacity.base_slots. |
permissionSlots() | int | From the highest emakistorage.slots.<n> permission tier. |
grantedSlots() | int | From command or API grants; may be negative. |
purchasedSlots() | int | Slots bought through the in-GUI unlock flow. |
effectiveSlots() | int | The clamped total actually available, never negative. |
maxSlots() | int | The configured hard ceiling; 0 means unlimited. |
usedSlots() | int | How many slots currently hold an entry. |
slotsPerPage() | int | How many storage slots one GUI page renders. |
freeSlots() | int | Remaining free slots, never negative. |
overflowing() | boolean | Whether occupancy exceeds the effective capacity. |
totalPages() | int | Total pages derived from the effective capacity, at least 1. |
reachablePages() | int | The last page that actually holds an entry, at least 1. |
empty() | static method | An all-zero capacity whose slotsPerPage is 1. |
StorageResult
The outcome of a mutation. Partial success is a first-class result: depositing 1000 units into a slot with 300 room left yields status == PARTIAL, appliedAmount == 300 and remainingAmount() == 700. Callers must inspect appliedAmount() rather than assuming the requested amount was applied in full.
| Field / method | Type | Description |
|---|---|---|
status() | Status | The outcome classification. |
requestedAmount() | long | The amount the caller asked for. |
appliedAmount() | long | The amount actually applied. |
reasonKey() | String | A stable machine-readable reason; null on full success. |
applied() | boolean | Whether any amount at all was applied (appliedAmount > 0). |
complete() | boolean | Whether the full amount was applied (status == SUCCESS). |
remainingAmount() | long | The amount that could not be applied, never negative. |
The five StorageResult.Status values:
| Status | Meaning |
|---|---|
SUCCESS | The full requested amount was applied. |
PARTIAL | Part was applied; the rest was rejected. |
FAILED | Nothing was applied. |
CANCELLED | A listener cancelled the operation; reasonKey is cancelled. |
UNAVAILABLE | The plugin, player data or owning thread was unavailable; reasonKey is unavailable. |
Integration Boundaries
- The API exposes only the static facade and the
StorageApiinterface, never internal services, caches, loaders or config parsers. - Mutating methods require the target player to be online with data loaded; otherwise they return
UNAVAILABLErather than throwing. - To move items or grant slots from a configured action chain, prefer CoreLib Actions; use
EmakiStorageApifor third-party code integration. - To intercept the deposit, withdraw or paid-expansion flow, use the Event API: those three events ship in
emaki-storage-apiand are consumed with a standard@EventHandler. - To merely read capacity and stored amounts inside a text template, use the PlaceholderAPI Placeholders instead of writing code.