Mod
这一页是签名与行为参考。入口接口:先写一个最小模组,再回来看每个回调的边界。直接做一遍:教程 · 第一个模组。
模组入口接口。第三方模组实现它(或其子接口 GameplayMod),通过 info() 提供元信息,并按需覆写生命周期回调。实现类必须有无参构造器:宿主从 jar 加载时用 Class.getDeclaredConstructor().newInstance() 实例化。
方法速览
| 方法 | 用途 |
|---|---|
info() | 返回模组元信息(唯一抽象方法) |
loadOrder() | 声明加载阶段与软依赖;默认 null = INIT、无软依赖 |
onPreInit(ModContext) | 注册窗口:唯一允许写注册表的阶段 |
onInit(ModContext) | 注册表冻结后的初始化:读配置、订阅事件、建运行期资源 |
onPostInit(ModContext) | 全部模组 onInit 完成后的收尾与跨模组交互 |
onUnload() | 卸载时释放资源(调用一次) |
createConfigScene() | 提供模组独立配置场景;默认 null = 无配置界面 |
完整签名
public interface Mod {
ModInfo info();
default ModLoadOrder loadOrder();
default void onPreInit(ModContext ctx);
default void onInit(ModContext ctx);
default void onPostInit(ModContext ctx);
default void onUnload();
default com.seecmd.mod.api.scene.Scene createConfigScene();
}info()
签名:
ModInfo info();行为:返回不可变的 ModInfo。宿主在注册阶段调用并校验:
- 返回
null、抛异常、id为空或不符合[a-z0-9_.-]{1,64}(不得含/与:)→ 拒绝注册,ModManager.register返回false。 name/version/description为空白,或authors为空列表 → 拒绝注册(日志给出缺失字段)。id重复 → 后一次注册被忽略并告警。
宿主也可能在模组列表、状态查询(getModInfo / getRegisteredMods / getActiveMods)时再次调用,因此实现应保持廉价、无副作用。模组 id 同时是数据目录名 data/mods/<id>、资源命名空间与注册键命名空间,务必与 manifest 的 Seecmd-Mod-Id 一致。
示例:
@Override
public ModInfo info() {
return ModInfo.of("hello_mod", "Hello Mod", "1.0.0",
List.of("YourName"), "我的第一个 Theseus 模组");
}loadOrder()
签名:
default ModLoadOrder loadOrder();行为:返回 ModLoadOrder,声明激活阶段与软依赖。默认返回 null,等价于 LoadStage.INIT 且无软依赖。该声明只决定同阶段内、每个阶段内部的遍历次序;「何时做」由三段回调表达。loadOrder() 抛异常时宿主按默认值(INIT、无软依赖)处理。硬依赖仍经 ModInfo.dependsOn() 声明,缺失即加载失败。
示例:
@Override
public ModLoadOrder loadOrder() {
return new ModLoadOrder(LoadStage.PRE_INIT, List.of("core_lib"), List.of());
}onPreInit(ModContext ctx)
签名:
default void onPreInit(ModContext ctx);行为:注册窗口回调,模组加载时最先调用。经 ctx.registries() 注册全部自有内容(见 ModRegistries);此刻注册表开放,全部模组的本回调结束后宿主立即冻结注册表。
- 注册键命名空间必须等于本模组 id,否则
IllegalArgumentException;重复键同样IllegalArgumentException。 - 不要在本回调里做阻塞 IO 或耗时操作:它处在全部模组的启动关键路径上。
- 抛异常 → 该模组不被激活,异常记入加载失败(
LOAD_FAILED),不影响其它模组。 - 此阶段创建的上下文会在模组停用时统一回收(网络线程池、图像句柄等)。
示例:
@Override
public void onPreInit(ModContext ctx) {
ctx.registries().collectibles().register(
RegistryKey.of("hello_mod", "first_note", CollectibleDefinition.class),
CollectibleDefinition.of("初遇").withBrief("第一次启动模组"));
}onInit(ModContext ctx)
签名:
default void onInit(ModContext ctx);行为:注册表已冻结、全部模组的 onPreInit 已完成之后调用。用于注册窗口之外的初始化:读配置、建缓存、订阅事件、创建运行期资源、读取其它模组已注册的内容。
- 任何注册写入都抛
RegistryFrozenException。 - 抛异常 → 该模组不被激活,记入加载失败(
LOAD_FAILED),运行期订阅与网络线程池由宿主清理。 - 本回调返回之后宿主才扫描 jar 内语言文件,故此处读自有译文为空。
示例:
@Override
public void onInit(ModContext ctx) {
boolean showToast = ctx.config().getBool("show_toast", true);
if (showToast) {
ctx.notifier().info("Hello Mod 已加载");
}
ctx.eventBus().subscribe(TickEvent.class, event -> {
// 任意场景每帧回调(宿主线程)
});
}onPostInit(ModContext ctx)
签名:
default void onPostInit(ModContext ctx);行为:全部模组的 onInit 已完成之后调用。用于发布初始事件、自检、建立跨模组交互、读取自有译文。
- 抛异常不撤销激活状态:仅记录错误,
getLoadStatus仍为ACTIVE,getLoadError可读到该异常。此时运行期订阅已经建立,判成失败会让订阅变孤儿。
示例:
@Override
public void onPostInit(ModContext ctx) {
ctx.getLogger().info("译文:" + ctx.language().get("hello_mod.greeting").orElse("(missing)"));
}onUnload()
签名:
default void onUnload();行为:模组卸载时调用一次(玩家停用需重启生效,实际发生在退出或注销时)。可覆写以释放资源。异常被宿主隔离,仅记录日志,不阻塞其它模组卸载。宿主随后还会强制关停网络线程池、释放图像句柄,并按模组归属清理事件订阅、能力、跨模组服务、全局快捷键与 UI 定制项;冻结式注册表不撤销。
示例:
@Override
public void onUnload() {
unloading = true; // 供弹窗重建逻辑判断终止条件
}createConfigScene()
签名:
default com.seecmd.mod.api.scene.Scene createConfigScene();行为:返回模组的独立配置场景实例;默认 null 表示无配置界面。模组可在菜单中(如 ModList 详情面板按 C)进入该场景。宿主每次进入配置都会调用本方法取得新实例;抛异常时返回 null。
示例:
@Override
public com.seecmd.mod.api.scene.Scene createConfigScene() {
return new MyConfigScene();
}线程:源码未声明额外线程约束;生命周期回调由宿主在启动线程串行调用。
稳定性:2.0 起冻结:2.x 内只增不破(禁止删签名 / 改签名 / 改语义);破坏性变更只在 major 版本发生且须提供迁移指南与至少一个大版本的弃用期。3.x 内继续只增不破。