Skip to content

HNCore 对其他插件公开了统一入口类 HNCoreAPI。只要 HNCore 已启用,其他插件就可以通过它获取日志、配置、统一调度器、玩家查找、数据库、共享存储、ClusterBus、统一授权接入、公式引擎、消息构建、GUI 构建、物品门面与 Groovy 脚本管理等公共能力。

当前构建结构已经拆成:

  • hncore-api:给下游插件编译依赖
  • hncore:服务器实际运行插件

因此二开项目现在更推荐直接依赖 hncore-api。

如果你现在关心的是“我作为二开作者应该优先依赖什么、哪些对象不要死缓存、reload 后怎么处理、线程语义是什么”,建议先读:开发者 API 与集成指南

入口类 ​

java
import com.github.hnplugins.hncore.api.HNCoreAPI;

HNCoreAPI 是纯静态入口,不需要手动 new。

使用前提 ​

  • HNCore 会在自身启用时通过平台桥接初始化 HNCoreAPI。
  • 依赖插件应确保 HNCore 已先于自己启用。
  • 部分方法在 HNCore 未启用时会返回 null,例如共享数据库、共享存储与 Groovy 脚本相关能力。
  • getEntityNameManager() 应该只在 HNCore 已完成启用后调用。

核心信息 ​

方法返回内容说明
getPlatform()HNCorePlatform获取当前已注册的平台桥接对象;大多数业务插件并不需要直接使用它
getVersion()String获取 HNCore 版本;未初始化时返回 Unknown
getPlugin()JavaPlugin获取当前运行中的 HNCore 主插件实例

调度与玩家查找 ​

统一调度器 ​

方法用途
getScheduler()获取统一调度器 CoreScheduler

新代码更推荐通过 HNCoreAPI.getScheduler() 调度任务,而不是直接在业务代码里散落 Bukkit.getScheduler()。

java
HNCoreAPI.getScheduler().runSync(this, () -> {
    logger.info("切回主线程执行");
});

玩家查找 ​

方法用途
getPlayerLookupService()获取统一玩家查找与补全服务

它适合处理:

  • 名称 / UUID 到 OfflinePlayer 的解析
  • 在线玩家补全
  • 统一显示名输出
java
var lookup = HNCoreAPI.getPlayerLookupService();
var target = lookup.resolve("Steve");
var display = lookup.displayName(target);

日志 ​

方法用途
getLogger(JavaPlugin plugin)以插件名创建日志器
getLogger(String name)用自定义名称创建日志器
getLogger(String name, String fromHex, String toHex)创建带渐变前缀的日志器
java
var logger = HNCoreAPI.getLogger("MyPlugin", "#60A5FA", "#A78BFA");
logger.info("插件已启动");

当前日志等级推荐在 HNCore/config.yml 中配置:

yml
log:
  level: INFO

可选值:OFF / INFO / DEBUG / TRACE。 旧写法 debug: true/false 仍兼容,但新项目更推荐 log.level。

配置与数据库 ​

配置管理 ​

方法用途
createConfigManager(JavaPlugin plugin)创建 ConfigManager,用于加载单文件、目录与默认配置

独立数据库管理器 ​

方法用途
createDatabaseManager(JavaPlugin plugin, DatabaseConfig config)根据显式配置创建数据库管理器
createDatabaseManager(JavaPlugin plugin, ConfigurationSection section)从配置节直接构建数据库管理器

共享数据库 ​

方法用途
getSharedDatabaseManager()获取 HNCore 当前共享数据库实例
hasSharedDatabaseManager()判断是否存在共享数据库实例
isSharedDatabaseEnabled()判断 storage.yml -> database.enabled 是否开启
isSharedDatabaseActive()判断共享数据库是否已成功激活
getSharedDatabaseType()获取当前共享数据库类型,如 sqlite / mysql

如果 HNCore 没有启用 storage.yml -> database.enabled,这些共享数据库方法会返回 null 或 false。

共享存储与消息总线 ​

