插件 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)
- 发现:
event.getAsmData().getAll(Plugin.class.getName())取出所有带@Plugin的类(ASM 扫描,datas即候选数)。 - 命名:读注解的
name;为空且deps非空时,用getModContainerFromID(deps.get(0)).getName()补上。 - 依赖检测:
allModsLoaded(deps)逐个Loader.isModLoaded(s)。不满足则整个跳过,日志输出未找到的依赖名(getUnfoundDeps)——目标 mod 没装时该插件不产生任何行为。 - 总开关:
WPConfigHandler.INSTANCE.isPluginEnabled(name)为假则跳过(日志Skipping over plugin {} as it was disabled.)。 - 实例化:
Class.forName后用clazz.newInstance(),要求公开的无参构造函数。类必须是IPlugin的实现,否则只记 error 而不加入。捕获Throwable而非Exception,因为缺类会抛NoClassDefFoundError。 - 登记到 WAILA 配置:
cfg.addConfig(WailaPlugins.NAME, name, name),即在 WAILA 的[modules]段为该插件名写入默认true的总开关,并在配置 GUI 中归入名为WAILA Plugins的模块组。 - 排序:
Collections.sort按@Plugin.order()升序(Double.compare,无注解按0),所以数字大的更晚load()。排序前后各打一条日志。 - 注册:依次
p.load(ModuleRegistrar.instance()),把 registrar 交给插件去挂 provider。 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 在当前源码中属预留、未被使用。
相关条目
- 配置系统 -
[plugins]段总开关与wailaplugins.cfg全部选项 - WAILA 模块开关 - 插件名如何变成 WAILA 里的开关键
- IC2 作物集成 / 血魔法集成 / Railcraft 集成 / RedLogic 集成 / Forge 流体桥接(IFluidHandler) / HarvestCraft 集成 / Forestry 集成 - 7 个内置插件