English
Public API
Compile-time dependency
xml
<repositories>
<repository><id>jiuwu-releases</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.28</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.28") }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 completes on either the calling thread or the final stage's execution domain — the completion thread is not fixed; schedule through scheduling() before a continuation touches Bukkit. Compilation and business failures complete normally as structured results rather than as 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
Animation entry points
CoreLib's animation timing infrastructure (definition registration, K-frame scheduling, priority pre-emption and conflict arbitration) is exposed through the same facade. Its contract types live in emaki.jiuwu.craft.corelib.api.animation: AnimationDefinition, AnimationKeyframe, AnimationPriority, AnimationConflictPolicy, AnimationPlaybackHandle, AnimationListener, and AnimationRegistration
registerAnimation(Plugin, AnimationDefinition)registers one owner-scoped definition and returns a closeableAnimationRegistration. Re-registering the same id under the same owner replaces the previous definition and stops future playbacks of the old oneplayAnimationAsync(Plugin, Entity, String, CoreActionExecutionContext)returns aCompletableFuture<EmakiResult<AnimationPlaybackHandle>>. It may be called from any thread and the future's completion thread is not fixed. An unknown definition, a disabled owner, or anAnimationConflictPolicy.REJECTconflict completes with a structured failure. A higher-priority playback pre-empts the current one; equal priorities follow the definition's own conflict policy. Keyframe action lines use the suppliedcontextas their execution input (the playing entity is added as caster when the context has none), and their Bukkit access happens on the entity's owner threadstopAnimation(Plugin, Entity, String)stops this owner's playback of one definition on that entity; anullor blankdefinitionIdstops all of this owner's playbacks on that entity. Returns whether a live playback was actually stoppedaddAnimationListener(Plugin, AnimationListener)registers a playback listener and returns a closeableAnimationRegistration; registering again for the same owner replaces the previous listener. Its callbacks (onPlay/onKeyframe/onStop) run on the entity's owner threadAnimationPlaybackHandlehas the three statesPLAYING / FINISHED / CANCELLED;playing()reports whether it is still advancing, andcancel()is idempotent and never throws after the playback has finished or been cancelled- Handles must be closed in
onDisable; when an owner plugin is disabled CoreLib revokes its definitions, listeners, and in-flight playbacks automatically - The animation definition registry survives a CoreLib reload, so no rebuild callback is required. This differs from the action stage table, which is rebuilt on reload and must be replayed through
onStageRegistryRebuilt/addStageRegistryRebuildListener(see above) - With CoreLib unavailable the registration and listener entry points return a stable inactive handle,
playAnimationAsynccompletes withEmakiResult.unavailable(), andstopAnimationreturnsfalse— none of them throw
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 distinguish "data not ready yet" from "that entry 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 - Re-registering from inside the callback to emulate a standing listener is forbidden: 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
moduleNameas a literal such as"EmakiItem"rather than a constant from that module's API jar, for the same class-loading reason documented onApiCapability.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 accessing 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:
| Phase | Meaning | What to do |
|---|---|---|
LOADING | The module started replacing its data | Invalidate caches now |
READY | Data is loaded and usable | Rebuild caches. Fires on first load and after every reload |
ABSENT | The module was disabled | Drop 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
onEnableruns 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. QueryisModuleReadyif 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 beforewhenReady'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
ABSENTphase 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 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