这是当前版本里最值得注意的新增能力之一。

存储配置访问 ​

方法用途
getStorageConfig()获取整份 storage.yml
getStorageSection()获取 storage.yml -> storage
getClusterSection()获取 storage.yml -> cluster
getStorageMysqlSection()获取 storage.yml -> storage.mysql
getStorageRedisSection()获取 storage.yml -> storage.redis

共享存储状态 ​

方法用途
isSharedStorageMysqlEnabled()判断共享 MySQL 是否启用
isSharedStorageMysqlActive()判断共享 MySQL 是否已激活
isSharedStorageRedisEnabled()判断共享 Redis 是否启用
isSharedStorageRedisActive()判断共享 Redis 是否已激活

当前更推荐业务插件优先通过高层 API 判断共享存储状态,而不是直接依赖底层实现类。

共享键值存储 / PubSub ​

方法用途
hasSharedKeyValueService()判断共享键值存储是否可用
getSharedKeyValueBackendName()获取当前键值后端名:redis / mysql / none
getSharedPubSubService()获取共享 Redis Pub/Sub 服务
isSharedPubSubAvailable()判断消息总线是否可用

当前实现中:

  • 共享键值存储优先使用 Redis
  • Redis 不可用时,会回退到共享 MySQL
  • Pub/Sub 只在 Redis 启用且激活时可用

ClusterBus / 集群控制面 ​

这是当前版本另一个重点新增能力。

集群入口 ​

方法用途
getClusterBus()获取当前 ClusterBus
getClusterActionRegistry()获取 ClusterActionRegistry
getClusterStatusService()获取 ClusterStatusService
getClusterPingService()获取 ClusterPingService
getClusterSection()获取 storage.yml -> cluster 配置节

可以怎么理解 ​

如果说:

  • getSharedPubSubService() 更像“底层 Redis 通道能力”

那么:

  • getClusterBus() 更像“面向业务模块的统一集群控制层”

它的主要作用是让业务模块按 namespace/action 注册动作,然后通过 HNCore 统一广播,而不是每个插件自己再去维护 Redis Pub/Sub 接入。

当前 transport 语义 ​

ClusterBus 当前可能走两种主要 transport:

  • redis:真正跨服
  • loopback:仅本服本进程自测

当 cluster.transport=auto 且 Redis 不可用时,会自动回退到 loopback。

一个最小广播示例 ​

java
var bus = HNCoreAPI.getClusterBus();
var registry = HNCoreAPI.getClusterActionRegistry();

registry.register("example", "refresh", context -> {
    // 当前实现会切回 Bukkit 主线程后再执行
});

if (bus != null && bus.isAvailable()) {
    bus.broadcast("example", "refresh", java.util.Map.of("scope", "all"));
}

一个最小状态 / ping 示例 ​

java
var statusService = HNCoreAPI.getClusterStatusService();
var pingService = HNCoreAPI.getClusterPingService();

if (statusService != null) {
    var snapshot = statusService.getSnapshot();
    logger.info("当前节点: " + snapshot.nodeId());
    logger.info("当前 transport: " + snapshot.transportName());
}

if (pingService != null) {
    pingService.pingAll().thenAccept(result -> {
        logger.info("收到回包节点数: " + result.replies().size());
    });
}

共享外部物品库与高层物品门面 ​

HNCore 现在还会统一提供外部物品库解析服务,避免业务模块直接依赖具体插件 API。

当前内置来源包括:

  • mythic:<itemId>,别名:mm:<itemId>
  • neige:<itemId>,别名:ni:<itemId>
  • baikiruto:<itemId>,别名:bi:<itemId>

入口方法 ​

方法用途
getItemLibraryGateway()获取统一物品库门面;新代码优先使用
getSharedItemLibraryService()获取旧共享物品库服务实例;兼容入口
isItemLibrarySourceAvailable(String source)判断某个来源当前是否可用
resolveLibraryItem(String source, String itemId, int amount)便捷解析入口;内部也会走 gateway
getItemFacade()获取统一高层物品门面,适合同时处理 identity / tag / 匹配 / 编解码 / 物品库解析

