Skip to content

这页不是面向服主的配置文档,而是纯面向二开作者的开发者说明。

目标只有一个:

让你在接入 HNCore 时,先看清 该依赖什么、别依赖什么、什么时候应该重新取服务、哪些能力适合业务层直接使用

如果你正在写:

  • HN 系列下游插件
  • 自己的 Bukkit / Paper 业务插件
  • 需要复用共享存储、ClusterBus、物品库、GUI Kit 的模块

建议先读这页,再读 HNCore Java API

一、先记住一个总原则

业务插件优先依赖 HNCoreAPI,不要直接依赖 HNCore 的底层实现类。

从构建角度看,当前推荐方式是:

  • 开发依赖:hncore-api
  • 服务器运行:hncore

也就是说,下游插件编译时应优先依赖 hncore-api,而不是直接拿运行版 hncore 作为开发依赖。

也就是说,优先顺序应该是:

  1. 先看 HNCoreAPI 有没有现成入口
  2. 如果有,就优先通过 HNCoreAPI 获取服务
  3. 只有在确实做底层扩展时,才考虑更低层的实现类

这是因为当前 HNCore 已经形成了比较明确的能力分层。

二、对外能力分层

可以把 HNCore 当前对外能力简单分成三层。

二点五、当前构建与依赖方式

当前 HNCore 已经拆成:

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

推荐写法:

kotlin
dependencies {
    compileOnly("com.github.hnplugins:hncore-api:x.y.z")
}

其中 x.y.z 应替换为你当前部署的 HNCore 对应版本。

如果你当前正在接入 HNCore 1.1.0 这一轮新增的公共能力,例如:

  • 物品 identity / matcher / serial 协议
  • PlaceholderProvider 统一出口
  • ItemSpec / ItemCodec / ItemSnapshot

那下游项目也应该同步使用 hncore-api:1.1.0,不要继续把依赖写成 1.0.0

服务器运行时仍然安装 HNCore 的运行版插件 jar,不需要额外再放一份单独的 hncore-apiplugins 目录。

1)稳定公共入口层(推荐业务插件直接使用)

主要指:

  • HNCoreAPI
  • ClusterBus
  • ClusterActionRegistry
  • ClusterStatusService
  • ClusterPingService
  • ItemLibraryGateway
  • ItemFacade
  • PlayerLookupService
  • CoreScheduler
  • GUI Kit 对外入口
  • 公式引擎入口
  • 统一授权接入入口
  • ConfigManager 创建入口

这层的特点是:

  • 语义相对稳定
  • 已经被设计成给下游业务模块复用
  • 文档、命令、配置都围绕这些入口组织

2)高级公共能力层(能用,但要知道边界)

主要指:

  • SharedPubSubService
  • SharedKeyValueService
  • SharedStorageManager
  • 共享数据库相关 manager / section 访问

这层适合:

  • 你明确知道自己在做“共享基础设施接入”
  • 你清楚 Redis / MySQL / fallback 的语义
  • 你不是在设计新的业务广播协议,而是在复用底层能力

3)底层实现层(不建议业务插件直接依赖)

例如:

  • StorageRedisService
  • StorageMysqlService
  • StorageMysqlKeyValueStore
  • Cluster transport / impl 细节类

这层的特点是:

  • 暴露出来不代表推荐直接依赖
  • 更接近 HNCore 内部实现
  • 未来演进时更可能变化

如果你的目标只是做一个业务插件,通常不要把这些类当成主要接入面。

占位符推荐规范

如果你的业务插件要通过 HNCore 暴露 PlaceholderAPI 占位符,建议把这三层概念分开理解:

1)内部命名空间

这是 HNCore 内部统一占位符服务使用的 token 语义:

text
attribute.attack_damage
mail.unread
shop.balance

也就是说,内部统一使用 namespace.key

2)统一桥接入口

HNCore 会自动提供一个统一的 PlaceholderAPI 入口:

text
%hncore_<namespace>_<key>%

例如:

text
%hncore_attribute_attack_damage%
%hncore_mail_unread%

这套写法适合:

  • 从框架视角统一理解所有业务命名空间
  • 做调试和底层规范说明
  • 在 HNCore 文档里统一表达各模块占位符出口

