Skip to content

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()StringYAML 文件与命令中使用的生物唯一 ID
entityType()EntityType底层原版实体类型
displayName()@Nullable StringMiniMessage 格式的自定义名; 未设置时为 null
experience()int击杀经验覆盖值; 0 表示沿用原版默认

**不暴露的内部实现: ** components, ea_attributes, actions, threat, boss_bar 均不在公共 API 中. 这些字段属于内部配置, 第三方插件无法查询

MobActionKeys 动作上下文键 ​

emaki.jiuwu.craft.mobs.api.MobActionKeys 提供 4 个 CoreActionKey<LivingEntity> 常量, 键名带 emakimobs: 命名空间:

常量键写入该上下文对象的触发器
ATTACKERemakimobs:attackeron_damage_take (攻击者为生物实体时)
KILLERemakimobs:killeron_death (存在存活击杀者时)
VICTIMemakimobs:victimon_kill
TARGETemakimobs:targeton_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 时同一配置不会报错, 只是筛不出目标