ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

杀戮尖塔Mod开发入门:Java编译注入与BaseMod集成

杀戮尖塔Mod开发入门:Java编译注入与BaseMod集成

简介:本资源是一份面向Java初学者与游戏模组开发新手的《杀戮尖塔》MOD制作系统入门指南,聚焦零基础环境搭建与核心开发流程。PDF文档完整覆盖三大必备支持库(ModTheSpire、STSlib、BaseMod)的订阅与集成、IntelliJ IDEA开发环境配置、Maven项目结构搭建、pom.xml依赖声明(含Steam路径变量配置)、控制台调试、面向对象编程实践(类继承、监听函数、作用域)等关键环节,内容由浅入深,配有结构图与代码片段示例。资源为单文件PDF格式,共1个文件,大小1.5MB,轻量易读,适合作为离线查阅手册反复研习。目前已有2097人学习下载,是掌握杀戮尖塔MOD开发底层逻辑、构建首个可运行Java模组的高实用性起点资料。

1. 杀戮尖塔Mod制作入门:从零构建第一个可运行的Java Mod,不是改配置、不是拖文件,是真编译、真注入、真进游戏看log

你刚在Steam创意工坊下载了一个“无限能量球”Mod,双击启动——没反应;再查ModTheSpire界面,名字灰着,勾不上;翻日志,满屏红色ClassNotFoundException: basemod.BaseMod。这不是你手残,是绝大多数人卡死的第一关:杀戮尖塔Mod不是资源包,不是ini配置,它是一个必须用Java编译器逐行校验、用Maven打包成jar、再被ModTheSpire动态注入到JVM进程里的完整Java程序。本篇不讲“怎么订阅Mod”,只讲“怎么让自己的代码真正跑进SlayTheSpire的desktop-1.0.jar里”。你会亲手搭起IDEA + Maven + Steam路径绑定的闭环开发链,写出第一行System.out.println("Mod loaded at " + System.currentTimeMillis()),并在游戏启动瞬间看到这行字刷进ModTheSpire控制台——不是截图,不是演示,是你本地真实发生的字节码注入。适合零Java基础但敢开终端敲命令的人,也适合写过Spring却搞不定systemPath依赖的老手:这里没有黑匣子,所有路径、所有scope、所有@SpireInitializer的触发时机,全部摊开在pom.xml和ModTheSpire.json里。别信“一键生成”,信mvn package后target目录下那个32KB的jar——那才是你代码活过来的证明。


2. 搭建可验证的Mod开发环境:IDEA + Maven + Steam路径三重绑定,绕过90%的“找不到BaseMod”报错

杀戮尖塔Mod开发最反直觉的一点:它不依赖网络下载的SDK,而依赖你本地Steam库中三个jar文件的绝对路径。BaseMod.jar、ModTheSpire.jar、desktop-1.0.jar这三个文件必须被Maven以systemscope精准定位,否则编译时连import basemod.BaseMod;都会标红。下面步骤全部基于Windows 10/11实测,macOS/Linux用户只需将路径分隔符\\换成/,%MAVEN_HOME%换成$MAVEN_HOME,逻辑完全一致。

2.1 安装IntelliJ IDEA并初始化Java项目骨架