3)业务前缀入口

现在 PlaceholderProvider 还可以声明自己的业务前缀,例如:

java
@Override
public List<String> papiIdentifiers() {
    return List.of("hnattr");
}

这样 HNCore 会自动额外暴露:

text
%hnattr_attack_damage%

而不需要业务插件自己再手写一层独立的 PlaceholderExpansion 壳子。

推荐实践

  • 对最终用户 / 服主文档:优先写业务前缀入口
    • 例如:%hnattr_attack_damage%
  • 对框架 / 开发者文档:可以补充统一桥接入口
    • 例如:%hncore_attribute_attack_damage%

这样既保留统一框架视角,又不会把 hncore_ 这个前缀强行暴露成唯一用户入口。

三、最推荐的接入方式

1)日志与配置

最普通的业务插件,几乎都可以从这里开始:

java
var logger = HNCoreAPI.getLogger(this);
var configManager = HNCoreAPI.createConfigManager(this);

适用场景:

  • 自己的插件配置文件管理
  • 统一日志输出风格

2)共享物品库与高层物品门面

如果你的模块要兼容多个外部物品库,不要自己分别接 Mythic / Neige / Baikiruto。

优先方式:

java
var gateway = HNCoreAPI.getItemLibraryGateway();
var result = gateway.resolve("bi", "example_item", 1);

如果你的场景还涉及 identity、标签读写、匹配规则或物品编解码,则进一步优先:

java
var itemFacade = HNCoreAPI.getItemFacade();

适合:

  • 邮件附件
  • 奖励发放
  • 商店商品
  • 任务奖励
  • 配置化生成物品

2.5)玩家查找

如果你的模块需要统一处理名称 / UUID 解析、在线补全或显示名,不要每个项目都自己重复写一套。

优先方式:

java
var lookup = HNCoreAPI.getPlayerLookupService();
var target = lookup.resolve(input);
var display = lookup.displayName(target);

2.6)统一调度器

如果你的模块只是想切主线程、跑异步或做延时任务,优先考虑:

java
HNCoreAPI.getScheduler().runSync(this, task);
HNCoreAPI.getScheduler().runAsync(this, task);

这样更容易和 HNCore 当前的统一线程语义保持一致。

3)ClusterBus

如果你的模块要做跨服控制消息,不要直接自己拼 Redis channel。

优先方式:

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

然后按 namespace/action 组织业务消息。

4)GUI Kit

如果你要做的是可复用 GUI,而不是一次性的硬编码小菜单,优先考虑:

  • createGuiTemplateLoader(...)
  • createGuiTemplateRenderer()
  • createGuiContext()

5)公式引擎

如果你有数值表达式或脚本公式需求,优先走:

  • createExpressionEngine(...)
  • createNumericFormulaEngine(...)

而不是自己再内嵌另一套公式系统。

四、生命周期:什么时候可以取服务

HNCore 必须先完成启用

下游插件最基本的前提是:

  • HNCore 必须先启用
  • 你的插件再开始取 HNCoreAPI 相关服务

所以依赖插件一般要确保:

  • plugin.yml 里正确声明依赖 / softdepend
  • onEnable() 里先做依赖检查

一个常见写法:

java
var hook = HNCoreAPI.createPluginHook(this);
if (hook.hookRequired("HNCore")) {
    return;
}

有些 API 可能返回 null

这不一定代表异常,很多时候只是当前能力未启用

例如:

  • getSharedDatabaseManager():共享数据库未启用时可能为 null
  • getSharedPubSubService():Redis Pub/Sub 未就绪时可能为 null
  • getClusterBus():极早期阶段或异常状态下可能为 null
  • getSharedItemLibraryService():旧兼容入口,在 HNCore 尚未完成初始化时可能为 null

需要补充的是:

  • getItemLibraryGateway() 作为门面对象通常可以直接获取
  • 但它背后的具体来源是否可用,仍应通过 isSourceAvailable(...) 或解析结果来判断

所以对外模块的原则是:

  • 先判空 / 先判可用,再使用

