English
API
Third-party plugins talk to EmakiDungeon through emaki-dungeon-api (current version 1.0.1). The API jar is a compile-time dependency only; the matching runtime artifact is EmakiDungeon-1.0.2.jar
Maven coordinates
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-dungeon-api</artifactId>
<version>1.0.1</version>
<scope>provided</scope>
</dependency>kotlin
repositories { maven("https://repo.crypticlib.com/repository/maven-public/") }
dependencies { compileOnly("emaki.jiuwu.craft:emaki-dungeon-api:1.0.1") }Compile-time only
The runtime embeds the unrelocated API classes. Do not install, bundle, shade, or relocate the API jar; duplicate ClassLoader types break events and the bridge
The EmakiDungeonApi facade
Everything starts at the static facade emaki.jiuwu.craft.dungeon.api.EmakiDungeonApi with five domain facades, and accessors never return null:
java
// availability and identity metadata; notInstalled while EmakiDungeon is absent
var status = EmakiDungeonApi.status();
var dungeons = EmakiDungeonApi.dungeonApi(); // entry and instance queries
var party = EmakiDungeonApi.partyApi(); // party
var instance = EmakiDungeonApi.instanceApi(); // force stop
var script = EmakiDungeonApi.scriptApi(); // flow scripts and triggers
var editor = EmakiDungeonApi.editorApi(); // open the editorinstall/uninstalland theBridgeinterface are internal and non-extendable; never call or implement them- Resolve accessors at the point of use, never cache them in a field: reload swaps the backing bridge
- With the plugin absent every facade returns a no-op implementation answering
Result.err(ErrorCodes.NOT_INSTALLED, ...); usestatus().usable()to check whether the bridge is live
The five domain facades
DungeonApi entry and instances
| Method | Returns | Description |
|---|---|---|
enter(Player, String dungeonId) | Result<Object> | Programmatic entry (same chain as /edg enter, including the same permission and limit checks) |
listInstances() | List<InstanceView> | Read-only views of all running instances |
getInstance(UUID instanceId) | Optional<InstanceView> | View of one instance, empty when absent |
PartyApi
| Method | Returns | Description |
|---|---|---|
create(UUID player) | Result<PartyId> | Create a party for the player |
invite(UUID player, UUID target) | Result<InviteToken> | Send an invite (without a party this asks to join, handled by the invite mode) |
kick(UUID member) | Result<Void> | Remove a member (the caller upholds leader semantics) |
confirmReady(UUID player) | Result<Void> | Programmatic ready confirmation, equivalent to /edg ready |
InstanceApi operations
| Method | Returns | Description |
|---|---|---|
forceStop(UUID instanceId) | Result<Void> | Force-stop an instance, the programmatic form of /edg admin stop |
ScriptApi
| Method | Returns | Description |
|---|---|---|
loadFlowScript(String dungeonId, String source) | Result<ScriptId> | Load a GraalJS flow script source |
registerTrigger(String triggerId, String event, ScriptTriggerHandler handler) | Result<Void> | Register an external trigger handler; triggerId must not collide with an existing trigger |
EditorApi
| Method | Returns | Description |
|---|---|---|
openEditor(Player, String dungeonId) | Result<EditorSession> | Open an editing session for the player; session exclusivity matches the command path, and the caller owns authorization |
Result<T> and error codes
Every fallible method returns the Result<T> record composed of code / messageKey / args / value:
java
Result<PartyId> result = EmakiDungeonApi.partyApi().create(player.getUniqueId());
if (result.isSuccess()) {
PartyId id = result.value(); // value() is null on failure
} else {
// result.code() is a stable error code, result.messageKey() maps into your own lang files
}ErrorCodes constants grouped by origin:
| Group | Codes |
|---|---|
| Entry denial | DENY_COOLDOWN, DENY_DAILY_LIMIT, DENY_WEEKLY_LIMIT, DENY_MONTHLY_LIMIT, DENY_WINDOW, DENY_GLOBAL_FULL, DENY_DUNGEON_FULL, DENY_PARTY_INVALID, DENY_NOT_LEADER, DENY_PERMISSION |
| Party | PARTY_ALREADY_EXISTS, PARTY_FULL, INVITE_PENDING, TARGET_OFFLINE, MEMBER_NOT_FOUND, NOT_LEADER |
| Challenge and instance | CHALLENGE_NOT_ACTIVE, ALREADY_READY, WINDOW_CLOSED, INSTANCE_NOT_FOUND, ALREADY_DISPOSING, DUNGEON_NOT_FOUND |
| Script | ENGINE_UNAVAILABLE, SCRIPT_COMPILE_ERROR, TRIGGER_DUPLICATE, SCRIPT_NOT_LOADED |
| Bridge absent | NOT_INSTALLED |
Events
Public events live in emaki.jiuwu.craft.dungeon.api.event. The DungeonEvent base carries getInstanceId() and getDungeonId(); DungeonChallengeStartEvent and DungeonChallengeCancelEvent carry only dungeonId (no instance exists yet at that point)
| Event | Fires when | Payload | Cancellable |
|---|---|---|---|
DungeonChallengeStartEvent | A challenge starts, difficulty and drop mode are picked, the ready window opens | dungeonId, windowSeconds, difficulty, dropMode | Yes - cancelling aborts the challenge |
DungeonChallengeCancelEvent | A challenge is voided | dungeonId, reason | No |
DungeonEnterEvent | A player enters the dungeon instance | player (UUID), partyId + base fields | No |
DungeonExitEvent | A player leaves the dungeon instance | player (String), reason | No |
DungeonRoomClearEvent | A room is cleared | roomId | No |
DungeonGateOpenEvent | A gate opens once its conditions hold | gateId, condition | No |
DungeonChoiceEvent | An option is about to show its selection prompt | optionId, selectMode, candidate set | Yes - cancelling suppresses the prompt |
DungeonOptionConfirmEvent | A choice is locked in | optionId, selected | No |
DungeonSealPathEvent | A losing candidate's seal area takes effect | optionId, sealedCases | No |
DungeonCompleteEvent | The dungeon is cleared | score, durationMs | No |
DungeonFailEvent | The dungeon run fails | reason (enum), detail | No |
DungeonEndingEvent | The ending is settled and payout begins | endingId, optionCase | No |
java
@EventHandler
public void onDungeonComplete(DungeonCompleteEvent event) {
// event.getInstanceId() / getDungeonId() come from the base class
log(event.getDungeonId() + " cleared: " + event.getScore() + " points in "
+ event.getDurationMs() + "ms");
}- All events except the two marked "Yes" have no cancellation channel and are informational only
- The two cancellable events are dispatched before the plugin proceeds: cancelling changes what the plugin does next, which makes them suitable for custom gating beyond permissions
Threading and lifecycle
- Before touching player inventories, worlds, or entities, make sure you are on the owner thread required by Bukkit/Paper; when you cannot confirm it, schedule back to the main thread (the corresponding region thread on Folia)
- When EmakiDungeon is absent, first loading the API classes carries a linkage-failure risk; isolate such calls and catch
LinkageError | RuntimeException - Events are dispatched synchronously at internal flow points; do not block inside a listener and do not mutate dungeon world state there without considering flow progression