API and Integration
Third-party plugins reach EmakiStation through emaki-station-api.
Maven Coordinates
<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:
dependencies:
server:
EmakiStation:
load: BEFORE
required: false
join-classpath: trueFacade
emaki.jiuwu.craft.station.api.EmakiStationApi is the static facade:
| Method | Description |
|---|---|
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:
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:
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.
| Method | Description |
|---|---|
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.
| Method | Thread | Description |
|---|---|---|
CompletableFuture<EmakiResult<SubmitOutcome>> submitAsync(UUID playerId, String stationId, String recipeId, long batch, MaterialChannel channel) | Any thread; requires the target player online | Submit a craft. batch must be positive. |
CompletableFuture<EmakiResult<Unit>> cancelAsync(UUID playerId, String stationId, int index) | Any thread; requires the target player online | Cancel one queue entry; index is zero-based. |
CompletableFuture<EmakiResult<Integer>> claimAsync(UUID playerId) | Any thread; requires the target player online | Claim 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 thread | Open 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:
| Type | Description |
|---|---|
StationView | Station view. |
RecipeView | Recipe view. |
MaterialRequirementView | Material requirement view. |
QueueSnapshot | Queue snapshot. |
QueueEntryView | Queue entry view. |
QueueEntryState | Entry state: WAITING, RUNNING, PENDING_CLAIM. |
ConsumedMaterial | Record of a consumed material. |
PendingOutput | An output awaiting delivery. |
SubmitOutcome | Submission result payload. |
MaterialChannel | Material channel enum; ignored by submitAsync. |
OutputRouting | Output routing. |
ProgressMode | Progression mode. |