Skip to content

Getting Started ​

Requirements ​

ItemRequirement
Java25
ServerPaper/Folia, built against Paper API 26.1.2.build.74-stable
Build toolMulti-module Gradle project (wrapper pins Gradle 9.8.0)
Required base moduleEmakiCoreLib

Build the runtime jars ​

Run this in the repository root (with JAVA_HOME pointing at a JDK 25 installation):

bash
./gradlew build -x test

The default build contains 27 modules: all 16 API modules (CoreLibApi, ItemApi, StrengthenApi, SkillsApi, AttributeApi, ForgeApi, CookingApi, GemApi, LevelApi, CodexApi, StorageApi, StationApi, AccessoryApi, MobsApi, StructureApi, DungeonApi) plus 11 runtimes (CoreLib, Forge, Strengthen, Cooking, Attribute, Level, Codex, Station, Mobs, Dungeon, Dummy). When a .key file exists in the repository root, settings.gradle adds 6 more private runtimes: Skills, Gem, Item, Storage, Structure, Accessory

Built plugins land in each module's Emaki*/build/libs/. Copy only the runtime jars such as EmakiCoreLib-*.jar and EmakiForge-*.jar into the server's plugins/ directory

Never install or bundle an API jar

An API jar must stay a Maven provided / Gradle compileOnly dependency. Every runtime jar already embeds its own API classes without relocation, so a second copy in plugins/ — or one shaded into your plugin — is loaded by a different ClassLoader and becomes a different type, which breaks at link time rather than at compile time

Compile against an API artifact ​

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") }

Replace the artifact and version with the domain API you use

Runtime jars only

Never put an API jar in plugins/, and never bundle, shade, or relocate one. Each runtime jar already embeds its own un-relocated API classes; a duplicate classloader identity breaks Bukkit event delivery and static bridge contracts

Current runtime/API pairs ​

The following versions come from each module's current build.gradle. Runtime and API versions are maintained independently, so the two versions of one pair are not always equal. The .key gate controls whether private runtime modules are built; it does not change API coordinates

Runtime pluginruntime versionAPI artifactAPI version
EmakiCoreLib4.8.36emaki-corelib-api4.8.28
EmakiAttribute4.8.7emaki-attribute-api4.8.6
EmakiForge4.8.8emaki-forge-api4.8.6
EmakiStrengthen4.8.9emaki-strengthen-api4.8.7
EmakiCooking4.3.8emaki-cooking-api4.3.6
EmakiGem2.8.11emaki-gem-api2.8.8
EmakiLevel1.6.9emaki-level-api1.6.6
EmakiSkills2.8.8emaki-skills-api2.8.7
EmakiItem2.8.16emaki-item-api2.8.15
EmakiCodex1.1.6emaki-codex-api1.1.5
EmakiStorage1.1.5emaki-storage-api1.1.3
EmakiStation1.1.8emaki-station-api1.1.7
EmakiAccessory1.1.8emaki-accessory-api1.1.7
EmakiMobs1.0.13emaki-mobs-api1.0.9
EmakiStructure1.0.5emaki-structure-api1.0.1
EmakiDungeon1.0.2emaki-dungeon-api1.0.1

Install only matching runtime jars. Check each facade with status().usable() before relying on its bridge. install, uninstall, and Bridge are runtime lifecycle contracts and must not be called or implemented by integrations

Result handling ​

java
if (result.isFailure()) {
    handleFailure(result.failureKind(), result.reasonKey());
} else {
    result.optionalValue().ifPresent(value -> handleValue(value, result.isPartial()));
}

Success is complete, Partial carries achieved work plus a shortfall key, and Failure has no payload. No-payload operations use EmakiResult<Unit>. Failure kinds are UNAVAILABLE, NOT_FOUND, INVALID_INPUT, REJECTED, CANCELLED, TARGET_OFFLINE, WRONG_THREAD, and INTERNAL_ERROR; configuration-disabled business features use REJECTED

Waiting for a module to become ready ​

Module load order does not guarantee that your onEnable runs after a target module's first data load. So instead of querying another module's table from your own onEnable, hook a readiness callback:

