插件 API 与生命周期

WAILAPlugins 自身不注册任何方块、物品或实体。它是一层插件宿主:用运行时注解扫描发现插件类,在 Forge 的 preInit / loadComplete 两个阶段完成"发现 → 实例化 → 排序 → 注册 → 收尾"的完整流程。

源码包:tterrag.wailaplugins

模组基本信息

属性 值
主类 tterrag.wailaplugins.WailaPlugins(@Mod)
modid wailaplugins
显示名 WAILA Plugins
版本 源码中为占位符 GRADLETOKEN_VERSION,由构建流程替换
依赖 after:endercore;after:Waila;(软依赖:必须排在 EnderIO Core 与 Waila 之后加载)
实现的接口 com.enderio.core.IEnderMod(EnderIO Core 的 mod 描述接口,提供 modid() / name() / version())
配置 GUI 工厂 guiFactory = "tterrag.wailaplugins.client.config.WPConfigFactory"
客户端/服务端代理 @SidedProxy → ClientProxy / CommonProxy

@Mod 注解的 dependencies 写的是 after: 而非 required-after:,因此两个依赖都是加载顺序约束而非强制依赖。但源码中直接 import 了 WAILA 与 EnderIO Core 的类,实际上二者缺失会导致加载失败。

生命周期

主类只挂两个事件处理器:

阶段 事件 动作
preInit FMLPreInitializationEvent ① WPConfigHandler.INSTANCE.initialize(event.getSuggestedConfigurationFile()) ② PluginRegistrar.INSTANCE.preInit(event)
loadComplete FMLLoadCompleteEvent PluginRegistrar.INSTANCE.postInit()

PluginRegistrar 是 enum 实现单例(INSTANCE),状态为 public List<IPlugin> allPlugins。

@Plugin 注解

标记插件类,运行时保留(@Retention(RUNTIME),作用于 TYPE)。三个属性:

属性 类型 默认 含义
name String "" 插件名。为空时取第一个依赖 modid 对应的 ModContainer.getName()(模组显示名,不是 modid)
deps String[] {} 依赖的 modid 列表,用于加载检测与命名推导
order int 0 注册排序,数字越大越晚注册

约束:name 与 deps 不能同时为空——PluginRegistrar 会抛 IllegalArgumentException("Name and deps cannot both be null! Culprit: ...")。

IPlugin 接口

public interface IPlugin extends IWailaDataProvider {
    void load(IWailaRegistrar registrar);
    void postLoad();
}

继承 WAILA 的 IWailaDataProvider,因此每个插件本身就是 WAILA 的一个数据提供者,另加两段自有生命周期。

PluginBase 抽象基类

所有内置插件都继承它,负责把 IWailaDataProvider 的 final 回调收敛为"先判开关、再转调可覆写钩子"的结构:

IWailaDataProvider 的 final 方法 转调到的可覆写钩子 短路条件
getWailaStack getWailaStack(accessor) enabled() 为假 → 返回 null
getWailaHead getHead(...) enabled() 为假 → 不追加
getWailaBody getBody(...) 同上
getWailaTail getTail(...) 同上
getNBTData(...) getNBTData(te, tag, world, pos) 同上

getNBTData 的 final 版还会无条件补写 x / y / z 三个 int 坐标键,插件只需填自己的数据。

PluginBase 还把注册动作收成 7 个受保护辅助方法,内部由私有 RegType 枚举分派到 WAILA registrar:

辅助方法 实际调用
registerHead(...) registerHeadProvider
registerBody(...) registerBodyProvider
registerTail(...) registerTailProvider
registerStack(...) registerStackProvider
registerNBT(...) registerNBTProvider
registerEntityBody(inst, ...) registerBodyProvider((IWailaEntityProvider) inst, ...)
registerEntityNBT(inst, ...) registerNBTProvider((IWailaEntityProvider) inst, ...)

registerAll 逐个类调用对应分派。后两个实体变体虽然签名带一个 IWailaEntityProvider inst 参数,但 RegType 的实现忽略该参数、一律用 this——即实体注册实际总是注册 PluginBase 子类自身。

PluginBase 的 toString() 也参与调试:若类上有 @Plugin 注解,返回解析后的插件名(name 为空时取依赖 mod 的显示名),否则退化为默认实现。

注册流程(PluginRegistrar.preInit)

  1. 发现:event.getAsmData().getAll(Plugin.class.getName()) 取出所有带 @Plugin 的类(ASM 扫描,datas 即候选数)。
  2. 命名:读注解的 name;为空且 deps 非空时,用 getModContainerFromID(deps.get(0)).getName() 补上。
  3. 依赖检测:allModsLoaded(deps) 逐个 Loader.isModLoaded(s)。不满足则整个跳过,日志输出未找到的依赖名(getUnfoundDeps)——目标 mod 没装时该插件不产生任何行为。
  4. 总开关:WPConfigHandler.INSTANCE.isPluginEnabled(name) 为假则跳过(日志 Skipping over plugin {} as it was disabled.)。
  5. 实例化:Class.forName 后用 clazz.newInstance(),要求公开的无参构造函数。类必须是 IPlugin 的实现,否则只记 error 而不加入。捕获 Throwable 而非 Exception,因为缺类会抛 NoClassDefFoundError。
  6. 登记到 WAILA 配置:cfg.addConfig(WailaPlugins.NAME, name, name),即在 WAILA 的 [modules] 段为该插件名写入默认 true 的总开关,并在配置 GUI 中归入名为 WAILA Plugins 的模块组。
  7. 排序:Collections.sort 按 @Plugin.order() 升序(Double.compare,无注解按 0),所以数字大的更晚 load()。排序前后各打一条日志。
  8. 注册:依次 p.load(ModuleRegistrar.instance()),把 registrar 交给插件去挂 provider。
  9. postInit() 遍历 allPlugins 调 p.postLoad()。

每一步都有独立日志(Attempting to create plugin / Successfully created plugin / Failed to create plugin / Skipping over plugin ... as its dependencies ... were not found. / Sorting plugins. Before/After / Successfully loaded plugin / Completed plugin registration. N plugins registered.),排查插件为何没生效时可直接看日志。

稳健性

插件层的失败是逐个隔离的,不会拖垮整个 mod:

  • 步骤 5 的构造/实例化失败 → 该插件标记 failed、记 error,其余插件继续
  • 步骤 8 的 load() 抛异常 → 捕获 Throwable,记 fatal 并跳过该插件的注册结果
  • PluginBase 的 getNBTData 覆写普遍标注 @SneakyThrows(Lombok),说明个别目标 mod API 变动直接抛检查异常

代理层

CommonProxy / ClientProxy 只提供两个能力,服务端全部返回空值:

方法 CommonProxy(服务端) ClientProxy(客户端)
getMouseOver() null Minecraft.getMinecraft().objectMouseOver
isShiftKeyDown() false Keyboard.isKeyDown(LSHIFT) || isKeyDown(RSHIFT)

源码提示:在本仓库的 17 个 Java 文件中,WailaPlugins.proxy 这个 @SidedProxy 字段没有任何读取点——只有注解声明本身引用了 proxy 类。真正需要 Shift 键判定的 Forestry 插件走的是 Forestry 自己的 forestry.core.proxy.Proxies。因此这套 proxy 在当前源码中属预留、未被使用。

相关条目