五、reload 语义:哪些对象不要长期死缓存

这是当前最关键的开发注意点之一。

1)HNCore reload 会重建部分运行时服务

当前 HNCore.reload() / /hncore reload 不只是读文件,它还会重建:

  • 共享数据库
  • 共享存储
  • ClusterBus
  • ClusterStatusService
  • ClusterPingService
  • 共享物品库相关状态
  • 脚本系统运行态

2)这意味着什么

这意味着某些对象在 reload 后可能已经不是原来的实例

尤其是:

  • ClusterBus
  • ClusterActionRegistry
  • ClusterStatusService
  • ClusterPingService
  • 共享数据库 / 共享存储相关 service

3)推荐做法

做法 A:每次使用时重新取

最稳:

java
var bus = HNCoreAPI.getClusterBus();

适合:

  • 调用频率不高
  • 只是偶尔广播或查询状态

做法 B:缓存,但要有重绑机制

如果你必须缓存,比如你注册了一批 cluster handler,那么应该像 HNAttribute 那样做:

  • 定期检查当前 bus / registry 是否已更换
  • 如果变了,注销旧订阅并重新绑定

4)不推荐做法

不建议这样:

  • 插件启动时拿一次 ClusterBus
  • 永久保存到字段里
  • 从此不再刷新

因为 /hncore reload 后这个引用可能已经过期。

五点五、日志等级:INFO / DEBUG / TRACE 怎么选

当前 HNCore 的日志已经从单纯 debug: true/false 升级为等级模型,推荐主配置写法:

yml
log:
  level: INFO

一般建议:

  • INFO:日常生产环境
  • DEBUG:模块级排障
  • TRACE:高噪声明细链路,例如 SQL、公式、ClusterBus 明细、Buff / Mythic 技能过程

旧写法 debug: true/false 仍兼容,但新文档和新项目都更推荐统一使用 log.level

如果你在自己的下游项目里使用 HNCoreAPI.getLogger(...),现在也可以逐步开始使用:

  • logger.trace(...)
  • logger.isDebugEnabled()
  • logger.isTraceEnabled()

六、线程语义:哪些回调在哪个线程

1)ClusterActionRegistry 的 handler

当前 ClusterActionRegistry.dispatch(...) 会把 handler 切回 Bukkit 主线程执行

也就是说:

  • 你在 registry.register(namespace, action, handler) 里写的 handler
  • 默认可以安全地访问大多数 Bukkit 主线程 API

这也是为什么它比直接裸用 Redis Pub/Sub 更适合业务层。

2)ClusterPingService 的 CompletableFuture

pingAll() / pingNode() 返回的是 CompletableFuture

需要注意:

  • 它的完成回调不保证一定在主线程
  • 如果你在 thenAccept(...) 里要操作 Bukkit API,最好自己切回主线程

3)SharedPubSubService

如果你直接使用 SharedPubSubService

  • subscribe(...)subscribeAsync(...) 线程语义不同
  • 这已经是偏底层用法

如果你只是做业务广播,优先改用 ClusterBus + ClusterActionRegistry,这样线程语义更清晰。

七、ClusterBus:什么时候该用它,什么时候不该用它

推荐用 ClusterBus 的场景

  • 模块级跨服 reload
  • 指定玩家跨服刷新
  • 管理指令广播
  • 轻量控制消息
  • ping / status / 健康检查

不建议直接用 ClusterBus 传什么

不建议把它当成:

  • 大量数据复制通道
  • 高频大包同步层
  • 长事务结果总线

它更适合:

  • 控制消息
  • 短 payload
  • 明确 namespace/action 的指令型通信

namespace/action 设计建议

推荐这样组织:

  • namespace:按插件或子系统划分
  • action:按具体动作划分

例如:

  • hnattribute/reload
  • hnattribute/refresh-player
  • hnmail/mail-sync
  • myplugin/rebuild-cache

targetNode 的建议

  • 管理广播:用 broadcast(...)
  • 定向控制:用 sendTo(...)
  • ping / 诊断:优先用 ClusterPingService

八、SharedPubSubService 和 ClusterBus 该怎么选

这是最容易混的点。

