简体中文
API 与集成
第三方插件通过 emaki-dungeon-api (当前版本 1.0.1) 访问 EmakiDungeon. API Jar 只作编译依赖, 对应 runtime 产物为 EmakiDungeon-1.0.2.jar
Maven 坐标
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") }仅编译依赖
runtime 已内嵌未 relocate 的 API 类. 不要安装, bundle, shade 或 relocate API Jar; 重复 ClassLoader 类型会破坏事件与 bridge
EmakiDungeonApi 门面
所有访问从静态门面 emaki.jiuwu.craft.dungeon.api.EmakiDungeonApi 开始, 共五个领域门面, 访问器永不返回 null:
java
// 可用性与身份元数据; EmakiDungeon 缺席时返回 notInstalled
var status = EmakiDungeonApi.status();
var dungeons = EmakiDungeonApi.dungeonApi(); // 进入与实例查询
var party = EmakiDungeonApi.partyApi(); // 队伍
var instance = EmakiDungeonApi.instanceApi(); // 实例强制结束
var script = EmakiDungeonApi.scriptApi(); // 流程脚本与触发器
var editor = EmakiDungeonApi.editorApi(); // 打开编辑器install/uninstall与Bridge接口标记为内部/不可扩展, 第三方不要调用或实现- 在使用点解析访问器, 不要缓存到字段: reload 会替换底层 bridge
- 插件缺席时每个门面返回空实现, 调用得到
Result.err(ErrorCodes.NOT_INSTALLED, ...), 用status().usable()判断桥是否可用
五个领域门面
DungeonApi 进入与实例
| 方法 | 返回 | 说明 |
|---|---|---|
enter(Player, String dungeonId) | Result<Object> | 以程序化方式发起进入 (与 /edg enter 同链路, 同样的权限与限制判定) |
listInstances() | List<InstanceView> | 当前全部运行中实例的只读视图 |
getInstance(UUID instanceId) | Optional<InstanceView> | 指定实例视图, 不存在为空 |
PartyApi 队伍
| 方法 | 返回 | 说明 |
|---|---|---|
create(UUID player) | Result<PartyId> | 为玩家创建队伍 |
invite(UUID player, UUID target) | Result<InviteToken> | 发起邀请 (无队伍时按邀请模式提交加入) |
kick(UUID member) | Result<Void> | 踢出队员 (需队长身份语义由调用方保证) |
confirmReady(UUID player) | Result<Void> | 程序化确认准备, 等价 /edg ready |
InstanceApi 实例运维
| 方法 | 返回 | 说明 |
|---|---|---|
forceStop(UUID instanceId) | Result<Void> | 强制结束实例, 等价 /edg admin stop 的程序化入口 |
ScriptApi 流程脚本
| 方法 | 返回 | 说明 |
|---|---|---|
loadFlowScript(String dungeonId, String source) | Result<ScriptId> | 载入一段 GraalJS 流程脚本源码 |
registerTrigger(String triggerId, String event, ScriptTriggerHandler handler) | Result<Void> | 注册外部触发器处理器; triggerId 不可与已有触发器重复 |
EditorApi 编辑器
| 方法 | 返回 | 说明 |
|---|---|---|
openEditor(Player, String dungeonId) | Result<EditorSession> | 为玩家打开该副本的编辑会话; 模板单会话互斥等规则与命令路径一致, 调用方自行把好授权关 |
Result<T> 与错误码
所有可失败方法返回 Result<T> record, 组成为 code / messageKey / args / value:
java
Result<PartyId> result = EmakiDungeonApi.partyApi().create(player.getUniqueId());
if (result.isSuccess()) {
PartyId id = result.value(); // 失败时 value() 为 null
} else {
// result.code() 是稳定错误码, result.messageKey() 是可映射到自己语言文件的消息键
}ErrorCodes 常量按来源分组:
| 分组 | 错误码 |
|---|---|
| 进入限制 | 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_ALREADY_EXISTS, PARTY_FULL, INVITE_PENDING, TARGET_OFFLINE, MEMBER_NOT_FOUND, NOT_LEADER |
| 挑战与实例 | CHALLENGE_NOT_ACTIVE, ALREADY_READY, WINDOW_CLOSED, INSTANCE_NOT_FOUND, ALREADY_DISPOSING, DUNGEON_NOT_FOUND |
| 脚本 | ENGINE_UNAVAILABLE, SCRIPT_COMPILE_ERROR, TRIGGER_DUPLICATE, SCRIPT_NOT_LOADED |
| 桥缺席 | NOT_INSTALLED |
事件
公共事件位于 emaki.jiuwu.craft.dungeon.api.event 包. DungeonEvent 基类携带 getInstanceId() 与 getDungeonId(); DungeonChallengeStartEvent 与 DungeonChallengeCancelEvent 直接携带 dungeonId (挑战阶段还没有实例)
| 事件 | 触发时机 | 载荷 | 可取消 |
|---|---|---|---|
DungeonChallengeStartEvent | 挑战发起, 难度与分配模式选定, 进入准备窗口时 | dungeonId, windowSeconds, difficulty, dropMode | 是, 取消即中止挑战 |
DungeonChallengeCancelEvent | 挑战作废时 | dungeonId, reason | 否 |
DungeonEnterEvent | 玩家进入副本实例时 | player (UUID), partyId + 基类字段 | 否 |
DungeonExitEvent | 玩家离开副本实例时 | player (String), reason | 否 |
DungeonRoomClearEvent | 房间被清空时 | roomId | 否 |
DungeonGateOpenEvent | 门禁满足条件开启时 | gateId, condition | 否 |
DungeonChoiceEvent | 选项即将弹出选择提示时 | optionId, selectMode, 候选集 | 是, 取消则不发选择提示 |
DungeonOptionConfirmEvent | 选项定案后 | optionId, selected | 否 |
DungeonSealPathEvent | 落选候选项的封路区生效时 | optionId, sealedCases | 否 |
DungeonCompleteEvent | 副本通关时 | score, durationMs | 否 |
DungeonFailEvent | 副本失败时 | reason (枚举), detail | 否 |
DungeonEndingEvent | 结局落定进入结算时 | endingId, optionCase | 否 |
java
@EventHandler
public void onDungeonComplete(DungeonCompleteEvent event) {
// event.getInstanceId() / getDungeonId() 来自基类
log(event.getDungeonId() + " 通关: " + event.getScore() + " 分, 用时 "
+ event.getDurationMs() + "ms");
}- 除标注"是"的两个外, 其余事件没有取消通道, 只做信息监听
- 可取消的两个事件在各自流程点先派发再判定: 取消会直接改变插件后续行为, 适合做权限外的自定义拦截
线程与生命周期
- 触碰玩家背包, 世界或实体前, 自行确认当前线程符合 Bukkit/Paper 的 owner 线程要求; 无法确认时调度回主线程 (Folia 上对应 region 线程)
- EmakiDungeon 未安装时, 首次加载 API 类存在链接失败风险, 建议把相关调用隔离并捕获
LinkageError | RuntimeException - 事件在插件内部流程点同步派发, 监听器内不要长时间阻塞, 也不要在监听器里直接修改副本世界状态而不考虑流程推进