简体中文
API 与集成
第三方插件通过 emaki-mobs-api (当前版本 1.0.9) 访问 EmakiMobs 的生物定义. API Jar 只作编译依赖, 对应 runtime 产物为 EmakiMobs-1.0.13.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-mobs-api</artifactId>
<version>1.0.9</version>
<scope>provided</scope>
</dependency>kotlin
repositories { maven("https://repo.crypticlib.com/repository/maven-public/") }
dependencies { compileOnly("emaki.jiuwu.craft:emaki-mobs-api:1.0.9") }仅编译依赖
runtime 已内嵌未 relocate 的 API 类. 不要安装, bundle, shade 或 relocate API Jar; 重复 ClassLoader 类型会破坏事件与 bridge
在 paper-plugin.yml 里把 EmakiMobs 声明为依赖:
yaml
dependencies:
server:
EmakiMobs:
load: BEFORE
required: false
join-classpath: true门面
emaki.jiuwu.craft.mobs.api.EmakiMobsApi 是静态门面:
| 方法 | 说明 |
|---|---|
status() | 可用性与身份元数据, 返回 CoreLib 的 ApiStatus |
catalog() | 查询层, 见下文 MobCatalog |
operations() | 操作层, 见下文 MobOperations |
extensions() | 扩展层, 见下文 MobExtensions |
访问器永不返回 null: EmakiMobs 缺失时 catalog 返回空答案, 因此调用方不得以 NullPointerException 作为可用性信号
MobCatalog 查询层
标注 @ApiStatus.NonExtendable
| 方法 | 线程 | 返回类型 | 说明 |
|---|---|---|---|
definition(String mobId) | 任意线程 | Optional<MobDefinition> | 按 id 查询生物定义 |
registeredIds() | 任意线程 | Set<String> | 列出所有已注册生物 id (不可变快照) |
identify(LivingEntity entity) | 实体 owner 线程 | Optional<String> | 读取实体 PDC, 返回该实体当前挂载的受管生物 id; 实体为 null, 非受管, EmakiMobs 未就绪或其定义已不再加载时返回空 |
definition 与 registeredIds 返回不可变快照, 线程契约为任意线程可调用; identify 读取存活实体状态, 线程契约为只能在实体 owner 线程调用
MobOperations 操作层
| 方法 | 返回类型 | 说明 |
|---|---|---|
spawn(Location location, String mobId) | Optional<LivingEntity> | 在指定位置生成一只已注册的自定义生物. location 的世界不可为 null, mobId 区分大小写; id 未注册或生成失败时返回空 Optional |
refresh(LivingEntity entity) | boolean | 用非破坏性刷新规则刷新一只受管实体. 该同步方法会读写存活实体状态, 必须在实体 owner 线程调用, 且不会代调用方调度工作; 实体为 null, 无效, 已死亡, 非受管, id 未知或不可用时返回 false |
remove(LivingEntity entity) | void | 移除一只受管实体. 接口 default 实现为空操作, 真实实现由运行时桥提供 |
EmakiMobs 缺失或尚未就绪时, operations() 返回的是安全的空操作实现, spawn 恒返回 Optional.empty(), refresh 恒返回 false
生成实体属于 Bukkit 状态改动: 在 Folia 上必须先切换到该位置的 owner 线程再调用; 在 Paper 上该位置的 owner 线程即主线程
MobExtensions 扩展层
| 方法 | 说明 |
|---|---|
registerCustomSpawner(Plugin owner, String id, CustomSpawner spawner) | 注册自定义刷怪器, 返回可关闭的 MobSpawnerRegistration 句柄; id 用于去重. owner 是拥有该注册的插件, 可为 null, 但为 null 时无法在该插件禁用时自动清理 |
unregisterCustomSpawners(Plugin owner) | 移除该 owner 注册的全部自定义刷怪器 |
MobExtensions.CustomSpawner 是定义在 MobExtensions 内部的函数式接口, 只有一个 onReload() 回调: 注册时立即调用一次, 之后每次 EmakiMobs 配置重载再调用一次. 在其中读取最新配置并重新排程自己的刷怪任务
java
MobSpawnerRegistration handle = EmakiMobsApi.extensions()
.registerCustomSpawner(this, "my_spawner", () -> {
// 注册时与每次 reload 后都会进入这里
reloadMySpawnConfig();
});
// 不再需要时关闭句柄以撤销注册
handle.close();MobDefinition 模型
**暴露字段: **
| 字段 | 类型 | 说明 |
|---|---|---|
id() | String | YAML 文件与命令中使用的生物唯一 ID |
entityType() | EntityType | 底层原版实体类型 |
displayName() | @Nullable String | MiniMessage 格式的自定义名; 未设置时为 null |
experience() | int | 击杀经验覆盖值; 0 表示沿用原版默认 |
**不暴露的内部实现: ** components, ea_attributes, actions, threat, boss_bar 均不在公共 API 中. 这些字段属于内部配置, 第三方插件无法查询
MobActionKeys 动作上下文键
emaki.jiuwu.craft.mobs.api.MobActionKeys 提供 4 个 CoreActionKey<LivingEntity> 常量, 键名带 emakimobs: 命名空间:
| 常量 | 键 | 写入该上下文对象的触发器 |
|---|---|---|
ATTACKER | emakimobs:attacker | on_damage_take (攻击者为生物实体时) |
KILLER | emakimobs:killer | on_death (存在存活击杀者时) |
VICTIM | emakimobs:victim | on_kill |
TARGET | emakimobs:target | on_target, on_damage_give (目标为生物实体时) |
这些键只在对应触发器的动作上下文里存在; 触发器与上下文的对应关系见技能触发器
不要缓存层对象
在使用点解析 catalog(), 不要把它存进字段: backing bridge 在 reload 时会被替换
java
// 正确
EmakiMobsApi.catalog().definition("elite_zombie");
// 错误: reload 后这个引用会指向旧 bridge
private final MobCatalog cached = EmakiMobsApi.catalog();示例
java
import emaki.jiuwu.craft.mobs.api.EmakiMobsApi;
import emaki.jiuwu.craft.mobs.api.MobCatalog;
import emaki.jiuwu.craft.mobs.api.model.MobDefinition;
import org.bukkit.entity.EntityType;
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
if (!EmakiMobsApi.status().usable()) {
getLogger().warning("EmakiMobs API 不可用");
return;
}
MobCatalog catalog = EmakiMobsApi.catalog();
// 查询单个生物定义
catalog.definition("elite_zombie").ifPresent(mob -> {
getLogger().info("找到生物:" + mob.displayName()
+ "(类型:" + mob.entityType()
+ ",经验:" + mob.experience() + ")");
});
// 列出所有已注册生物
getLogger().info("已注册 " + catalog.registeredIds().size() + " 种生物");
for (String mobId : catalog.registeredIds()) {
catalog.definition(mobId).ifPresent(mob -> {
if (mob.entityType() == EntityType.ZOMBIE) {
getLogger().info(" - " + mobId + " (僵尸变种)");
}
});
}
}
}事件
emaki.jiuwu.craft.mobs.api.event 包暴露三个受管生物生命周期事件. 三者都是标准 Bukkit 事件, 用 @EventHandler 监听; 每个事件类都自带静态 getHandlerList()
| 事件 | 可取消 | 主要方法 |
|---|---|---|
EmakiMobSpawnEvent | 是 | getEntity(), getMobId(), isCancelled() / setCancelled(boolean) |
EmakiMobDeathEvent | 否 | getEntity(), getMobId(), getKiller() |
EmakiMobSkillTriggerEvent | 是 | getMob(), getMobId(), getTrigger(), isCancelled() / setCancelled(boolean) |
三个事件都在同步线程上触发, 运行于触发点所属实体的 owner 线程: Paper 上就是主线程, Folia 上是对应区域线程. 不要在回调中执行阻塞操作
EmakiMobSpawnEvent
在 EmakiMobs 的 MobFactory 创建, 标记并同步配置完一只受管生物之后触发; 此时 boss bar, 延迟血量补足与 on_spawn 动作管道都尚未注册或执行
getEntity(): 新建并完成配置的受管实体getMobId(): 已加载的生物定义 id- 取消该事件会移除刚创建的实体, 并使生成操作返回空
Optional - 只覆盖经 EmakiMobs 生物工厂创建的实体; 之后被类型覆盖规则标记的原版实体不会触发
EmakiMobDeathEvent
在 EmakiMobs 为一只受管生物的死亡完成经验与掉落处理后触发
- 该事件仅用于观察, 不可取消, 无法改变死亡结果
getEntity(): 死亡的受管实体;getMobId(): 已加载的定义 id;getKiller(): Bukkit 记录的击杀者, 无击杀者时为null- 仅当死亡实体仍持有受管生物 id 且其定义仍处于加载状态时触发; PDC 中的 id 已失效或实体未持有时不触发
EmakiMobSkillTriggerEvent
在一只受管生物触发解析出至少一条动作管道之后, 任何已解析管道执行之前触发
getMob(): 执行该触发器的受管实体;getMobId(): 已加载的定义 id;getTrigger(): 即将执行的触发器 id- 取消该事件会抑制本次调用的全部管道
- CoreLib 不可用, 生物定义缺失或没有解析出可执行管道时不会触发
集成建议
- 使用
definition()在生物生成或交互时验证 id 是否为 EmakiMobs 管理的自定义生物 registeredIds()可用于 tab-complete 或配置验证MobDefinition字段有限, 主要用于基础信息展示. 需要更多细节 (如 components, ea_attributes) 时, 考虑直接读取 EmakiMobs 的配置文件或使用实体 PDC 标记- EmakiMobs 启用时会向 EmakiCoreLib 的目标选择器注册
type: emakimobs身份 (身份值为受管生物 id), 随模块停用注销. 因此 CoreLib 动作里的select源段可以按该身份筛出 EmakiMobs 生物; 未安装或未启用 EmakiMobs 时同一配置不会报错, 只是筛不出目标