提示:不要用IDEA内置的“New Project → Java”向导,它默认创建标准Maven结构但缺失关键配置。必须手动新建空项目再导入pom.xml。

  1. 下载IntelliJ IDEA Community Edition(免费),安装时勾选“Add launchers path to the system PATH”;
  2. 启动IDEA,选择“New Project” → “Empty Project”(不是“Maven”模板!);
  3. 设置项目位置,例如D:\stsmods\tutorial,点击“Create”;
  4. 在项目根目录右键 →“New → File”,命名为pom.xml,粘贴以下内容(注意:<steam.path>需按你实际SteamLibrary路径修改):
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>tutorialmod</artifactId> <version>0.1.0</version> <packaging>jar</packaging> <name>Tutorial Mod</name> <description>A minimal SlayTheSpire mod</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <!-- ⚠️ 关键:此处必须是你SteamLibrary的实际路径 --> <steam.path>D:\SteamLibrary\steamapps</steam.path> </properties> <dependencies> <!-- BaseMod:提供卡牌/角色/事件等核心扩展能力 --> <dependency> <groupId>basemod</groupId> <artifactId>basemod</artifactId> <version>5.38.0</version> <scope>system</scope> <systemPath>${steam.path}/workshop/content/646570/1605833019/BaseMod.jar</systemPath> </dependency> <!-- ModTheSpire:Mod加载器本体,所有Mod的入口 --> <dependency> <groupId>modthespire</groupId> <artifactId>modthespire</artifactId> <version>2.6.0</version> <scope>system</scope> <systemPath>${steam.path}/workshop/content/646570/1605060445/ModTheSpire.jar</systemPath> </dependency> <!-- SlayTheSpire:游戏本体,用于继承Card、Monster等类 --> <dependency> <groupId>slaythespire</groupId> <artifactId>slaythespire</artifactId> <version>2.7.0</version> <scope>system</scope> <systemPath>${steam.path}/common/SlayTheSpire/desktop-1.0.jar</systemPath> </dependency> </dependencies> <build> <finalName>tutorialmod</finalName> <plugins> <!-- 编译插件:强制使用Java 8,因STP仅兼容JDK 8 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <!-- 自动复制jar到Mods目录:省去手动拖拽 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>1.8</version> <executions> <execution> <phase>package</phase> <configuration> <target> <mkdir dir="${steam.path}/common/SlayTheSpire/mods/" /> <copy file="target/tutorialmod.jar" tofile="${steam.path}/common/SlayTheSpire/mods/tutorialmod.jar" overwrite="true"/> </target> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>

参数说明:

  • <steam.path>:必须与你Steam客户端设置的“Steam Library Folders”路径完全一致(Steam → Settings → Downloads → Steam Library Folders);
  • systemPath中的数字1605833019是BaseMod在创意工坊的ID,若你订阅的是新版,需替换为实际ID(打开steamapps/workshop/content/646570/目录,找最新时间戳文件夹);
  • <maven.compiler.source>和<target>必须为1.8,STP不支持JDK 11+,否则编译通过但运行时报UnsupportedClassVersionError。

粘贴后,IDEA右下角会弹出“Import Changes”,点击它——此时Maven开始解析依赖。等待右下角Maven面板出现“tutorialmod [pom.xml]”且无红色波浪线,即表示BaseMod等jar已被成功识别。若仍报错,请立即进入第2.3节“避坑指南”。

2.2 创建标准源码目录结构与Mod元数据文件

Maven要求严格的目录约定,任何偏差都会导致编译失败或Mod无法加载:

  1. 在项目根目录D:\stsmods\tutorial下,手动创建以下嵌套文件夹:
    src/ main/ java/ resources/ test/ java/
  2. 在src/main/resources/目录下,右键 → New → File,命名为ModTheSpire.json(注意大小写和扩展名),内容如下:
{ "modid": "tutorialmod", "name": "Tutorial Mod", "author_list": ["dev"], "description": "First mod built with Maven and IDEA", "version": "0.1.0", "sts_version": "2.7.0", "mts_version": "2.6.0", "dependencies": ["basemod"] }

关键字段解释:

  • "modid":必须全小写、无空格、无特殊字符,且与pom.xml中<artifactId>完全一致(这里是tutorialmod);
  • "sts_version":填你当前游戏版本号(启动STP → 点击左上角“About” → 查看“Slay the Spire Version”);
  • "mts_version":填你安装的ModTheSpire版本(同上,看“ModTheSpire Version”);
  • "dependencies":声明依赖的其他Mod ID,basemod是必填项,否则BaseMod.subscribe(this)会抛空指针。

注意:ModTheSpire.json必须放在src/main/resources/下,不能放在src/main/java/或项目根目录。Maven打包时会自动将其复制到jar包根目录,STP启动时正是读取这个路径下的json来注册Mod。