返回结果 ​

物品库解析会返回 ItemLibraryResolveResult,其中包含:

  • 是否解析成功
  • 解析后的 ItemStack
  • 错误码 / 错误消息

当前内置错误码包括:

  • item-library-unavailable
  • item-library-source-not-found
  • item-library-item-not-found
  • item-library-invalid-amount
  • item-library-resolve-failed

新代码推荐写法 ​

java
var gateway = HNCoreAPI.getItemLibraryGateway();
var result = gateway.resolve("bi", "example_item", 1);
if (result.isSuccess()) {
    ItemStack item = result.itemStack();
} else {
    logger.warn("物品库解析失败: " + result.errorCode());
}

如果你的场景不只是“解析物品” ​

如果你还需要:

  • 读写统一 identity
  • 读取或写入标签
  • 构建匹配规则
  • 编解码 ItemStack

那更推荐走:

java
var itemFacade = HNCoreAPI.getItemFacade();

例如:

java
var result = HNCoreAPI.getItemFacade().resolveLibraryItem("mm", "example_item", 1);

兼容说明 ​

  • getItemLibraryGateway() 是当前更推荐的稳定入口
  • getSharedItemLibraryService() 仍保留,但更适合兼容旧代码
  • resolveLibraryItem(...) 作为便捷入口仍可继续使用,但新代码如果需要更明确的依赖边界,优先直接拿 gateway 或 item facade

表达式与数值公式引擎 ​

ExpressionEngine ​

方法用途
createExpressionEngine()创建表达式引擎
createExpressionEngine(Logger logger)创建带日志输出的表达式引擎

NumericFormulaEngine ​

方法用途
createNumericFormulaEngine()创建默认数值公式引擎
createNumericFormulaEngine(Logger logger)创建带日志器的数值公式引擎
createNumericFormulaEngine(Logger logger, boolean groovyEnabled, GroovySecurityLevel groovySecurityLevel)创建可指定 Groovy 开关与安全级别的数值公式引擎
getCoreFormulaEngineOptions()获取 HNCore 当前的全局 Groovy 策略

需要注意的是,模块请求的 Groovy 能力会被 HNCore 全局策略收紧:

  • 模块请求开启 Groovy,不代表一定能开启
  • 模块请求较宽松的安全级别,不代表一定能保持宽松
  • 最终实际生效的限制,以 HNCore 的 formula.groovy 全局配置为准
java
var logger = HNCoreAPI.getLogger("MyPlugin");
var engine = HNCoreAPI.createNumericFormulaEngine(logger, true, GroovySecurityLevel.SANDBOXED);

GroovyScripts 相关能力 ​

方法用途
loadGroovyConfig(String scriptName)读取某个脚本对应的配置文件
createGroovyConfigProxy(String scriptName)创建脚本配置代理
registerGroovyListener(Listener listener)注册由脚本创建的监听器
unregisterGroovyListener(Listener listener)注销由脚本创建的监听器

这组接口主要用于与 HNCore 的 Groovy 功能系统协同,而不是普通 Bukkit 事件注册的替代品。

文本与消息 ​

方法用途
colorize(String text)将 & 颜色代码格式化为游戏可用文本
gradient(String text, String fromHex, String toHex)生成渐变文本
text(String text)创建 TextBuilder
createMessageManager(JavaPlugin plugin)创建默认消息管理器
createMessageManager(JavaPlugin plugin, String fileName)基于指定语言文件创建消息管理器

GUI 与物品构建 ​

传统代码式 GUI ​

方法用途
guiBuilder(JavaPlugin plugin, String title, int size)创建 GuiBuilder
itemBuilder(Material material)基于材质创建 ItemBuilder
itemBuilder(ItemStack item)基于已有物品创建 ItemBuilder

这组接口适合继续写“纯代码式 GUI”,例如直接在 Java 里指定 slot、材质、名字与点击行为。

