主题
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-unavailableitem-library-source-not-founditem-library-item-not-founditem-library-invalid-amountitem-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) | 创建确认对话框构建器 |
模板能力范围
第一阶段模板已支持:
titlesizecancel-clicklayout.content-slotsitems.<key>.slotitems.<key>.fill-slotsmaterialnameloreamountcustom-model-dataenchantsflagsunbreakable
一个最小模板示例
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();
}
}
}最佳实践
在类初始化时探测:将探测结果存储为静态常量,避免重复探测
javaprivate static final boolean HAS_PAPER_CHAT = RuntimeCapability.hasPaperAsyncChat();优先使用预定义方法:HNCore 提供的预定义方法更语义化
java// 推荐 if (RuntimeCapability.hasAdventureComponentSendMessage()) { ... } // 不推荐 if (RuntimeCapability.hasMethod(CommandSender.class, "sendMessage", Component.class)) { ... }提供降级方案:始终为不支持的情况提供兼容实现
javaif (RuntimeCapability.hasClass("NewAPI")) { useNewAPI(); } else { useLegacyAPI(); // 降级方案 }避免过度探测:只探测真正需要的 API,不要为了探测而探测