用 ClusterBus,如果你在做业务层跨服控制

例如:

  • 某插件要广播“刷新玩家数据”
  • 某插件要广播“重载配置”
  • 某插件要做轻量节点间信号交互

用 SharedPubSubService,如果你明确在做底层 Redis 通道接入

例如:

  • 你已经有一套明确的频道协议
  • 你就是要直接操作 Redis channel
  • 你能自己承担线程与协议边界

简单判断规则

如果你在犹豫:

“我是不是该直接 publish 一个 Redis channel?”

大多数情况下答案是:

先不要,优先看能不能改成 ClusterBus。

九、SharedKeyValueService 该怎么理解

这是共享键值存储抽象,不是“必须是 Redis”。

当前语义是:

  • 优先 Redis
  • Redis 不可用时回退 MySQL
  • 两者都不可用时抛异常

适合场景:

  • 轻量共享状态
  • 短字符串配置 / 标记
  • 简单分布式标志位

不适合场景:

  • 大对象存储
  • 复杂查询
  • 事务型业务数据

如果你要做复杂结构化数据,优先看共享数据库,而不是把一切塞进 key-value。

十、ItemLibraryGateway / ItemFacade 该怎么理解

当前更推荐把物品库相关能力分成两层理解:

ItemLibraryGateway

这是统一解析外部物品库物品的稳定门面,不是某一个库的专用包装。

推荐业务模块使用它的原因:

  • 减少对具体外部插件 API 的直接耦合
  • 可以统一失败语义
  • 可以统一配置写法
  • 可以统一 source 别名与规范来源处理

推荐做法:

  • 对外配置里让用户写 source + itemId + amount
  • 业务代码优先通过 getItemLibraryGateway() 解析

ItemFacade

如果你的模块不只是“解析库物品”,还要处理:

  • identity
  • 标签读写
  • 匹配规则
  • 物品编解码

那更适合直接把 ItemFacade 当成高层入口。

不要做的事:

  • 一边接 ItemLibraryGateway / ItemFacade
  • 一边又直接硬编码调 Mythic / Neige / Baikiruto API

这样会把抽象层价值抵消掉。

十一、GUI Kit 当前适合做到哪一步

当前 GUI Kit 更适合:

  • 标题 / 布局 / 静态模板
  • 占位符渲染
  • 分页骨架
  • 通用导航按钮

当前仍然推荐:

  • 模板负责外观
  • Java 代码负责点击逻辑

也就是说,不要预期它已经变成一套“把全部业务逻辑都写进 yml”的重型框架。

十二、一个最小插件接入示例

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

        var logger = HNCoreAPI.getLogger(this);
        logger.info("HNCore version = " + HNCoreAPI.getVersion());

        var bus = HNCoreAPI.getClusterBus();
        var registry = HNCoreAPI.getClusterActionRegistry();
        if (bus != null && registry != null) {
            registry.register("myplugin", "refresh", context -> {
                logger.info("收到 cluster refresh");
            });
        }
    }
}

十三、一个更稳的 ClusterBus 使用建议

如果你的插件长期依赖 cluster handler,推荐思路是:

  1. 启动时先绑定一次
  2. 保存当前 bus / registry 引用
  3. 在合适时机检查引用是否变化
  4. 如果变化,注销旧订阅并重新注册

如果你不想自己做这套重绑逻辑,那就至少:

  • 不要长期缓存旧 bus
  • 每次广播前重新 HNCoreAPI.getClusterBus()

十四、推荐阅读顺序

如果你是开发者,建议顺序是:

  1. 先读这页:开发者 API 与集成指南
  2. 再读:HNCore Java API
  3. 如果你要做跨服,再读:ClusterBus 与集群控制面
  4. 如果你要做配置与运维联动,再读:storage.yml 配置说明
  5. 如果你要做 GUI,再读:GUI Kit 与模板规范

十五、最后给开发者的一句话

把 HNCore 当成:

  • 一套能力底座
  • 一套稳定入口集合
  • 一套不要越层乱用的分层系统

你接入得越贴近 HNCoreAPI 和稳定公共接口,后面维护成本通常就越低。

HN 系列插件文档