新增:GUI 模板层 ​

当前版本新增了一套更适合长期复用的 GUI Kit,核心思路是:

  • 模板负责标题、尺寸、布局、静态物品、导航按钮外观
  • GuiContext 负责占位符值
  • 点击逻辑仍由业务插件自己绑定
方法用途
createGuiTemplateLoader(JavaPlugin plugin)创建 GuiTemplateLoader,用于读取业务插件自己的 GUI yml
createGuiTemplateRenderer()创建 GuiTemplateRenderer,用于把模板渲染成 Gui / ItemStack
createGuiContext()创建 GuiContext,统一承载占位符数据
createGuiNavigator(JavaPlugin plugin)创建 GUI 导航器,适合多页面 / 向导式界面
createConfirmDialog(JavaPlugin plugin)创建确认对话框构建器

模板能力范围 ​

第一阶段模板已支持:

  • title
  • size
  • cancel-click
  • layout.content-slots
  • items.<key>.slot
  • items.<key>.fill-slots
  • material
  • name
  • lore
  • amount
  • custom-model-data
  • enchants
  • flags
  • unbreakable

一个最小模板示例 ​

yml
gui:
  title: "&8邮箱菜单"
  size: 27
  cancel-click: true

  layout:
    content-slots: [10, 11, 12, 13, 14, 15, 16]

  items:
    background:
      fill-slots: [0, 1, 2, 3, 4, 5, 6, 7, 8]
      material: LIGHT_BLUE_STAINED_GLASS_PANE
      name: " "

    inbox:
      slot: 10
      material: CHEST
      name: "&b收件箱"
      lore:
        - "&7总邮件数: &f{total}"
        - "&7未读邮件: &e{unread}"

一个最小渲染示例 ​

java
GuiTemplate template = HNCoreAPI.createGuiTemplateLoader(this)
        .load("gui/mail-main-menu.yml");
GuiContext context = HNCoreAPI.createGuiContext()
        .with("player", player.getName())
        .with("total", total)
        .with("unread", unread);

Gui gui = HNCoreAPI.createGuiTemplateRenderer().render(this, template, context);

gui.setItem(template.requireSlot("inbox"),
        HNCoreAPI.createGuiTemplateRenderer().renderItem(template, "inbox", context),
        viewer -> openInbox(viewer));

这里有一个很重要的设计边界:

  • 模板决定外观和布局
  • 业务代码决定点击后做什么

这样既能减少 slot / 物品外观硬编码,又不会把业务逻辑锁死在一套过重框架里。

多渠道输入会话框架 ​

HNCore 2.2.0+ 提供了统一的玩家输入会话管理框架,支持三种输入方式:

渠道适用场景优点缺点
CHAT长文本、多行内容无长度限制需要关闭 GUI
ANVIL短文本、价格、名称GUI 内输入、体验流畅长度限制(~35 字符)
SIGN多行短文本支持 4 行、GUI 内输入需要 Paper API

核心类型 ​

类型用途
ChatInputSessionManager聊天输入会话管理器
MultiChannelInputSessionManager多渠道输入会话管理器(支持铁砧、告示牌)
SimpleChatInputSession简单聊天输入会话
ValidatedChatInputSession带验证的聊天输入会话
AnvilInputSession铁砧输入会话
SignInputSession告示牌输入会话(2.3.0+)
InputValidators内置验证器集合

聊天输入示例 ​

java
ChatInputSessionManager sessionManager = new ChatInputSessionManager(plugin);
plugin.getServer().getPluginManager().registerEvents(sessionManager, plugin);

SimpleChatInputSession<MyContext> session = SimpleChatInputSession
    .builder(player.getUniqueId(), context)
    .onInput((p, input) -> {
        // 处理输入
    })
    .onCancel(p -> {
        // 处理取消
    })
    .timeout(60000)
    .build();

sessionManager.beginSession(player, session);
player.closeInventory();
player.sendMessage("§a请输入内容:");

铁砧输入示例 ​

