Skip to content

模块化架构

ArcartX-Suite 使用 宿主 + 模块 Jar 架构。宿主提供核心基础设施,各功能模块可以打包为独立 Jar 放入 modules/ 目录按需加载。

开源 SDK 仓库

公开 API 与宿主参考实现见 ArcartXSuite-Core。下述目录结构与该仓库一致(axs-api/proxy/ 等)。

项目结构

ArcartX-Suite/
├── ArcartX-Suite-api/              # 模块 API 接口层(AXSModule, ModuleContext 等)
├── ArcartX-Suite-core/             # 宿主核心(ShadowJar 输出)
├── modules/
│   ├── announcer/        # Announcer 播报 + Subtitle 字幕
│   ├── entitytracker/    # EntityTracker 实体追踪 + 目标 HUD
│   ├── essentials/       # Essentials 基础工具 (传送/管控/砍树/InvActions)
│   ├── chat/             # Chat 频道聊天
│   ├── conversation/     # Conversation 对话桥
│   ├── eventpacket/      # EventPacket 事件引擎
│   ├── combateffect/     # CombatEffect 战斗特效 + 伤害飘字
│   ├── loginview/        # LoginView 登录界面
│   ├── mail/             # Mail 邮箱
│   ├── map/              # Map 世界地图
│   ├── onlinerewards/    # OnlineRewards 在线奖励
│   ├── pickup/           # Pickup 拾取提示
│   ├── prop/             # Prop 快捷道具
│   ├── questgps/         # QuestGPS 任务导航
│   ├── regions/          # Regions 区域保护 + 世界规则
│   ├── rgb/              # RGB 渐变色文本
│   ├── tab/              # Tab 在线列表
│   ├── title/            # Title 称号
│   ├── warehouse/        # Warehouse 仓库银行
│   ├── market/           # Market 全球市场
│   ├── qqbot/            # QQBot QQ群服互联
│   ├── battlepass/       # BattlePass 战令系统
│   ├── fishing/          # Fishing 钓鱼系统
│   ├── lottery/          # Lottery 抽奖系统
│   ├── afkreward/        # AfkReward 挂机奖励
│   └── menu/             # Menu 通用菜单系统

核心组件

组件包路径说明
AXSModuleArcartX-Suite-api模块生命周期接口:onEnable / onDisable / onReload / isReady
ModuleContextArcartX-Suite-api宿主暴露给模块的上下文:plugin 实例、Logger、各种 Bridge
ModuleDescriptorArcartX-Suite-api模块元数据:id / name / version / depends
ModuleCommandHandlerArcartX-Suite-api可选命令处理接口,实现后自动注册 /axs <moduleId> 子命令
ModuleRegistryArcartX-Suite-core模块扫描 / 加载 / 启用 / 禁用 / 重载
ModuleClassLoaderArcartX-Suite-core模块隔离 ClassLoader,每个模块 Jar 独立加载
DefaultModuleContextArcartX-Suite-coreModuleContext 的默认实现

启动流程

onEnable()
  ├── 初始化反射桥 (packetBridge, clientBridge, itemStackBridge …)
  ├── 创建 ModuleRegistry
  ├── scanAvailableModuleIds()
  │     └── 预扫描 modules/ 目录,收集所有外部模块 Jar 的 id
  ├── 对每个内置模块:
  │     externalModuleIds.contains(id) → 跳过(交给 ModuleRegistry)
  │     否则 → 执行宿主内置加载(走内置 Service 初始化)
  ├── printModuleStatusSummary()
  ├── moduleRegistry.loadAll()
  │     └── 按拓扑排序加载所有外部模块 Jar
  └── 加载完成

关键设计:外部模块 Jar 已完全独立化,通过自建 Service 运行;宿主仅对无外部 Jar 的模块执行内置加载,防止双重初始化。

重载流程

/axs reload all

