Theseus 模组文档
API 参考 / 入口与生命周期

ModContext


想先跑起来?

这一页是签名与行为参考。日志、数据目录、配置、注册表、事件总线都从这里拿。直接做一遍:教程 · 第一个模组。

宿主在生命周期回调中传入的运行上下文,是模组访问宿主基础设施的唯一入口。除 openFilePick 为 default 方法(未实现它的宿主返回 false 且不触发回调,模组据此走降级路径)外,其余方法保持抽象(无 default 实现),因为需返回有意义的宿主提供值。多数返回对象是绑定当前模组的视图,模组停用时宿主按归属统一清理。

方法速览

方法用途
getLogger()本模组日志记录器
getDataDir()本模组数据目录 data/mods/<id>
config()扁平 key-value 配置读写
unlockCollect(String)主动解锁自有收集品
resources()加载模组自带图像/文本资源
language()贡献模组译文
registerSound(ResourceLocation, double, boolean)注册模组自带音效
registerMusic(ResourceLocation, double, boolean)注册模组自带 BGM
registries()统一注册表入口(仅 onPreInit 可写)
capabilities()能力注入注册表
modServices()跨模组命名服务
eventBus()全进程唯一事件总线
notifier()toast / 定时弹窗通知
panels()右下角 复古桌面 弹窗
network()宿主托管网络线程池
uiCustomization()原版 UI 扩展点
hotkeys()全局快捷键注册
openFilePick(FilePickRequest, Consumer<Path>)游戏内文件选取

完整签名

java
public interface ModContext {

    java.util.logging.Logger getLogger();

    Path getDataDir();

    ModConfig config();

    void unlockCollect(String collectId);

    ResourceManager resources();

    LanguageRegistry language();

    void registerSound(ResourceLocation location, double volume, boolean preload);

    void registerMusic(ResourceLocation location, double volume, boolean preload);

    ModRegistries registries();

    CapabilityRegistry capabilities();

    ModServiceRegistry modServices();

    EventBus eventBus();

    Notifier notifier();

    Panels panels();

    NetworkAccess network();

    UiCustomization uiCustomization();

    GlobalHotkeys hotkeys();

    default boolean openFilePick(FilePickRequest request, java.util.function.Consumer<Path> callback);
}

getLogger()

签名:

java
java.util.logging.Logger getLogger();

行为:返回名为 seecmd.mod.<模组id> 的 java.util.logging.Logger。该实例关闭了父处理器(useParentHandlers = false),自带 ConsoleHandler,输出格式为 [<模组id>] 消息,ERROR 红色、WARNING 黄色、其余青色。重复激活时会先移除旧 handler,避免日志重复。线程:任意线程(Logger 自身线程安全)。

示例:

java
ctx.getLogger().info("初始化完成,数据目录:" + ctx.getDataDir());

getDataDir()

签名:

java
Path getDataDir();

行为:返回模组数据目录。默认是相对工作目录的 data/mods/<id>;宿主/编辑器可注入数据根,此时为 <root>/<id>。每次调用都会尝试 Files.createDirectories(...)(幂等),创建失败静默忽略,因此返回的路径不保证真实存在,读写时仍需处理 IO 异常。线程:任意线程。

示例:

java
Path dir = ctx.getDataDir();
Files.writeString(dir.resolve("state.txt"), "ready");

config()

签名:

java
ModConfig config();

行为:返回本模组的配置文件读写接口,封装 <dataDir>/config.json。首次调用时懒创建实例,之后返回同一个实例。线程:实例方法 synchronized 保护,可在事件处理器与滤镜中并发访问。

示例:

java
ctx.config().set("volume", 0.8);
double volume = ctx.config().getDouble("volume", 1.0);

unlockCollect(String collectId)

签名:

java
void unlockCollect(String collectId);

行为:解锁指定 id 的收集品。collectId = 注册该收集品时所用 RegistryKey 的 path。为空或已解锁时无效果;成功解锁会记录获取日期并持久化进度。宿主解锁处理器尚未就绪时(如故事系统未初始化),请求先入队,处理器注册后按序回放。线程:源码未声明额外线程约束。

示例:

java
ctx.unlockCollect("first_note");

resources()

签名:

java
ResourceManager resources();

