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,这些共享数据库方法会返回 nullfalse

共享存储与消息总线

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

存储配置访问

方法用途
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 不存在导致的 NoSuchMethodErrorClassNotFoundException

基础方法

检测类是否存在

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 系列插件文档