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.1.7</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, every layer returns an explicit FailureKind.UNAVAILABLE result; callers must not use a NullPointerException as an availability signal

status() derives its three text fields from the bridge: pluginName is EmakiStation, pluginVersion is the module version from the descriptor (1.1.8), and apiVersion is the bridge's built-in contract constant "1.0.0". apiVersion is not the version of the emaki-station-api artifact, so never use it to decide which API artifact version to depend on

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 rather than disguising a timing problem as "the data does not exist"

Combine it 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, register the resident listener once instead of 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 configuration that has already loaded, and they are safe to call 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 must be read from disk. It works for offline players: queue data is per-player file state and does not require the owner to be present. A player with no entries yields an empty queue rather than a failure

Do not assume that the 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 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 only

Two consequences for existing callers: passing MaterialChannel.BACKPACK is no longer refused (the historical station.api_backpack_unsupported reason key is no longer produced), and a submission may draw on the inventory even when MaterialChannel.STORAGE was requested

Failure Reason Keys of the Operation Layer ​

submitAsync returns station.submit_bad_request / station.bad_batch for null arguments and batch <= 0, and UNAVAILABLE while the module is not ready. cancelAsync and claimAsync return targetOffline() when the target player is offline, and cancelAsync additionally returns station.unknown_station for an unknown station

openGui can return station.open_bad_request, UNAVAILABLE, station.unknown_station, station.queue_not_loaded, station.missing_layout or FailureKind.WRONG_THREAD

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 ​

openGui touches the viewer and their inventory window immediately, so it 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. A call from any other thread returns FailureKind.WRONG_THREAD and never schedules 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