Skip to content

Public API

Compile-time dependency

xml
<repositories>
  <repository><id>emaki-public</id><url>https://repo.crypticlib.com/repository/maven-public/</url></repository>
</repositories>
<dependency>
  <groupId>emaki.jiuwu.craft</groupId><artifactId>emaki-corelib-api</artifactId><version>4.8.1</version><scope>provided</scope>
</dependency>
kotlin
repositories { maven("https://repo.crypticlib.com/repository/maven-public/") }
dependencies { compileOnly("emaki.jiuwu.craft:emaki-corelib-api:4.8.1") }

Compile only — never install, shade, or relocate this jar

The runtime jar already embeds the same un-relocated API classes. A second copy gives Bukkit events and static bridges different class identities.

Narrow third-party facade

Use EmakiCoreLibApi.status().usable() before relying on a bridge. This facade is intentionally narrow for third-party plugins; Emaki runtime modules link the CoreLib implementation directly. dialogs() exposes vanilla dialogs, scheduling() exposes Folia-safe scheduling, and implementation services such as rendering, YAML, GUI internals, expressions, lifecycle, economy, and PDC internals remain hidden.

The facade also exposes both itemDisplayName overloads, configured-item build/patch overloads, item component capabilities, and owner-scoped stage registration through registerActionStage, registerActionSource, and registerActionGate. There is no source dimension and no unregister-by-id method: each call returns a closeable CoreStageRegistration, and a stage is revoked through that handle or automatically when its owner is disabled. install, uninstall, and Bridge are internal runtime contracts: do not call or implement them.

executeActionLineAsync(Plugin, String, CoreActionExecutionContext) is callable from any thread. Its future may complete on the calling thread or in the final stage's execution domain; schedule through scheduling() before a continuation touches Bukkit. Compilation and business failures complete normally as structured results rather than exceptional completion.

The result is CoreActionExecutionResult, with status, reasonKey/reasonArguments, diagnostics, ordered stages, and keptTargets. Statuses are SUCCESS, SKIPPED, PARTIAL, COMPILE_FAILED, EXECUTION_FAILED, INVALID_REQUEST, and UNAVAILABLE.

Read-only registry queries are actionStages() / actionStage(id) and actionTriggers() / actionTrigger(id). onStageRegistryRebuilt(owner, callback) keeps its compatibility replacement semantics: a later callback for the same owner replaces the earlier one. Use addStageRegistryRebuildListener(owner, callback) for independent callbacks from one plugin; callbacks are appended independently and return a closeable CoreStageRebuildRegistration.

Readiness contract

Every Emaki module publishes its state to CoreLib when its own data finishes loading, when it enters a reload, and after it shuts down. Consumers use this to tell "the data is not loaded yet" apart from "that entry genuinely does not exist".

whenReady(Plugin owner, String moduleName, Runnable callback) runs the callback once the target module's data is loaded and returns a closeable ReadinessRegistration:

  • The callback runs once. A reloading module goes back to not-ready and becomes ready again, but an already-fired callback is not replayed. To follow every reload use addModuleListener (see below), or simply re-check at the point of use.
  • Do not re-register from inside the callback to emulate a standing listener: the module is already marked ready when callbacks run, so the re-registration takes the already-ready path, fires synchronously, registers again, and recurses until StackOverflowError.
  • Registering the same owner twice adds a second callback rather than replacing the first.
  • Pass moduleName as a literal such as "EmakiItem" rather than a constant from that module's API jar, for the same class-loading reason documented on ApiCapability.of(String). Matching is case-insensitive.
  • If the module is already ready the callback runs synchronously and the returned handle is inactive. An inactive handle is also returned when EmakiCoreLib is unavailable or the arguments are unusable.
  • Thread: callable from any thread. The callback runs on whichever thread marked the module ready, which is not guaranteed to be a Bukkit owner thread. Schedule explicitly before touching players, inventories, worlds, or GUIs.
  • Callbacks registered by an owner plugin are dropped when that plugin is disabled.

isModuleReady(String moduleName) is the polling counterpart, meant for diagnostics or a call site that can simply skip its work. Prefer whenReady when the work must run exactly once after readiness.

Listening for reloads

whenReady answers "has it loaded yet" but not "has it reloaded since". A consumer that caches another module's content needs the latter:

java
EmakiCoreLibApi.addModuleListener(this, "EmakiItem", phase -> {
    switch (phase) {
        case LOADING, ABSENT -> myCache.invalidate();
        case READY -> myCache.rebuild();
    }
});

The three ModuleReadinessPhase values:

PhaseMeaningWhat to do
LOADINGThe module started replacing its dataInvalidate caches now
READYData is loaded and usableRebuild caches. Fires on first load and after every reload
ABSENTThe module was disabledDrop caches. It may be enabled again in the same session, followed by LOADING then READY

How it differs from whenReady:

  • Standing: notified on every transition until the handle is closed.
  • Registering the same owner for the same module again replaces the previous listener rather than adding a second one, so a plugin whose onEnable runs twice does not rebuild its cache twice.
  • Registering while the module is already ready does not invoke the listener immediately. That immediate call exists to close whenReady's missed-signal window; a standing listener has none. Query isModuleReady if you need the state at registration time.
  • Republishing the same state notifies nobody: phases fire only on a real transition. Several modules publish ready from both their synchronous and asynchronous reload paths, and the de-duplication lives inside CoreLib.
  • Within one READY, standing listeners run before whenReady's one-shot callbacks, so a consumer holding both has its one-off initialisation read an already-rebuilt cache.
  • Thread: same as whenReady — the listener runs on whichever thread published the transition, not guaranteed to be a Bukkit owner thread.
  • Listeners are dropped when their owner plugin is disabled. The ABSENT phase does not drop them, because a consumer has no way to notice it would need to re-register.

Use this when you only need to know that a reload happened. CoreLib exposes no way for a third party to trigger another module's reload.

There is a window between LOADING and the data actually being replaced: modules that reload asynchronously (Item, Attribute, Cooking, Gem, Skills, Strengthen) may briefly still return the old data after LOADING. That is existing timing behaviour, not a guarantee.

A module's status().ready() now means "data is loaded", not "components were constructed". During a reload window status() reports loading and methods returning EmakiResult return unavailable(). Plain queries returning Set, List, or boolean have no way to express unavailability, so during a reload they may return stale or empty content: do not read an empty result as "no such entry".

Result contract

EmakiResult.Success<T> carries a complete non-null value. Partial<T> carries the achieved value and a stable shortfall key. Failure<T> carries no value, a FailureKind, a stable reason key, and immutable placeholders. Use optionalValue() for a value from either Success or Partial; orElse, failureKind, reasonKey, and reasonPlaceholders preserve the same distinction. Void-style operations return EmakiResult<Unit> (EmakiResult.ok() carries Unit.INSTANCE).

FailureKind is exactly: UNAVAILABLE, NOT_FOUND, INVALID_INPUT, REJECTED, CANCELLED, TARGET_OFFLINE, WRONG_THREAD, and INTERNAL_ERROR. REJECTED includes configuration-disabled business features; there is no separate disabled kind.