English
Getting Started
Requirements
| Item | Requirement |
|---|---|
| Java | 25 |
| Server | Paper/Folia, built against Paper API 26.1.2.build.74-stable |
| Build tool | Multi-module Gradle project (wrapper pins Gradle 9.8.0) |
| Required base module | EmakiCoreLib |
Build the runtime jars
Run this in the repository root (with JAVA_HOME pointing at a JDK 25 installation):
bash
./gradlew build -x testThe 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 plugin | runtime version | API artifact | API version |
|---|---|---|---|
EmakiCoreLib | 4.8.36 | emaki-corelib-api | 4.8.28 |
EmakiAttribute | 4.8.7 | emaki-attribute-api | 4.8.6 |
EmakiForge | 4.8.8 | emaki-forge-api | 4.8.6 |
EmakiStrengthen | 4.8.9 | emaki-strengthen-api | 4.8.7 |
EmakiCooking | 4.3.8 | emaki-cooking-api | 4.3.6 |
EmakiGem | 2.8.11 | emaki-gem-api | 2.8.8 |
EmakiLevel | 1.6.9 | emaki-level-api | 1.6.6 |
EmakiSkills | 2.8.8 | emaki-skills-api | 2.8.7 |
EmakiItem | 2.8.16 | emaki-item-api | 2.8.15 |
EmakiCodex | 1.1.6 | emaki-codex-api | 1.1.5 |
EmakiStorage | 1.1.5 | emaki-storage-api | 1.1.3 |
EmakiStation | 1.1.8 | emaki-station-api | 1.1.7 |
EmakiAccessory | 1.1.8 | emaki-accessory-api | 1.1.7 |
EmakiMobs | 1.0.13 | emaki-mobs-api | 1.0.9 |
EmakiStructure | 1.0.5 | emaki-structure-api | 1.0.1 |
EmakiDungeon | 1.0.2 | emaki-dungeon-api | 1.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 untilStackOverflowError - Pass
moduleNameas 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
isModuleReadyif 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
Recommended install order
- EmakiCoreLib — the base runtime every business module depends on
- EmakiAttribute — install it when you need attributes, resources, or combat integration
- Equipment line — EmakiItem, EmakiForge, EmakiStrengthen, EmakiGem
- Progression and gameplay line — EmakiLevel, EmakiSkills, EmakiCooking, EmakiCodex, EmakiStorage
- 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
- Put only the runtime jars you actually need into
plugins/ - Start the server and let the default configuration files generate
- Stop the server, or confirm no player is using the relevant systems, before editing
plugins/Emaki*/ - Restart and deal with the earliest load error in the console first
- Verify GUIs, commands, config reload, and external bridges module by module
- Third-party plugins may record each API's
status()at startup, but must not call the runtime-onlyinstall/uninstalllifecycle methods or implementBridge
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