2.3 避坑:90%的“找不到类”错误都源于这四个路径陷阱

现象 → 原因 → 解决,全是血泪经验:

  1. 现象:IDEA中import basemod.BaseMod;标红,提示Cannot resolve symbol 'basemod'
    原因:pom.xml中<systemPath>指向的BaseMod.jar路径错误,或该jar文件根本不存在(订阅未完成/Steam未更新)
    解决:打开文件管理器,导航至${steam.path}/workshop/content/646570/,确认存在一个以数字命名的子目录(如1605833019),且其内部有BaseMod.jar。若无,请打开Steam → 库 → 右键SlayTheSpire → 属性 → 创意工坊 → 订阅BaseMod并等待下载完成(可能需重启Steam)。

  2. 现象:mvn compile报错[ERROR] Failed to execute goal ... Compilation failure ... package basemod does not exist
    原因:Maven未识别systemscope依赖,常见于IDEA未正确导入pom.xml,或<properties>中<steam.path>路径含中文或空格
    解决:检查pom.xml中<steam.path>值是否为纯英文路径(如D:\SteamLibrary),若为D:\我的游戏\SteamLibrary,请将SteamLibrary迁移到英文路径;然后在IDEA中右键pom.xml → “Maven → Reload project”。

  3. 现象:mvn package成功,但STP中Mod列表不显示,或显示灰色不可勾选
    原因:ModTheSpire.json文件名错误(如写成modthespire.json或ModTheSpire.JSON),或文件编码不是UTF-8无BOM
    解决:用记事本打开ModTheSpire.json→ 另存为 → 编码选择“UTF-8”,取消勾选“UTF-8 with BOM”→ 文件名确认为全小写modthespire.json?不,必须是首字母大写的ModTheSpire.json(STP严格区分大小写)。

  4. 现象:游戏启动后STP控制台报java.lang.NoClassDefFoundError: com/evacipated/cardcrawl/modthespire/lib/SpireInitializer
    原因:pom.xml中ModTheSpire.jar的<systemPath>指向了错误版本,或该jar被杀毒软件误删
    解决:进入steamapps/workshop/content/646570/,找到最新日期的文件夹(如1605060445),检查其内部是否有ModTheSpire.jar。若无,重新订阅ModTheSpire;若有,复制其完整路径(含.jar)粘贴到pom.xml对应<systemPath>中。


3. 编写第一个可运行的Mod:从空壳类到控制台输出,理解@SpireInitializer的触发时机

现在你的项目骨架已就绪,但还只是个“空壳”——没有Java类,没有初始化逻辑,STP加载时只会默默跳过。本节带你写出第一行真正被STP执行的代码,并精确控制它在哪个时刻打印日志。这不是Hello World,而是验证Mod生命周期的探针。

3.1 创建主Mod类:package、class、@SpireInitializer三位一体

在src/main/java/目录下,手动创建包(package):右键 → New → Package,命名为tutorialmod(全小写,与pom.xml中<artifactId>一致)。
在该包内,右键 → New → Java Class,命名为TutorialMod(首字母大写,符合Java类命名规范)。

粘贴以下代码:

package tutorialmod; import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer; import com.megacrit.cardcrawl.core.CardCrawlGame; // @SpireInitializer 标注此class为STP的初始化入口 @SpireInitializer public class TutorialMod { // 构造函数:当STP创建该Mod实例时调用 public TutorialMod() { System.out.println("[TutorialMod] Constructor called at " + System.currentTimeMillis()); } // 静态初始化方法:STP在加载Mod时自动调用此方法 // 注意:必须是public static void,且方法名必须为initialize() public static void initialize() { System.out.println("[TutorialMod] initialize() method triggered at " + System.currentTimeMillis()); // 创建实例,触发构造函数 new TutorialMod(); // 验证BaseMod是否可用:尝试获取游戏实例 if (CardCrawlGame.player != null) { System.out.println("[TutorialMod] CardCrawlGame is ready!"); } else { System.out.println("[TutorialMod] CardCrawlGame not initialized yet (normal during load)"); } } }

