Getting Started
Requirements
| Item | Requirement |
|---|---|
| Java | 25 |
| Server | Paper/Folia, built against Paper API 1.21.8 |
| Build tool | Maven multi-module project |
| Required base module | EmakiCoreLib |
Build the runtime jars
Run this in the repository root:
mvn -DskipTests packageThe default reactor contains 22 modules: all 14 API modules (CoreLibApi, ItemApi, StrengthenApi, SkillsApi, AttributeApi, ForgeApi, CookingApi, GemApi, LevelApi, CodexApi, StorageApi, StationApi, AccessoryApi, MobsApi) plus 8 runtimes (CoreLib, Forge, Strengthen, Cooking, Attribute, Level, Codex, Station). When a .key file exists in the repository root the private-modules profile activates automatically and adds 6 more runtimes: Skills, Gem, Item, Storage, Accessory, Mobs. You can also request it explicitly:
mvn -DskipTests -Pprivate-modules packageBuilt plugins land in Emaki*/target/. 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
<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>repositories { maven("https://repo.crypticlib.com/repository/maven-public/") }
dependencies { compileOnly("emaki.jiuwu.craft:emaki-corelib-api:4.8.1") }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 the current module POMs. The .key profile controls whether private runtime modules are built; it does not change API coordinates.
| Runtime plugin | API artifact | Version |
|---|---|---|
EmakiCoreLib | emaki-corelib-api | 4.8.1 |
EmakiAttribute | emaki-attribute-api | 4.7.11 |
EmakiForge | emaki-forge-api | 4.7.11 |
EmakiStrengthen | emaki-strengthen-api | 4.7.19 |
EmakiCooking | emaki-cooking-api | 4.2.9 |
EmakiGem | emaki-gem-api | 2.7.15 |
EmakiLevel | emaki-level-api | 1.5.8 |
EmakiSkills | emaki-skills-api | 2.7.11 |
EmakiItem | emaki-item-api | 2.7.16 |
EmakiCodex | emaki-codex-api | 1.0.5 |
EmakiStorage | emaki-storage-api | 1.0.6 |
EmakiStation | emaki-station-api | 1.0.8 |
EmakiAccessory | emaki-accessory-api | 1.0.3 |
EmakiMobs | emaki-mobs-api | 1.0.6 |
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
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 your onEnable runs after a target module's first data load. Rather than querying another module's table directly from your own onEnable, hook a readiness callback:
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
A plugin that caches another module's content has to invalidate and rebuild when that module reloads. whenReady fires only once, so use addModuleListener:
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:
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
EquipmentSkillPdcCodec lives in the emaki.jiuwu.craft.skills.api.pdc package of emaki-skills-api. It is a standalone low-level PDC protocol helper 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.