Skip to content

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 editor
  • install / uninstall and the Bridge interface 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, ...); use status().usable() to check whether the bridge is live

The five domain facades ​

DungeonApi entry and instances ​

MethodReturnsDescription
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 ​

MethodReturnsDescription
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 ​

MethodReturnsDescription
forceStop(UUID instanceId)Result<Void>Force-stop an instance, the programmatic form of /edg admin stop

ScriptApi ​

MethodReturnsDescription
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 ​

MethodReturnsDescription
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:

GroupCodes
Entry denialDENY_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
PartyPARTY_ALREADY_EXISTS, PARTY_FULL, INVITE_PENDING, TARGET_OFFLINE, MEMBER_NOT_FOUND, NOT_LEADER
Challenge and instanceCHALLENGE_NOT_ACTIVE, ALREADY_READY, WINDOW_CLOSED, INSTANCE_NOT_FOUND, ALREADY_DISPOSING, DUNGEON_NOT_FOUND
ScriptENGINE_UNAVAILABLE, SCRIPT_COMPILE_ERROR, TRIGGER_DUPLICATE, SCRIPT_NOT_LOADED
Bridge absentNOT_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)

EventFires whenPayloadCancellable
DungeonChallengeStartEventA challenge starts, difficulty and drop mode are picked, the ready window opensdungeonId, windowSeconds, difficulty, dropModeYes - cancelling aborts the challenge
DungeonChallengeCancelEventA challenge is voideddungeonId, reasonNo
DungeonEnterEventA player enters the dungeon instanceplayer (UUID), partyId + base fieldsNo
DungeonExitEventA player leaves the dungeon instanceplayer (String), reasonNo
DungeonRoomClearEventA room is clearedroomIdNo
DungeonGateOpenEventA gate opens once its conditions holdgateId, conditionNo
DungeonChoiceEventAn option is about to show its selection promptoptionId, selectMode, candidate setYes - cancelling suppresses the prompt
DungeonOptionConfirmEventA choice is locked inoptionId, selectedNo
DungeonSealPathEventA losing candidate's seal area takes effectoptionId, sealedCasesNo
DungeonCompleteEventThe dungeon is clearedscore, durationMsNo
DungeonFailEventThe dungeon run failsreason (enum), detailNo
DungeonEndingEventThe ending is settled and payout beginsendingId, optionCaseNo
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