java
MultiChannelInputSessionManager sessionManager = new MultiChannelInputSessionManager(plugin);
plugin.getServer().getPluginManager().registerEvents(sessionManager, plugin);

AnvilInputSession<MyContext> session = AnvilInputSession
    .builder(player.getUniqueId(), context)
    .initialText("默认值")
    .promptMessage("§a请输入价格:")
    .onInput((p, input) -> {
        // 处理输入
    })
    .onCancel(p -> {
        // 处理取消
    })
    .timeout(60000)
    .build();

sessionManager.beginSession(player, session);

告示牌输入示例(2.3.0+) ​

java
SignInputSession<MyContext> session = SignInputSession.<MyContext>builder(player.getUniqueId(), context)
    .initialLines(List.of("第一行", "第二行", "", ""))
    .promptMessage("§a请在告示牌上输入内容:")
    .onInput((p, input) -> {
        // input 是所有行合并后的文本(用空格分隔)
    })
    .onCancel(p -> {
        // 处理取消
    })
    .timeout(60000)
    .build();

sessionManager.beginSession(player, session);

带验证的输入示例 ​

java
ValidatedChatInputSession<Void> session = ValidatedChatInputSession
    .builder(player.getUniqueId(), null)
    .validator(InputValidators.allOf(
        InputValidators.NOT_BLANK,
        InputValidators.length(1, 16)
    ))
    .onSuccess((p, input) -> {
        // 验证通过
    })
    .onFailure((p, input) -> {
        p.sendMessage("§c输入格式不正确");
    })
    .onCancel(p -> {
        // 处理取消
    })
    .maxRetries(3)
    .timeout(60000)
    .build();

sessionManager.beginSession(player, session);

内置验证器 ​

java
// 非空验证
InputValidators.NOT_BLANK

// 数字验证
InputValidators.INTEGER
InputValidators.POSITIVE_INTEGER
InputValidators.DOUBLE
InputValidators.POSITIVE_DOUBLE

// 范围验证
InputValidators.intRange(1, 100)
InputValidators.doubleRange(0.1, 999.9)

// 长度验证
InputValidators.length(1, 16)

// 正则验证
InputValidators.regex("^[a-zA-Z0-9_]+$")

// 枚举验证
InputValidators.oneOf("yes", "no", "是", "否")

// 组合验证
InputValidators.allOf(
    InputValidators.NOT_BLANK,
    InputValidators.length(3, 16),
    InputValidators.regex("^[a-zA-Z0-9_]+$")
)

分页与导航复用 ​

如果你的界面是列表型 GUI,HNCore 还提供了:

  • AbstractPagedGui<T>:统一分页列表基类
  • GuiNavItems:统一上一页 / 下一页 / 返回 / 刷新 / 关闭按钮渲染

适合邮箱列表、黑名单列表、商店商品页、强化记录等场景。

当前分页条目点击还支持接收 InventoryClickEvent,因此像“右键领取”“Shift+右键删除”这类复杂交互也可以继续保留在分页 GUI 中。

统一授权接入 ​

如果你要接的是 HN 系列付费模块或内部授权模块,当前主要相关方法包括:

方法用途
checkManagedLicenseServiceHealth()检查 HNCore 托管授权中心健康状态
checkManagedLicenseServiceHealth(long timeoutMs)带超时的托管授权中心健康检查
registerLicensedPlugin(JavaPlugin plugin, CoreManagedLicenseOptions options)以 HNCore 托管配置方式注册授权插件
registerLicensedPlugin(JavaPlugin plugin, RegisterLicensedPluginOptions options)以显式参数方式注册授权插件
getLicenseState(String productCode)获取某个商品当前授权运行时状态
isLicensedPluginAvailable(String productCode)判断某商品是否处于可用状态
hasLicensedFeature(String productCode, String feature)判断某商品是否具备某个功能标记
getLicenseErrorMessage(LicenseErrorCode code)获取统一错误提示文案