关键逻辑说明:

  • @SpireInitializer是STP的“门把手”,没有它,STP根本不会扫描这个类;
  • initialize()方法是STP的硬性约定:必须是public static void,且方法名必须为initialize,否则STP找不到入口;
  • new TutorialMod()这行代码至关重要——它触发构造函数,证明你的类已被JVM加载并实例化;
  • CardCrawlGame.player的判空检查,是为了让你理解STP加载顺序:initialize()在游戏主循环启动前执行,此时player对象尚未创建,所以返回null是正常现象。

3.2 编译并验证:用mvn package生成jar,观察STP控制台实时输出

  1. 在IDEA底部,打开“Maven”工具窗口(若未显示,View → Tool Windows → Maven);
  2. 展开你的项目 → Lifecycle → 双击package(或在终端执行mvn clean package);
  3. 观察控制台输出:若看到[INFO] BUILD SUCCESS,则进入下一步;
  4. 启动Steam → 启动SlayTheSpire → 在启动选项中选择“With ModTheSpire”;
  5. STP启动后,在Mod列表中勾选BaseMod、ModTheSpire和你的Tutorial Mod,点击“Play”;
  6. 紧盯STP右下角的黑色控制台窗口(不是游戏窗口!),你会看到类似输出:
[TutorialMod] initialize() method triggered at 1712345678901 [TutorialMod] Constructor called at 1712345678902 [TutorialMod] CardCrawlGame not initialized yet (normal during load)

这意味着什么?
你的代码已成功注入STP进程,initialize()在STP解析完Mod元数据后立即执行,构造函数紧随其后。此时Mod已“活”在游戏内存中,只是还没挂载任何游戏事件监听。

重要技巧:STP控制台默认只显示最近200行日志。若想查看更多,右键控制台 → “Save Log As...”,保存为txt后用编辑器搜索关键词。这是排查Mod加载失败的唯一可靠途径。

3.3 深度解析:为什么必须用static initialize(),而不是main()?

新手常问:“为什么不能像普通Java程序一样写public static void main(String[] args)?”
答案是:STP不是你的程序,你是它的插件。STP启动时,JVM已运行,它通过Java Agent技术动态注入字节码,然后反射调用你标注了@SpireInitializer的类的initialize()方法。整个过程不经过main入口,main方法对STP完全不可见。

你可以把initialize()理解为STP的“回调函数”:

  • STP加载你的jar → 扫描所有@SpireInitializer类 → 反射调用其initialize()静态方法 → 你的代码获得执行权。
    这就是为什么initialize()必须是static:它需要在不创建对象实例的前提下被直接调用。而构造函数TutorialMod()则是在initialize()内部显式new出来的,用于执行实例级初始化逻辑(如订阅事件、注册卡牌等)。

4. 实现首个游戏内功能:监听抽卡事件并输出卡名,掌握PostDrawSubscriber接口

空壳Mod验证成功后,下一步是让它与游戏世界互动。杀戮尖塔Mod的核心机制是“事件监听”——当游戏发生某个行为(如抽卡、受伤、战斗结束),STP会广播事件,你的Mod通过实现特定接口接收并响应。本节实现最基础的PostDrawSubscriber,监听玩家每抽一张卡就打印其名称,这是所有卡牌Mod的起点。

4.1 修改主类:实现PostDrawSubscriber接口并订阅事件

打开src/main/java/tutorialmod/TutorialMod.java,将代码替换为:

package tutorialmod; import basemod.BaseMod; import basemod.interfaces.PostDrawSubscriber; import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer; import com.megacrit.cardcrawl.cards.AbstractCard; import com.megacrit.cardcrawl.core.CardCrawlGame; @SpireInitializer public class TutorialMod implements PostDrawSubscriber { // 实现接口 public TutorialMod() { // 在构造函数中订阅事件,确保每次实例化都绑定 BaseMod.subscribe(this); System.out.println("[TutorialMod] Subscribed to PostDraw event"); } public static void initialize() { System.out.println("[TutorialMod] initialize() triggered"); new TutorialMod(); // 创建实例并自动订阅 } // 重写接口方法:当玩家抽卡后,STP自动调用此方法 @Override public void receivePostDraw(AbstractCard card) { // card参数即为刚抽到的卡牌对象 String cardName = (card == null) ? "NULL_CARD" : card.name; System.out.println("[PostDraw] Drew card: '" + cardName + "' | ID: " + card.cardID); // 进阶:过滤掉非玩家抽卡(如敌人抽卡) if (card.owner != null && card.owner.isPlayer) { System.out.println("[PostDraw] ✓ Player drew: " + cardName); } } }

关键点解析:

  • implements PostDrawSubscriber:声明该类能响应“抽卡后”事件;
  • BaseMod.subscribe(this):将当前实例注册到BaseMod事件总线,this指代TutorialMod对象;
  • receivePostDraw(AbstractCard card):接口规定的回调方法名,参数card是STP传入的刚抽到的卡牌对象;
  • card.owner.isPlayer:杀戮尖塔中所有实体(玩家、敌人、Boss)都继承自AbstractCreature,owner字段标识该卡属于谁,此判断确保只响应玩家抽卡。

4.2 重新编译并测试:在真实游戏中观察抽卡日志流

  1. 再次执行mvn clean package(clean确保旧class被清除);
  2. 启动STP,勾选你的Mod,进入任意一场战斗(推荐“Neow's Trial”快速开始);
  3. 在战斗中点击“End Turn”,观察STP控制台——每当玩家抽卡,你会看到:
[PostDraw] Drew card: 'Strike' | ID: Strike_Red [PostDraw] ✓ Player drew: Strike [PostDraw] Drew card: 'Defend' | ID: Defend_Red [PostDraw] ✓ Player drew: Defend

如果没看到输出?按此顺序排查:

  • 检查STP控制台是否被其他Mod日志刷屏(如BaseMod自身日志),尝试禁用其他Mod只留你的;
  • 确认receivePostDraw方法签名完全匹配:public void receivePostDraw(AbstractCard card),少一个public或参数类型写成Card都会导致STP无法识别;
  • 在initialize()中添加System.out.println("BaseMod version: " + BaseMod.VERSION);,若此行不输出,说明BaseMod未正确加载。

4.3 接口原理:STP事件总线如何将游戏行为分发给Mod

PostDrawSubscriber只是STP定义的数十个事件接口之一。其底层机制是典型的观察者模式:

  1. 游戏本体在AbstractDungeon.player.drawCard()末尾,插入一行STP钩子代码:
    BaseMod.publishPostDraw(card);
  2. BaseMod维护一个List<PostDrawSubscriber>列表,遍历调用每个订阅者的receivePostDraw(card);
  3. 你的TutorialMod因BaseMod.subscribe(this)被加入该列表,故能收到通知。

为什么不用继承?因为一个Mod可能同时监听多个事件(如抽卡+受伤+战斗结束),Java单继承限制下,接口是唯一解耦方案。你完全可以这样写:
public class TutorialMod implements PostDrawSubscriber, OnPlayerDamagedSubscriber, PostBattleSubscriber


5. 调试与排错实战:用日志定位Mod加载失败、事件不触发、变量为空三大高频问题

Mod开发中最耗时的不是写代码,而是读懂STP控制台里那一长串红色堆栈。本节不讲理论,只给可立即执行的诊断脚本和三张速查表,覆盖95%的线上问题。

5.1 日志分级诊断法:从STP启动到游戏内行为的四层断点

STP日志按时间顺序分为四个阶段,每阶段对应不同问题域:

阶段触发时机关键日志特征典型问题快速验证命令
L1:Mod加载期STP启动后、游戏加载前Loading mod: Tutorial Mod+Mod loaded successfullyModTheSpire.json路径错误、modid不匹配检查mods/目录下jar文件名是否与modid一致
L2:初始化期initialize()执行时[TutorialMod] initialize() triggeredpom.xml依赖缺失、JDK版本错误在initialize()开头加System.out.println("JDK: "+System.getProperty("java.version"));
L3:事件订阅期BaseMod.subscribe(this)后Subscribed to PostDraw eventBaseMod未启用、接口实现不完整在STP界面确认BaseMod已勾选且状态为绿色
L4:事件触发期游戏内行为发生时[PostDraw] Drew card: 'Strike'事件条件不满足(如card.owner.isPlayer==false)在receivePostDraw开头加System.out.println("Raw card: "+card);

操作示例:当你发现L4无输出,但L1-L3正常,立即在receivePostDraw第一行插入:

System.out.println("[DEBUG] receivePostDraw called with card=" + card + ", owner=" + (card!=null?card.owner:null));

若输出card=null,说明STP传入了空对象,需检查游戏版本兼容性;若owner=null,说明该卡不属于任何实体,属正常边界情况。

5.2 变量空指针速查表:杀戮尖塔常用对象的非空断言模板

杀戮尖塔对象链极深(player.hand.get(0).upgraded),新手常因未判空导致Mod崩溃。以下是高频空指针场景及防御代码:

对象危险访问安全写法说明
AbstractCard cardcard.nameif (card != null) System.out.println(card.name);抽卡事件中card可能为null(如抽到无效卡)
AbstractPlayer playerplayer.hand.groupif (player != null && player.hand != null) player.hand.group.forEach(...);player在initialize()中必为null,只能在PostUpdateSubscriber后访问
AbstractMonster monstermonster.hbif (monster != null && monster.hb != null) monster.hb.move(monster.drawX, monster.drawY);敌人血条hb在初始化阶段未创建,需在render()后才可用

通用防御模式:

// 用Optional风格封装,避免层层嵌套if private String safeCardName(AbstractCard card) { return (card != null && card.name != null) ? card.name : "Unknown"; }

5.3 事件不触发终极排查:三步强制验证法

当确定代码无语法错误,但事件就是不触发,执行以下三步:

  1. 验证STP版本兼容性:
    在pom.xml中,将<mts_version>改为当前STP版本(如3.25.0),并确认ModTheSpire.json中"mts_version"与之完全一致。版本不匹配会导致STP跳过事件注册。

  2. 检查事件接口是否被STP识别:
    在TutorialMod类上添加@SuppressWarnings("unused"),然后在receivePostDraw方法上悬停鼠标——若IDEA显示“Method is never used”,说明STP未扫描到该实现,立即检查类名是否与ModTheSpire.json中"modid"一致(大小写、下划线)。

  3. 强制触发事件调试:
    在receivePostDraw中加入强制日志+异常抛出:

    @Override public void receivePostDraw(AbstractCard card) { System.out.println("[FORCE] PostDraw received! Thread: " + Thread.currentThread().getName()); try { throw new RuntimeException("PostDraw breakpoint"); } catch (Exception e) { e.printStackTrace(); // 此行会强制在STP控制台打印堆栈,证明方法确被调用 } }

    若此堆栈未出现,100%是事件注册失败;若出现,则问题在你的业务逻辑中。


6. 进阶技巧:用Maven Profile管理多游戏版本,告别“改一次pom,编译十次”的玄学操作

你很快会遇到这个问题:在STP 2.7.0上开发的Mod,同事用STP 3.0.0测试时崩溃,报错NoSuchMethodError。因为desktop-1.0.jar的API在不同版本间有微小变更(如CardGroup.shuffle()方法签名调整)。每次切换版本都要手动改pom.xml里的<steam.path>和<sts_version>,极易出错。解决方案:用Maven Profile定义多套构建配置,一条命令切换目标环境。

6.1 在pom.xml中定义Profile:为STP 2.7.0和3.0.0创建独立配置

在pom.xml的</project>闭合标签前,添加以下Profile配置:

<profiles> <!-- Profile for SlayTheSpire v2.7.0 (stable) --> <profile> <id>sts-2.7.0</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <steam.path>D:\SteamLibrary\steamapps</steam.path> <sts.version>2.7.0</sts.version> <mts.version>2.6.0</mts.version> <baseMod.id>1605833019</baseMod.id> <mts.id>1605060445</mts.id> </properties> <dependencies> <dependency> <groupId>basemod</groupId> <artifactId>basemod</artifactId> <version>5.38.0</version> <scope>system</scope> <systemPath>${steam.path}/workshop/content/646570/${baseMod.id}/BaseMod.jar</systemPath> </dependency> <dependency> <groupId>modthespire</groupId> <artifactId>modthespire</artifactId> <version>${mts.version}</version> <scope>system</scope> <systemPath>${steam.path}/workshop/content/646570/${mts.id}/ModTheSpire.jar</systemPath> </dependency> <dependency> <groupId>slaythespire</groupId> <artifactId>slaythespire</artifactId> <version>${sts.version}</version> <scope>system</scope> <systemPath>${steam.path}/common/SlayTheSpire/desktop-1.0.jar</systemPath> </dependency> </dependencies> </profile> <!-- Profile for SlayTheSpire v3.0.0 (beta) --> <profile> <id>sts-3.0.0</id> <properties> <steam.path>E:\SteamLibrary\steamapps</steam.path> <sts.version>3.0.0</sts.version> <mts.version>3.25.0</mts.version> <baseMod.id>1923456789</baseMod.id> <mts.id>1923456788</mts.id> </properties> <dependencies> <dependency> <groupId>basemod</groupId> <artifactId>basemod</artifactId> <version>6.0.0</version> <scope>system</scope> <systemPath>${steam.path}/workshop/content/646570/${baseMod.id}/BaseMod.jar</systemPath> </dependency> <dependency> <groupId>modthespire</groupId> <artifactId>modthespire</artifactId> <version>${mts.version}</version> <scope>system</scope> <systemPath>${steam.path}/workshop/content/646570/${mts.id}/ModTheSpire.jar</systemPath> </dependency> <dependency> <groupId>slaythespire</groupId> <artifactId>slaythespire</artifactId> <version>${sts.version}</version> <scope>system</scope> <systemPath>${steam.path}/common/SlayTheSpire/desktop-1.0.jar</systemPath> </dependency> </dependencies> </profile> </profiles>

参数说明:

  • <id>sts-2.7.0</id>:Profile唯一标识,后续用-P参数激活;
  • <activeByDefault>true</activeByDefault>:设为默认激活,mvn package即使用此配置;
  • ${baseMod.id}等占位符:将路径中易变的创意工坊ID提取为变量,避免重复修改。

6.2 使用Profile编译:一条命令切换目标环境

  • 编译适配STP 2.7.0的Mod:
    mvn clean package(自动使用sts-2.7.0Profile)

  • 编译适配STP 3.0.0的Mod:
    mvn clean package -Psts-3.0.0

  • 查看当前激活的Profile:
    mvn help:active-profiles

效果:执行-Psts-3.0.0后,Maven会自动替换所有${xxx}变量,使用E盘路径、3.0.0版本jar,并生成target/tutorialmod-3.0.0.jar(Profile名会追加到jar名后)。你无需打开pom.xml,无需记忆路径,命令即文档。

6.3 终极验证:用JUnit写一个冒烟测试,确保Mod基础功能不退化

在src/test/java/下创建包tutorialmod.test,新建类ModSmokeTest:

package tutorialmod.test; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class ModSmokeTest { @Test public void testModIdMatchesArtifactId() { // 验证ModTheSpire.json中的modid与pom.xml中artifactId一致 // (实际项目中可读取json文件解析,此处简化为字符串断言) String pomArtifactId = "tutorialmod"; String jsonModId = "tutorialmod"; // 从ModTheSpire.json读取 assertEquals(pomArtifactId, jsonModId, "Mod ID mismatch: pom.xml artifactId must equal ModTheSpire.json modid"); } @Test public void testInitializeMethodExists() { try { Class.forName("tutorialmod.TutorialMod") .getMethod("initialize"); } catch ( <p> <a href="https://download.csdn.net/download/weixin_35775988/86313593" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进