对每个模块判断加载来源:

  • 外部 Jar 已加载moduleRegistry.reloadModule(id) → 触发模块 onReload()
  • 内置加载 → 执行宿主内置重载逻辑(调用模块 onReload()

/axs reload <模块名>

单模块重载遵循同样逻辑,通过 isExternalModule() 判断走外部还是内置路径。

热加载 / 热卸载

不同于 reload(onDisable + onEnable 在同一个 ClassLoader 内复位),/axs load|unload 提供真正的运行时插拔:

/axs load <模块名>

  1. 检查模块未加载(已加载则拒绝,提示走 reload)。
  2. 扫描 modules/ 目录寻找 id 匹配的 jar。
  3. 进入与启动期相同的 loadAndEnable(DiscoveredModule) 流程:
    • 检查外部插件依赖 / ArcartX-Suite 模块依赖(depends)
    • 创建独立 ModuleClassLoader(URLClassLoader 子类)
    • 实例化 AXSModule 主类
    • 构建 DefaultModuleContext
    • 注册模块的 ModuleConfigSpec(用于配置诊断)
    • 调用 instance.onEnable(context)
  4. 失败时调用 cleanupFailedModule(id) 回滚(onDisable + 关闭 ClassLoader + 从 modules 表移除)。

/axs unload <模块名>

  1. 反向依赖检查:遍历所有已启用模块的 descriptor.depends(),若存在依赖该模块的 dependent,则拒绝卸载并提示 dependents 列表。
  2. 执行 disableModule(loaded)
    • commandHandlers.remove(id) — 取消 /axs <id> 子命令
    • instance.onDisable() — 模块自清理(停止 Service、注销事件、关闭数据库)
    • 标记 loaded.setEnabled(false)
  3. removePacketHandlers(id) — 移除该模块注册的 ClientPacketHandler
  4. modules.remove(id) — 从注册表移除。
  5. closeClassLoader(loaded) — 调用 URLClassLoader.close() 释放 jar 文件句柄。

已知约束

  • Capability 清理:当前 capability 表不跟踪 owner,模块需在 onDisable 中自行清理 capabilities(否则旧引用会持有死对象)。ModuleRegistry.removeCapabilities(id) 保留作为接口契约位。
  • 依赖图变化unload 不会自动 disable dependents,要求管理员按依赖顺序手动 unload。

UI 注册与更新

每个有 UI 的模块在 reload 时严格执行以下步骤:

步骤操作
1onDisable() 停止旧 Service,基类自动注销所有已注册 UI
2onEnable() 加载新配置 → 根据 uiResourceMappings() 导出 UI YAML 文件
3startService() 中调用 registerModuleUi() → 内部使用 packetBridge.registerOrReloadUi() 重新读取文件并注册/热重载
4创建并启动新 Service

registerModuleUi()AbstractAXSModule 提供的统一封装,内部调用 registerOrReloadUi(),每次都会重新读取磁盘上的 UI 文件,确保手动修改的 YAML 在 /axs reload 后立即生效。onDisable() 时基类会自动 unregisterUi,不再存在 UI 残留问题。

替代旧 API

旧代码中使用的 context.prepareUiBinding() 在 reload 时不会重新读取文件,已废弃。新模块或重构时请改用 registerModuleUi()

模块实现模式

独立模式

模块自建 Service,完全不依赖宿主业务逻辑。适合逻辑简单或已完全解耦的模块。

java
public final class RgbModule implements AXSModule {
    private ModuleContext context;
    private RgbService service;

    @Override
    public ModuleDescriptor descriptor() {
        return ModuleDescriptor.builder("rgb")
            .name("RGB").version("1.2.0-beta")
            .mainClass(getClass().getName()).build();
    }

    @Override
    public boolean onEnable(ModuleContext context) throws Exception {
        this.context = context;
        service = new RgbService(context);
        service.start();
        return true;
    }

    @Override
    public void onDisable() {
        if (service != null) { service.shutdown(); service = null; }
    }

    @Override
    public void onReload() throws Exception {
        onDisable();
        if (context != null) onEnable(context);
    }
}

模块 Jar 描述文件

每个模块 Jar 在 resources/ 中必须包含 module.yml

yaml
id: mymodule           # 唯一标识,与 config.yml 中的键对应
name: MyModule         # 显示名称
version: 1.2.0-beta

main: com.example.MyModule   # AXSModule 实现类全限定名
api-version: 1.0
depends: []            # 强依赖的其他模块 id
softdepends: []        # 软依赖的其他模块 id
external-depends: []   # 强依赖的外部 Bukkit 插件名
external-softdepends: []

ModuleContext API

完整参考见 ModuleContext 上下文

跨模块协作请阅读 Capability 详解;第三方模块开发见 开发者指南

方法返回类型稳定性说明
plugin()JavaPlugin宿主插件实例
logger()Logger模块专用 Logger
dataFolder()File模块私有数据目录(plugins/ArcartX-Suite/data/<moduleId>/
pluginDataFolder()File宿主插件数据目录(plugins/ArcartX-Suite/
packetBridge()PacketBridgeAPI@StableArcartX UI/Packet 桥接
clientBridge()ClientBridgeAPI@StableArcartX 客户端桥接
itemStackBridge()ItemBridgeAPI@StableItemStack → JSON
packetGuard()PacketGuardAPI@Stable客户端包守卫(频率限制)
accountTypeService()AccountTypeService@Stable统一账号识别服务(微软正版 / LittleSkin / 离线)
itemSourceRegistry()ItemSourceRegistry@Stable全局物品来源注册表
itemMatcher()ItemMatcherAPI@Stable全局物品匹配器
currencyManager()CurrencyBridgeAPI@Stable全局货币管理器
attributeBridge()AttributeBridgeRegistry@Stable全局属性桥接注册表
registerCapability()void@Stable注册跨模块能力
getCapability()T@Stable查找跨模块能力
hasPlugin(String)boolean检查外部 Bukkit 插件

QuestGPS × Chemdah

  • overlay 根键 = Chemdah Template.getId()(裸 ID,如 gps_main_newcomer
  • 分类:category.source 二选一 — chemdah 仅 meta.type,overlay 仅 overlay category
  • UI 发包:categories / quests / tasks / rewards Map(Title 式 entryKey 模板列表)
  • 详见 QuestGPS 模块文档

基于 GPL-3.0 许可发布