推荐做法是:

  • 服主把授权中心地址和 productCode -> license-key 配在 HNCore/auth.yml
  • 下游插件通过 registerLicensedPlugin(...) 把自己接入 HNCore 的统一授权体系

插件依赖检查与实体名称 ​

方法用途
createPluginHook(JavaPlugin plugin)创建依赖检查器,用于必需/可选依赖检查
getEntityNameManager()获取 HNCore 的实体名称映射管理器

EntityNameManager 适合在需要把 EntityType 转成中文显示名时复用 HNCore 已加载好的映射结果。

一段最常见的接入示例 ​

java
public final class MyPlugin extends JavaPlugin {
    @Override
    public void onEnable() {
        var logger = HNCoreAPI.getLogger(this);
        var hook = HNCoreAPI.createPluginHook(this);
        if (hook.hookRequired("HNCore")) {
            return;
        }

        var configManager = HNCoreAPI.createConfigManager(this);
        var engine = HNCoreAPI.createNumericFormulaEngine(logger, true, GroovySecurityLevel.SANDBOXED);

        logger.info("当前 HNCore 版本: " + HNCoreAPI.getVersion());
        logger.info("共享键值后端: " + HNCoreAPI.getSharedKeyValueBackendName());
    }
}

一个共享存储示例 ​

java
var pubSub = HNCoreAPI.getSharedPubSubService();
if (HNCoreAPI.hasSharedKeyValueService()) {
    logger.info("共享键值后端: " + HNCoreAPI.getSharedKeyValueBackendName());
}
if (pubSub != null && HNCoreAPI.isSharedPubSubAvailable()) {
    pubSub.publish("demo:channel", "hello");
}

适合怎么理解这套 API ​

如果你只想复用一小部分通用能力,可以这样记:

  • 要日志:getLogger(...)
  • 要配置:createConfigManager(...)
  • 要公式:createExpressionEngine(...) / createNumericFormulaEngine(...)
  • 要共享数据库:看 getSharedDatabaseManager() 这一组
  • 要共享存储 / PubSub:看 isSharedStorageMysqlActive()、isSharedStorageRedisActive()、getSharedPubSubService() 这一组
  • 要业务层集群广播:看 getClusterBus()、getClusterActionRegistry()、getClusterStatusService() 这一组
  • 要消息:text(...) / createMessageManager(...)
  • 要 GUI:guiBuilder(...) / itemBuilder(...)
  • 要依赖检测:createPluginHook(...)
  • 要 Groovy 脚本集成:看 loadGroovyConfig(...)、createGroovyConfigProxy(...) 与监听器注册接口

如果你只需要服主命令而不是开发接口,可以继续看 工具箱命令 与 命令说明。

运行时能力探测(v3.4.3+) ​

RuntimeCapability ​

RuntimeCapability 是 v3.4.3 新增的运行时能力探测工具类,用于检测当前服务端是否支持特定的 API。

java
import com.github.hnplugins.hncore.compat.RuntimeCapability;

为什么需要运行时探测?

不同的服务端核心(Paper、Spigot、Folia)和不同的 Minecraft 版本支持的 API 不同。通过运行时探测,你的插件可以:

  • 在支持新 API 的服务端上使用新特性
  • 在旧服务端上自动降级到兼容实现
  • 避免因 API 不存在导致的 NoSuchMethodError 或 ClassNotFoundException

基础方法 ​

检测类是否存在 ​

java
if (RuntimeCapability.hasClass("io.papermc.paper.event.player.AsyncChatEvent")) {
    // 使用 Paper 的 AsyncChatEvent
} else {
    // 降级到 Bukkit 的 AsyncPlayerChatEvent
}

检测方法是否存在 ​

java
if (RuntimeCapability.hasMethod(CommandSender.class, "sendMessage", Component.class)) {
    // 使用 Adventure Component 发送消息
    sender.sendMessage(Component.text("Hello"));
} else {
    // 降级到字符串消息
    sender.sendMessage("Hello");
}

查找方法(包括接口方法) ​