行为:返回资源入口,首次调用懒建并缓存。路径约定 assets/<命名空间>/<路径>(命名空间约定 = 模组 id),宿主按「data/mods/<模组id>/assets/... 磁盘覆盖 → 模组 jar 内 assets/...」两级查找;找不到抛受检异常 ResourceNotFoundException,调用点必须处理。解码只依赖 JDK,可在任意线程;后端纹理在首次绘制时懒创建,句柄须在渲染线程消费;阻塞 IO 不要放在游戏/渲染线程,改用 loadImageAsync。模组停用时宿主释放本入口产出的图像后端资源。

示例:

java
try {
    RenderImage img = ctx.resources().loadImage(ResourceLocation.of("hello_mod", "textures/icon.png"));
} catch (ResourceNotFoundException e) {
    ctx.getLogger().warning("缺少图标:" + e.getMessage());
}

language()

签名:

java
LanguageRegistry language();

行为:返回本模组的译文贡献入口(无状态视图)。也可改为在 jar 内放 assets/<模组id>/lang/<locale>.json 由宿主自动加载;两种方式叠加,后注册的覆盖先注册的。自动扫描发生在本模组 onInit 返回之后,故 onInit 中读自有译文为空。

示例:

java
ctx.language().add("zh", Map.of("hello_mod.greeting", "你好"));

registerSound(ResourceLocation location, double volume, boolean preload)

签名:

java
void registerSound(ResourceLocation location, double volume, boolean preload);

行为:注册模组自带音效。资源定位与 resources() 同口径;location.toString() 即音频在引擎内的注册 id,天然带命名空间,不会与内置音频或其它模组冲突。宿主把音频落地到 <dataDir>/audio/<命名空间>/<路径> 后注册进音频引擎(每次覆盖写,jar 更新后副本同步)。volume 为默认音量 0.0~1.0;preload 表示是否立即预加载进内存(短音效建议 true)。资源不可读时仅记录警告,不抛异常。线程:源码未声明额外线程约束。

示例:

java
ctx.registerSound(ResourceLocation.of("hello_mod", "sfx/click.wav"), 1.0, true);

registerMusic(ResourceLocation location, double volume, boolean preload)

签名:

java
void registerMusic(ResourceLocation location, double volume, boolean preload);

行为:注册模组自带 BGM,语义同 registerSound,类别为 BGM(受 bgmVolume 音量控制)。preload 仅表示是否预校验文件存在,BGM 不会预加载进内存。

示例:

java
ctx.registerMusic(ResourceLocation.of("hello_mod", "bgm/menu.ogg"), 0.9, false);

registries()

签名:

java
ModRegistries registries();

行为:返回绑定本模组的统一注册表入口,注册音符类型 / 游戏模式 / 判定等级 / 谱面扩展段 / 渲染滤镜 / 背景渲染器 / 打击特效样式 / 判定策略 / 判定调整器 / 判定窗口合成器 / 收集品 / 皮肤 / 层覆盖 / 自定义渲染层 / 轨道与相机姿态调整器。写路径做归属校验:注册键命名空间必须等于本模组 id,否则 IllegalArgumentException;读路径直接透传全局注册表。

只能在注册窗口内(Mod.onPreInit)写入:宿主在全部模组的 onPreInit 结束后冻结所有注册表,此后写入抛 RegistryFrozenException。注册在进程生命周期内一次性完成、不再撤销,因此启用/停用模组需要重启才生效。

示例:

java
ctx.registries().renderFilters().register(
        RegistryKey.of("hello_mod", "scanline", RenderFilter.class),
        new MyScanlineFilter());

capabilities()

签名:

java
CapabilityRegistry capabilities();

行为:返回绑定本模组的能力注入注册表视图。模组可在 onInit 中注册自定义能力实例(如 GameplayHud 覆盖);宿主在每个游玩场景自动把可用的能力(GameStateAccess / SceneHost / AudioAccess 等)以同类型注册,模组用 get(Class) 取用,未注册返回 null。模组停用时宿主按归属清理其注册项。详见 CapabilityRegistry。

示例:

java
GameStateAccess state = ctx.capabilities().get(GameStateAccess.class);
if (state != null) {
    ctx.getLogger().info("当前音乐时间:" + state.musicTimeMs());
}

modServices()

签名:

java
ModServiceRegistry modServices();

行为:返回绑定本模组的跨模组命名服务注册表。register(name, service) 暴露自己的状态控制面,全局 id 自动拼接为 <本模组id>:<name>;find(globalId) 查询并读写其它模组暴露的服务,无需编译期耦合。模组停用时宿主自动清理其服务。详见 ModServiceRegistry。

示例:

java
ctx.modServices().register("state", myService);
ModService other = ctx.modServices().find("other_mod:state");

eventBus()

签名:

java
EventBus eventBus();

行为:返回带本模组归属的事件总线视图。一条总线、全进程唯一:游玩事件(判定/暂停/帧…)与跨场景事件(切场/启动/设置…)都在同一条总线上,模组用事件类型区分。模组经它建立的全部订阅会在停用/卸载时由宿主一次性退订。线程:任意线程可订阅;投递在宿主线程(游戏/渲染线程)同步发生。

示例:

java
ctx.eventBus().subscribe(GameStartEvent.class, event -> ctx.getLogger().info("开始游玩"));

notifier()

签名:

java
Notifier notifier();

行为:返回 Notifier 实现(宿主单例):右下角 toast(info / warning / error)与屏幕中央定时弹窗(dialog)。任意线程可调用,仅入队后立即返回,游戏线程统一渲染。

示例:

java
ctx.notifier().warning("配置项缺失,使用默认值");

panels()

签名:

java
Panels panels();

行为:返回 Panels 实现(宿主单例):在屏幕右下角创建与游戏 BGM 信息面板同款的 复古桌面 滑入面板,支持定时自动滑出、常驻、点击回调、动态更新内容。任意线程可调用,游戏线程统一渲染。

示例:

java
PanelHandle handle = ctx.panels().persistent("状态", false);
handle.setContentLines(List.of("已连接"));

network()

签名:

java
NetworkAccess network();

行为:返回本模组专属的网络线程池入口,首次调用懒创建核心 4 线程的守护调度池(线程名 mod-net-<id>-N)。模组用标准 java.net / java.nio 在其上实现任意网络功能。严禁在游戏线程做阻塞 IO。宿主在模组停用与游戏退出时 shutdownNow;关停后再调用 executor() 抛 IllegalStateException(不重建线程池)。详见 NetworkAccess。

示例:

java
ctx.network().executor().execute(() -> {
    // 所有 socket / 文件网络读写都在这里
});

uiCustomization()

签名:

java
UiCustomization uiCustomization();

行为:返回绑定本模组的 UI 定制能力入口,提供嵌入原版固定 UI 区域的扩展点(主界面底部状态栏、光标渲染、结算覆盖层、轨道样式、设置扩展等),区别于 panels() 的独立悬浮弹窗。模组停用时宿主移除其定制项。详见 UiCustomization。

示例:

java
ctx.uiCustomization().addStatusBarItem("status", "就绪").setOnClick(() -> {
    // 点击回调在游戏线程触发
});

hotkeys()

签名:

java
GlobalHotkeys hotkeys();

行为:返回全局快捷键注册入口。模组在任意场景下可触发的快捷键(如开关 HUD)必须经此注册,进入原生设置「全局快捷键」集合供玩家统一改键;禁止经 event.input.KeyEvent 实现玩家无法改键的隐性快捷键。宿主在模组停用与游戏退出时自动清理注册项。线程:注册任意线程可调用;快捷键回调在游戏线程触发(每帧至多一次)。详见 GlobalHotkeys。

示例:

java
ctx.hotkeys().add(new GlobalHotkey() {
    @Override public String id() { return "toggle_hud"; }
    @Override public String labelKey() { return "hello_mod.hotkey.toggle_hud"; }
    @Override public int defaultKeyCode() { return java.awt.event.KeyEvent.VK_H; }
    @Override public void onHotkey() { /* 游戏线程 */ }
});

openFilePick(FilePickRequest request, Consumer callback)

签名:

java
default boolean openFilePick(FilePickRequest request, java.util.function.Consumer<Path> callback);

行为:发起一次游戏内文件选取(导入图片 / 谱面等文件的用户入口)。宿主决定选取界面形态,模组只给 FilePickRequest 配置并在回调里消费结果;不要自行调起 Swing/AWT 系统文件对话框(会阻塞游戏线程且脱离宿主界面口径)。

示例:

java
boolean accepted = ctx.openFilePick(
        FilePickRequest.of().title("导入特效图片").extensions("png", "jpg"),
        path -> {
            if (path == null) {
                return;                      // 用户取消:不改动任何数据
            }
            ctx.getLogger().info("已选择:" + path);
        });

稳定性:2.0 起冻结:2.x 内只增不破(禁止删签名 / 改签名 / 改语义);破坏性变更只在 major 版本发生且须提供迁移指南与至少一个大版本的弃用期。3.x 内继续只增不破。