Skip to content

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

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

Gradle Dependency

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

Availability Check

java
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

MethodReturn typeDegraded value when unavailable
available()booleanfalse
isAvailable()booleanfalse
isReady()booleanfalse
apiVersion()StringEmpty string
pluginName()StringEmpty 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.

MethodSemantics
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

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();
        // 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 / methodTypeDescription
playerId()UUIDThe owning player.
entries()List<StorageEntrySnapshot>Entries in slot order, already List.copyOf.
capacity()StorageCapacityThe capacity breakdown at snapshot time.
defaultStackLimit()longThe player-level per-slot ceiling; 0 means inherit config.
sortMode()StringThe persisted sort mode id.
entryCount()intHow many distinct entries are stored.
totalAmount()longSummed amount across every entry, saturating at Long.MAX_VALUE.
empty(UUID)static methodReturns an empty snapshot whose sortMode is amount_desc.

StorageEntrySnapshot

A read-only view of a single entry.

Field / methodTypeDescription
slotIndex()intLogical slot index, 0-based and gap-free.
template()ItemStackThe stored item template with amount normalised to 1. Each call returns a fresh clone(), safe to mutate.
amount()longHow many units are stored.
stackLimit()longThe effective per-slot ceiling applied to this entry.
remainingCapacity()longRoom left before the ceiling; Long.MAX_VALUE - amount when stackLimit <= 0.
fillRatio()doubleOccupancy 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 / methodTypeDescription
baseSlots()intFrom capacity.base_slots.
permissionSlots()intFrom the highest emakistorage.slots.<n> permission tier.
grantedSlots()intFrom command or API grants; may be negative.
purchasedSlots()intSlots bought through the in-GUI unlock flow.
effectiveSlots()intThe clamped total actually available, never negative.
maxSlots()intThe configured hard ceiling; 0 means unlimited.
usedSlots()intHow many slots currently hold an entry.
slotsPerPage()intHow many storage slots one GUI page renders.
freeSlots()intRemaining free slots, never negative.
overflowing()booleanWhether occupancy exceeds the effective capacity.
totalPages()intTotal pages derived from the effective capacity, at least 1.
reachablePages()intThe last page that actually holds an entry, at least 1.
empty()static methodAn 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 / methodTypeDescription
status()StatusThe outcome classification.
requestedAmount()longThe amount the caller asked for.
appliedAmount()longThe amount actually applied.
reasonKey()StringA stable machine-readable reason; null on full success.
applied()booleanWhether any amount at all was applied (appliedAmount > 0).
complete()booleanWhether the full amount was applied (status == SUCCESS).
remainingAmount()longThe amount that could not be applied, never negative.

The five StorageResult.Status values:

StatusMeaning
SUCCESSThe full requested amount was applied.
PARTIALPart was applied; the rest was rejected.
FAILEDNothing was applied.
CANCELLEDA listener cancelled the operation; reasonKey is cancelled.
UNAVAILABLEThe plugin, player data or owning thread was unavailable; reasonKey is unavailable.

Integration Boundaries

  • The API exposes only the static facade and the StorageApi interface, never internal services, caches, loaders or config parsers.
  • Mutating methods require the target player to be online with data loaded; otherwise they return UNAVAILABLE rather than throwing.
  • To move items or grant slots from a configured action chain, prefer CoreLib Actions; use EmakiStorageApi for third-party code integration.
  • To intercept the deposit, withdraw or paid-expansion flow, use the Event API: those three events ship in emaki-storage-api and 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.