java
try {
    Method method = RuntimeCapability.findMethod(
        serviceClass, 
        "getCurrency", 
        String.class
    );
    Object result = method.invoke(service, "gold");
} catch (NoSuchMethodException e) {
    // 方法不存在,使用降级方案
}

预定义探测方法 ​

HNCore 提供了一些常用的预定义探测方法:

java
// 检测是否支持 Adventure Component 消息
if (RuntimeCapability.hasAdventureComponentSendMessage()) {
    sender.sendMessage(Component.text("支持 Adventure"));
}

// 检测是否支持 Paper AsyncChatEvent
if (RuntimeCapability.hasPaperAsyncChat()) {
    // 注册 Paper 聊天事件监听器
}

// 检测是否支持 Folia 区域化调度
if (RuntimeCapability.hasFoliaRegionScheduler()) {
    // 使用 Folia 的区域化调度器
}

// 检测 Server 类是否有 getServerName() 方法
if (RuntimeCapability.hasServerGetServerName()) {
    String serverName = Bukkit.getServer().getServerName();
}

性能特性 ​

  • 自动缓存:探测结果会被缓存,同一个探测在整个 JVM 生命周期内只执行一次
  • 线程安全:使用 ConcurrentHashMap 保证并发安全
  • 零开销:缓存命中后无需再次反射

实际应用示例 ​

示例 1:兼容 Paper 和 Spigot 的消息发送 ​

java
public class MessageSender {
    private static final boolean SUPPORTS_COMPONENT = 
        RuntimeCapability.hasAdventureComponentSendMessage();
    
    public void send(CommandSender sender, String message) {
        if (SUPPORTS_COMPONENT) {
            // Paper: 使用 Adventure Component
            sender.sendMessage(ColorUtil.toComponent(message));
        } else {
            // Spigot: 使用传统字符串
            sender.sendMessage(ColorUtil.format(message));
        }
    }
}

示例 2:兼容 Paper 和 Bukkit 的聊天事件 ​

java
public class ChatListener implements Listener {
    
    public void register(JavaPlugin plugin) {
        if (RuntimeCapability.hasPaperAsyncChat()) {
            // Paper: 使用 AsyncChatEvent
            Bukkit.getPluginManager().registerEvents(new PaperChatListener(), plugin);
        } else {
            // Bukkit: 使用 AsyncPlayerChatEvent
            Bukkit.getPluginManager().registerEvents(new BukkitChatListener(), plugin);
        }
    }
}

示例 3:动态加载插件扩展 ​

java
public class PluginIntegration {
    
    public void loadIntegrations() {
        // 检测 PlaceholderAPI
        if (RuntimeCapability.hasClass("me.clip.placeholderapi.PlaceholderAPI")) {
            logger.info("检测到 PlaceholderAPI,启用占位符支持");
            loadPlaceholderIntegration();
        }
        
        // 检测 Vault
        if (RuntimeCapability.hasClass("net.milkbowl.vault.economy.Economy")) {
            logger.info("检测到 Vault,启用经济系统集成");
            loadVaultIntegration();
        }
    }
}

最佳实践 ​

  1. 在类初始化时探测:将探测结果存储为静态常量,避免重复探测

    java
    private static final boolean HAS_PAPER_CHAT = 
        RuntimeCapability.hasPaperAsyncChat();
  2. 优先使用预定义方法:HNCore 提供的预定义方法更语义化

    java
    // 推荐
    if (RuntimeCapability.hasAdventureComponentSendMessage()) { ... }
    
    // 不推荐
    if (RuntimeCapability.hasMethod(CommandSender.class, "sendMessage", Component.class)) { ... }
  3. 提供降级方案:始终为不支持的情况提供兼容实现

    java
    if (RuntimeCapability.hasClass("NewAPI")) {
        useNewAPI();
    } else {
        useLegacyAPI(); // 降级方案
    }
  4. 避免过度探测:只探测真正需要的 API,不要为了探测而探测

HN 系列插件文档