Addon 架构与加载顺序

基本信息

属性 值
类型 内部扩展架构(一个 jar 内 7 个 @Mod 容器)
主类 net.bdew.neiaddons.NEIAddons,modid = NEIAddons,modName = NEI Addons
FML 依赖声明 dependencies = "after:NotEnoughItems"
addon 基类 BaseAddon implements NEIAddon(net.bdew.neiaddons.BaseAddon)
addon 容器 public static List<NEIAddon> addons(ArrayList)

一个 jar 里的 7 个 @Mod 容器

grep -rn "@Mod" src 命中 7 个类。它们共享同一个 modVersion(Tags.VERSION),通过 modid 后缀区分:

@Mod 类 modid 显示名 getName() FML dependencies
NEIAddons NEIAddons NEI Addons —(主容器,无 addon 名) after:NotEnoughItems
AddonAppeng NEIAddons|AppEng NEI Addons: Applied Energistics 2 Applied Energistics 2 after:NEIAddons;after:appliedenergistics2
AddonBotany NEIAddons|Botany NEI Addons: Botany Botany after:NEIAddons;after:Botany
AddonCraftingTables NEIAddons|CraftingTables NEI Addons: Crafting Tables Crafting Tables after:NEIAddons
AddonDeveloper NEIAddons|Developer NEI Addons: Developer Tools Developer Tools after:NEIAddons
AddonExnihilo NEIAddons|ExNihilo NEI Addons: Ex Nihilo Ex Nihilo after:NEIAddons;after:exnihilo
AddonForestry NEIAddons|Forestry NEI Addons: Forestry Forestry after:NEIAddons;after:Forestry

⚠️ mcmod.info 声明了 9 个容器,比源码多 2 个:文件里还有 NEIAddons|ExtraBees(“NEI Addons: Extra Bees”)与 NEIAddons|MiscPeripherals(“NEI Addons: Misc Peripherals”),但 src 下没有 AddonExtraBees / AddonMiscPeripherals 的 Java 文件,@Mod 注解也只有 7 处。这两条元数据是上游遗留,指向不存在的类。

加载流程

阶段 1:preInit(各 addon 自己的 @Mod.EventHandler)

每个 addon 的 preInit 调 doPreInit(ev)(BaseAddon 的 final 方法),它做三件事,顺序即判定顺序:

  1. 取出 ev.getModLog() 存进 log 字段。
  2. checkSide(ev.getSide()) 为假 → 打日志 "Wrong side: %s, %s Addon not loading" 并直接 return,不入列表。
  3. 遍历 getDependencies(),逐条 verifyModVersion(spec);任一不满足 → 打 "Requirements unmet, %s Addon not loading" 并 return。
  4. 三关全过 → NEIAddons.register(this),即 addons.add(addon)。

四个 checkSide 返回 false(仅客户端)的 addon:AddonBotany、AddonCraftingTables、AddonDeveloper、AddonExnihilo。AddonAppeng 与 AddonForestry 不覆写,双端都加载。

阶段 2:主类 init(FMLInitializationEvent)

NEIAddons.init 遍历已入列的 addon:

  • 读 Addons 分类下 addon.getName() 开关,关则打 "%s Addon disabled - skipping"。
  • 开则 addon.init(event.getSide()),整段包在 try / catch (Exception e) 里——单个 addon 初始化炸掉只会打警告,不影响其余 addon。
  • 之后 config.save()。
  • 建立网络:serverHandler = new ServerHandler() 并 channel.addHandler(Side.SERVER, ...);客户端额外建 ClientHandler 并 addHandler(Side.CLIENT, ...)。

阶段 3:NEI 回调 IConfigureNEI.loadConfig()

NEIAddonsConfig 是全源码唯一的 IConfigureNEI。它的 loadConfig() 遍历 NEIAddons.addons,对每个 isActive() 为真的 addon 调 loadClient(),每个 addon 单独包一层 catch (Throwable),失败时打 "Addon %s failed client initialization"。

isActive() 是 BaseAddon 里的 protected boolean active,默认 false,只有 init() 成功跑完才置 true。所以「init 里因为目标 mod 没装而提前 return」的 addon,loadClient() 不会执行。

各 addon 的 loadClient() 实际做的事:

addon loadClient() 内容
AppEng AppEngHelper.init() → API.registerNEIGuiHandler(new AppEngGuiHandler())
Botany FlowerHelper.setup()
Crafting Tables AddonCraftingTablesClient.load() → 给每个工作台类注册 GUI 叠加层
Developer Tools DeveloperHelper.init() → GuiContainerManager.addTooltipHandler(new DeveloperGuiHandler())
Ex Nihilo 空方法体,只有一句注释 "Currently this addon includes only some WAILA handlers"
Forestry BeeHelper.setup() + TreeHelper.setup() + ButterflyHelper.setup()

阶段 4:改写 mod 描述

NEIAddons.init 最后用 Loader.instance().activeModContainer().getMetadata().description 覆盖 FML 元数据描述,内容是 Loaded Addons: 加每行 - <名字>: Active/Inactive;若一个 addon 都没入列则写 "No Addons loaded :("。这是运行期改 FML 元数据,加载列表界面里看到的那段描述由此而来。

verifyModVersion 的语义

getDependencies() 返回 FML VersionParser 语法的版本要求串。verifyModVersion 的三种失败都不抛异常,只打 INFO 日志并返回 false:

情况 日志
mod 不在 Loader.instance().getIndexedModList() 里 "Required mod %s is not installed, dependent features will be unavailable"
getProcessedVersion() 为 null "Unable to determine version of required mod %s, ..."
!req.containsVersion(found) "Version mismatch: %s is required while %s was detected, ..."

只有 2 个 addon 声明了版本要求:

addon getDependencies() 实际效果
AddonForestry {"Forestry@[4.0.8.36,)"} 有下界无上界
AddonBotany {"Botany"} 裸 modid,只查存在性
AddonExnihilo {"exnihilo", "Waila"} 两条裸 modid
AddonAppeng {"appliedenergistics2"} 裸 modid

AddonCraftingTables 与 AddonDeveloper 不覆写,继承基类返回 new String[0],即不做任何版本检查。

Crafting Tables 的特殊激活条件

它是唯一不靠 active = true 收尾的 addon:init() 里逐条 tryLoadTableClass(modId, className, humanName),每个类先 verifyModVersion(modId) 再反射 Utils.getAndCheckClass,全部塞进 craftingTables 集合,最后 if (craftingTables.size() > 0) 才 active = true。8 个目标类一个都没加载出来时,这个 addon 保持 inactive。

反射工具

Utils.getAndCheckClass(cls, sup) 用 Class.forName 加载并断言 sup.isAssignableFrom(c),否则抛 RuntimeException(cls + " doesn't extend " + sup.getName())。所有跨 mod 的类引用都走它,因此本 mod 编译期不硬依赖 Forestry / AE2 / ExNihilo / Waila 的任何一个。

Utils.getAndCheckStaicField(cls, field, sup)(方法名拼写为 Staic,源码原样)做静态字段读取 + 类型断言。仓库内无调用点。

相关条目