Skip to content

API and Integration

Third-party plugins reach EmakiStation through emaki-station-api.

Maven Coordinates

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

Do not put emaki-station-api-*.jar into the server plugins/ folder. It is a compile-time artifact only.

Declare EmakiStation as a dependency in your paper-plugin.yml:

yaml
dependencies:
  server:
    EmakiStation:
      load: BEFORE
      required: false
      join-classpath: true

Facade

emaki.jiuwu.craft.station.api.EmakiStationApi is the static facade:

MethodDescription
status()Availability and identity metadata, returning CoreLib's ApiStatus.
catalog()Query layer, see StationCatalog below.
operations()Operation layer, see StationOperations below.
extensions()Reserved extension surface; currently an empty interface.
install(Bridge) / uninstall(Bridge)For EmakiStation's own lifecycle only; annotated @ApiStatus.Internal.

Accessors never return null. While EmakiStation is absent the layers return explicit FailureKind.UNAVAILABLE results, so callers must not treat a NullPointerException as an availability signal.

Bridge is annotated @ApiStatus.NonExtendable; third-party plugins must not implement it.

Readiness Criterion

status().ready() is backed by the module's contentReady — configuration, stations and recipes have finished loading — not by "the service object is non-null". During a reload it is false and the operation layer returns UNAVAILABLE instead of disguising a timing problem as "the data does not exist".

Use it together with CoreLib's cross-module readiness contract:

java
EmakiCoreLibApi.whenReady(myPlugin, "EmakiStation", () -> {
    // EmakiStation recipes and stations are loaded
});

To invalidate your own cache after every EmakiStation reload, use the resident listener rather than re-registering whenReady inside the callback:

java
EmakiCoreLibApi.addModuleListener(myPlugin, "EmakiStation", phase -> {
    if (phase == ModuleReadinessPhase.READY) {
        rebuildMyCache();
    }
});

StationCatalog — Query Layer

Annotated @ApiStatus.NonExtendable. The synchronous methods read already-loaded configuration and are safe from any thread.

MethodDescription
List<StationView> stations()List every loaded station in stable id order; empty while the runtime is unavailable.
Optional<StationView> station(String stationId)Look up a station by id.
List<RecipeView> recipes()List every recipe.
Optional<RecipeView> recipe(String recipeId)Look up a recipe by id.
List<RecipeView> recipesOf(String stationId)List the recipes offered by one station.
CompletableFuture<EmakiResult<QueueSnapshot>> queueSnapshotAsync(UUID playerId, String stationId)Read a detached snapshot of one player's queue at one station.

queueSnapshotAsync is asynchronous because a queue that is not cached has to be read from disk. It works for offline players: queue data is per-player file state and does not need the owner present. A player with no entries yields an empty queue rather than a failure.

Do not assume that future completes on an owner thread.

StationOperations — Operation Layer

Annotated @ApiStatus.NonExtendable.

MethodThreadDescription
CompletableFuture<EmakiResult<SubmitOutcome>> submitAsync(UUID playerId, String stationId, String recipeId, long batch, MaterialChannel channel)Any thread; requires the target player onlineSubmit a craft. batch must be positive.
CompletableFuture<EmakiResult<Unit>> cancelAsync(UUID playerId, String stationId, int index)Any thread; requires the target player onlineCancel one queue entry; index is zero-based.
CompletableFuture<EmakiResult<Integer>> claimAsync(UUID playerId)Any thread; requires the target player onlineClaim every deliverable pending output across all stations; the payload counts the entries actually cleared.
EmakiResult<Unit> openGui(Player player, String stationId)Must run on that player's owner threadOpen a station window.

The channel Parameter of submitAsync Is Inert

Materials now come from a single merged pool spanning the player's inventory and their warehouse, spent inventory-first. channel therefore no longer selects anything and is ignored. The parameter is retained for source compatibility.

Two consequences for existing callers: passing MaterialChannel.BACKPACK used to be refused with station.api_backpack_unsupported and now submits normally, so that reason key is no longer produced; and a submission may draw on the inventory even when MaterialChannel.STORAGE was requested.

Refund Semantics of cancelAsync

Refunds always return to the channel each material came from, independent of the station's output routing. A partially refunded cancellation still reports success and carries the shortfall as a Partial reason key.

openGui Is Deliberately Synchronous

It touches the viewer and their inventory window immediately and therefore must run on the supplied player's owner thread. On Folia that is the entity scheduler owner; on Paper it is the main server thread. Calls from any other thread return FailureKind.WRONG_THREAD and never schedule a later open on the caller's behalf.

Model Types

The emaki.jiuwu.craft.station.api.model package:

TypeDescription
StationViewStation view.
RecipeViewRecipe view.
MaterialRequirementViewMaterial requirement view.
QueueSnapshotQueue snapshot.
QueueEntryViewQueue entry view.
QueueEntryStateEntry state: WAITING, RUNNING, PENDING_CLAIM.
ConsumedMaterialRecord of a consumed material.
PendingOutputAn output awaiting delivery.
SubmitOutcomeSubmission result payload.
MaterialChannelMaterial channel enum; ignored by submitAsync.
OutputRoutingOutput routing.
ProgressModeProgression mode.