java
EmakiCoreLibApi.whenReady(this, "EmakiItem", () -> {
    // EmakiItem definitions are loaded at this point
    buildMyIndex();
});
  • The callback runs once. A reloading target 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); do not re-register from inside the callback — that recurses until StackOverflowError
  • Pass moduleName as a literal such as "EmakiItem" rather than referencing a constant from that module's API jar
  • The return value is a closeable ReadinessRegistration. If the module is already ready the callback runs synchronously and an inactive handle is returned
  • The callback runs on whichever thread marked the module ready, which is not guaranteed to be a Bukkit owner thread. Use EmakiCoreLibApi.scheduling() before touching players, inventories, worlds, or GUIs
  • When you only need "skip if not ready", poll EmakiCoreLibApi.isModuleReady(name) instead

Inside a reload window the target's status() reports loading and EmakiResult-returning methods return unavailable(). Plain queries returning Set, List, or boolean cannot express unavailability and may return stale or empty content. Do not read an empty result as "that entry does not exist"

Listening for module reloads ​

If your plugin caches another module's content, invalidate and rebuild that cache whenever the module reloads. whenReady fires only once, so use addModuleListener:

java
EmakiCoreLibApi.addModuleListener(this, "EmakiItem", phase -> {
    switch (phase) {
        case LOADING, ABSENT -> myCache.invalidate();
        case READY -> myCache.rebuild();
    }
});
  • Three phases: LOADING (data is being replaced, invalidate), READY (data usable, rebuild; fires on first load and every reload), ABSENT (module disabled)
  • Standing until the handle is closed. Registering the same owner for the same module again replaces rather than adds
  • Registering while the module is already ready does not invoke the listener immediately; query isModuleReady if you need the current state
  • Same threading rule as whenReady: the listener runs on the publishing thread, not guaranteed to be an owner thread
  • This only reports that a reload happened. There is no API for a third party to trigger another module's reload

See docs/en/modules/corelib/api.md for the full phase table and timing boundaries

Threading ​

On Folia, dispatch entity, location, or global work to its owner before touching Bukkit state. Storage asynchronous methods and Skills casts may be submitted from any thread, but their future callbacks do not grant Bukkit ownership. LevelCatalog.loadPlayerDataAsync also leaves its completion thread unspecified. Use CoreLib's EmakiScheduling facade for explicit owner scheduling

Declare runtime dependencies ​

Compiling against an API artifact does not make the server load the matching runtime. Your paper-plugin.yml still has to declare the plugin dependencies you need: use a hard dependency when a feature cannot work without it, and a soft dependency plus a runtime probe when the integration is optional:

java
if (!EmakiForgeApi.status().usable()) {
    // Hide the Forge integration; never let an optional module take the whole plugin down
    return;
}

While a bridge is uninstalled or during a reload window, an API facade returns a non-null degraded implementation: catalogs are usually empty and state-changing calls return an EmakiResult unavailable failure. When the soft-depended runtime is genuinely absent, even the first load of an API class can raise a linkage error, so keep optional integrations in separate classes and catch LinkageError | RuntimeException at the boundary

  1. EmakiCoreLib — the base runtime every business module depends on
  2. EmakiAttribute — install it when you need attributes, resources, or combat integration
  3. Equipment line — EmakiItem, EmakiForge, EmakiStrengthen, EmakiGem
  4. Progression and gameplay line — EmakiLevel, EmakiSkills, EmakiCooking, EmakiCodex, EmakiStorage
  5. Add soft dependencies as your module configuration requires: PlaceholderAPI, Vault/ExcellentEconomy, MythicMobs, CraftEngine, ItemsAdder, and similar

The authoritative load order is each module's own paper-plugin.yml. CoreLib is a hard dependency for every business module; when a soft dependency is missing, only the matching bridge capability should switch off

First-startup checklist ​

  1. Put only the runtime jars you actually need into plugins/
  2. Start the server and let the default configuration files generate
  3. Stop the server, or confirm no player is using the relevant systems, before editing plugins/Emaki*/
  4. Restart and deal with the earliest load error in the console first
  5. Verify GUIs, commands, config reload, and external bridges module by module
  6. Third-party plugins may record each API's status() at startup, but must not call the runtime-only install / uninstall lifecycle methods or implement Bridge

Equipment skill PDC contract ​

Do not read EquipmentSkillPdcCodec as a readiness signal. It is a standalone low-level PDC protocol helper in the emaki.jiuwu.craft.skills.api.pdc package of emaki-skills-api and does not imply that the EmakiSkills runtime is ready. The internal embed/relocate strategy used by Item, Forge, Gem, and Strengthen also does not change the rule that third-party plugins must depend on the API with compileOnly