Theseus 模组文档
开发指南

环境搭建与第一个模组


第一次接触本项目:建议先跟 教程:序章:把环境搭起来 与 教程:第一个模组 走一遍,把工程跑通。本篇是同一批内容的细节参考(构建脚本做了什么、manifest 每个字段的协商规则、目录约定),适合回头查阅。

本节的目标是让一个从零开始的开发者把「能编译、能进游戏、能在模组列表里看到 ACTIVE」这条最短路径走通。当前版本的模组只依赖三个外部件:JDK 17+、mod-api-3.2.jar(编译期 API)、gson-*.jar(如 gson-2.10.1.jar,com.google.gson.JsonObject 出现在 API 签名里,所以编译期需要它;运行期由宿主提供)。

模组编译期只应依赖 com.seecmd.mod.api.*(含子包)这一套瘦身 API,不得把宿主或游戏内核的类打进自己的 jar。

1.1 构建路线一:build.bat(无需 Maven,推荐)

官方模板 mods-template\ 已带好脚本与依赖,最快的路径是复制模板目录再改:

  1. 把 mod-api-3.2.jar 与 gson-*.jar 放在工程根目录(与 build.bat 同级);
  2. 编辑 build.bat 顶部的变量:MOD_ID、MOD_NAME、MOD_VERSION、MOD_CLASS(入口类全限定名)、API_VERSION;
  3. 在 src\main\java 下写代码,在 src\main\resources 下放资源;
  4. 双击 build.bat,产物为 target\<MOD_ID>-<MOD_VERSION>.jar。

脚本内部做四件事,值得知道它到底依赖什么:

前提:本机装了 JDK 17+,且 javac、jar 在 PATH 中。脚本找不到 mod-api-*.jar 时会直接报错退出(也可以在脚本里手工指定 API_JAR);gson-*.jar 缺省时会尝试从 %USERPROFILE%\.m2 里找一个可用的。

1.2 构建路线二:Maven(pom.xml)

模板同样带 pom.xml,两条路线产物一致,都是带完整 manifest 的模组 jar。使用前需要先把 API jar 安装进本地仓库(只需一次):

text
mvn install:install-file -Dfile=mod-api-3.2.jar -DgroupId=com.seecmd -DartifactId=mod-api -Dversion=3.2 -Dpackaging=jar

然后 mvn package,产物在 target\ 下。关键配置有两点:

编译目标版本用 maven.compiler.release 设为 17(模板已配好)。

1.3 目录布局与资源放哪

text
my-mod/
├── mod-api-3.2.jar            # 编译期依赖(必需)
├── gson-2.10.1.jar            # 编译期依赖(必需,spi 签名用到 JsonObject)
├── build.bat                  # 路线一
├── pom.xml                    # 路线二(二选一)
└── src/main/
    ├── java/com/example/hellomod/HelloMod.java
    └── resources/assets/hello_mod/
        ├── lang/zh.json       # 自带语言文件,宿主自动加载
        ├── lang/en.json
        └── textures/note.png

资源约定只有一种:文件放在 src/main/resources/assets/<模组id>/<路径>,代码里用 ResourceLocation.of("<模组id>", "<相对路径>") 引用。例如上面的贴图对应 ResourceLocation.of("hello_mod", "textures/note.png")。

当前版本没有裸路径回退:宿主按「data/mods/<模组id>/assets/... 磁盘覆盖 → 模组 jar 内 assets/...」两级查找,找不到会抛受检异常 ResourceNotFoundException,所以加载资源的调用点必须显式处理(try/catch 或声明抛出),不能靠判空。

语言文件放在 assets/<模组id>/lang/<locale>.json,键名以模组 id 作前缀(如 "hello_mod.greeting"),宿主在装配后自动扫描加载。模组数据目录是 data/mods/<模组id>/,经 ctx.getDataDir() 拿到;配置文件经 ctx.config() 读写。

1.4 manifest 字段

键是否必须说明
Seecmd-Mod-Class必须入口类全限定名,必须是 Mod 或 GameplayMod 的实现
Seecmd-Mod-Id可选仅在加载失败时作为失败记录的键(缺失时退化为 jar 文件名)。真正的模组 id 由 ModInfo.id() 提供,两者不一致时以 ModInfo 为准
Seecmd-Mod-Name / Seecmd-Mod-Version / Seecmd-Mod-Authors / Seecmd-Mod-Description宿主不读取模组列表展示的是 ModInfo 的对应字段。这些 manifest 字段只作为 jar 元信息,方便人工识别,不参与加载
Seecmd-Api-Version建议期望的 mod-api 版本(如 3.2,判定按 major / minor 解析)

Seecmd-Api-Version 的判定规则:

声明情况宿主行为
键缺失或为空白按宿主当前版本处理(不因缺键被拒绝)
无法解析拒绝加载
major 不同拒绝加载,并给出「此模组要求 API X,宿主支持 Y」的明确提示
minor 更高(major 相同)告警放行(宿主保证 minor 向后兼容)
patch 不同不参与判定

校验发生在创建模组类加载器之前,所以不会出现「加载到一半才炸」。major 不匹配属于最容易踩的坑:仓库里放着旧版 API jar 编译出来的模组,进游戏就会直接 LOAD_FAILED。

1.5 最小可运行模组

最小模组只需实现 Mod,提供 info(),并按需覆写生命周期回调。下面这段是可直接编译的参考骨架;带日志、数据目录与玩家提示的完整走查见 教程 · 第一个模组。

java
package com.example.hellomod;

import com.seecmd.mod.api.Mod;
import com.seecmd.mod.api.ModContext;
import com.seecmd.mod.api.ModInfo;

import java.util.List;

public class HelloMod implements Mod {

    /** 模组 id:小写下划线,必须与 manifest 的 Seecmd-Mod-Id 一致。 */
    private static final String MOD_ID = "hello_mod";

    @Override
    public ModInfo info() {
        return ModInfo.of(
                MOD_ID,                       // id
                "Hello Mod",                  // 显示名称
                "0.1.0",                      // 模组自己的版本号(与 mod-api 的 1.0 无关)
                List.of("YourName"),          // 作者列表
                "我的第一个 Theseus 模组");      // 描述
    }

    @Override
    public void onPreInit(ModContext ctx) {
        // 唯一允许注册内容的阶段。本模组暂时没有自有内容要注册。
        // 不要在这里做 IO / 耗时操作:全部模组的 onPreInit 一结束宿主就冻结注册表。
        ctx.getLogger().info("注册窗口:暂无内容注册");
    }

    @Override
    public void onInit(ModContext ctx) {
        // 注册表已冻结:读配置、读资源、建缓存、订阅事件都在这里。
        ctx.getLogger().info("初始化完成,数据目录:" + ctx.getDataDir());
        ctx.notifier().info("Hello Mod 已加载");
    }
}

说明几点:

1.6 安装到游戏

把构建产物 target\<MOD_ID>-<MOD_VERSION>.jar 放进游戏根目录的 mods\ 文件夹,重启游戏即可自动加载;在模组列表里能看到它,状态为 ACTIVE。