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 方法),它做三件事,顺序即判定顺序:
- 取出
ev.getModLog()存进log字段。 checkSide(ev.getSide())为假 → 打日志"Wrong side: %s, %s Addon not loading"并直接 return,不入列表。- 遍历
getDependencies(),逐条verifyModVersion(spec);任一不满足 → 打"Requirements unmet, %s Addon not loading"并 return。 - 三关全过 →
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,源码原样)做静态字段读取 + 类型断言。仓库内无调用点。
相关条目
- 配置文件 -
Addons分类的键名来源与 21 个选项 - 网络协议 -
init阶段建立的通道与握手 - NEI 接口实现总表 -
IConfigureNEI之后的实际注册 - 合成工作台集成 - 依赖
craftingTables.size() > 0的那个 addon - Ex Nihilo 集成 - 唯一通过 IMC 而非 NEI API 注册的 addon