Skip to content

Commit 884d5d6

Browse files
author
REME
committed
2026-04-02 重构、改名、联动、保存
1 parent bbff693 commit 884d5d6

15 files changed

Lines changed: 947 additions & 781 deletions

File tree

Basics/01 - 环境配置/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,8 @@ public class Entry
169169

170170
## 运行并验证
171171

172-
运行游戏。第一次会提示是否开启mod,选择是,然后游戏会关闭,打开第二次即可,如果右下角显示“已加载模组”即加载成功。如果发现存档丢失,看下一章。
172+
运行游戏。第一次会提示是否开启mod,选择是,然后游戏会关闭,打开第二次即可,如果右下角显示“已加载模组”即加载成功。如果发现存档丢失,看下一章。
173+
174+
## 不启动Godot打包(可选)
175+
176+
Godot支持命令行导出pck(首先你需要添加一个导出配置),例如使用终端命令:`"{你的godot.exe的路径}" --headless --export-pack "{你的导出配置的名字,例如Windows Desktop}" "{杀戮尖塔根目录}/mods/{你的modid}/{你的modid}.pck"`,参考 https://docs.godotengine.org/zh-cn/4.x/tutorials/editor/command_line_tutorial.html#exporting 。你可以把这个命令保存成一个cmd或者csproj里的target,自行搜索相关配置说明。

Basics/02 - 安装、看源码、修改/README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ https://github.com/GDRETools/gdsdecomp
4545

4646
开启了模组,按下`~`(tab上方那个键)即可打开控制台。输入`help`即可查看命令。例如`card SURVIVOR`是把一张生存者加入手中。
4747

48+
你可以查询一个命令的帮助,使用`help card`等。
49+
4850
## DEBUG
4951

5052
尖塔根目录有许多`launch_xxx.bat`,选择一个合适的,右键记事本编辑,在其中加一个`--log`,例如`@echo off
@@ -54,4 +56,20 @@ https://github.com/GDRETools/gdsdecomp
5456

5557
## 本地联机测试
5658

57-
复制出两个新的`bat`,其中一个添加`--fastmp=host`参数,作为主机,另一个添加`--fastmp=join -clientId=1001`参数,作为非主机玩家。当然你可以添加更多,记得修改`clientId`
59+
复制出两个新的`bat`,其中一个添加`--fastmp=host`参数,作为主机,另一个添加`--fastmp=join -clientId=1001`参数,作为非主机玩家。当然你可以添加更多,记得修改`clientId`
60+
61+
如果你打完一层遇到保存问题,记得以管理员模式启动bat。
62+
63+
## 项目改名
64+
65+
<b>以下修改的都建议使用一个名字</b>。
66+
67+
* 打开`project.godot`,修改`config/name`以及`project/assembly_name`
68+
69+
*`{modid}.csproj`的名字修改成你想要的。
70+
71+
*`{modid}.json`的名字修改成你想要的。以及里面的`id`部分。
72+
73+
*`{modid}.sln`的名字修改成你想要的。
74+
75+
* 然后重新打包。不要忘记把你之前名字的mod删了。
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
## 代码
2+
3+
创建一个新的`Cards`文件夹方便管理,并创建新的`cs`文件,例如`TestCard.cs`
4+
5+
```csharp
6+
using BaseLib.Abstracts;
7+
using BaseLib.Utils;
8+
using MegaCrit.Sts2.Core.Commands;
9+
using MegaCrit.Sts2.Core.Entities.Cards;
10+
using MegaCrit.Sts2.Core.GameActions.Multiplayer;
11+
using MegaCrit.Sts2.Core.Localization.DynamicVars;
12+
using MegaCrit.Sts2.Core.Models.CardPools;
13+
using MegaCrit.Sts2.Core.ValueProps;
14+
15+
namespace Test.Scripts.Cards;
16+
17+
// 加入哪个卡池
18+
[Pool(typeof(ColorlessCardPool))]
19+
public class TestCard : CustomCardModel
20+
{
21+
// 基础耗能
22+
private const int energyCost = 1;
23+
// 卡牌类型
24+
private const CardType type = CardType.Attack;
25+
// 卡牌稀有度
26+
private const CardRarity rarity = CardRarity.Common;
27+
// 目标类型(AnyEnemy表示任意敌人)
28+
private const TargetType targetType = TargetType.AnyEnemy;
29+
// 是否在卡牌图鉴中显示
30+
private const bool shouldShowInCardLibrary = true;
31+
32+
// 卡牌的基础属性(例如这里是12点伤害)
33+
protected override IEnumerable<DynamicVar> CanonicalVars => [new DamageVar(12, ValueProp.Move)];
34+
35+
public TestCard() : base(energyCost, type, rarity, targetType, shouldShowInCardLibrary)
36+
{
37+
}
38+
39+
// 打出时的效果逻辑
40+
protected override async Task OnPlay(PlayerChoiceContext choiceContext, CardPlay cardPlay)
41+
{
42+
await DamageCmd.Attack(DynamicVars.Damage.BaseValue) // 造成伤害,数值来源于卡牌的基础伤害属性
43+
.FromCard(this) // 伤害来源于这张卡牌
44+
.Targeting(cardPlay.Target) // 伤害目标是玩家选择的目标
45+
.Execute(choiceContext);
46+
}
47+
48+
// 升级后的效果逻辑
49+
protected override void OnUpgrade()
50+
{
51+
DynamicVars.Damage.UpgradeValueBy(4); // 升级后增加4点伤害
52+
}
53+
}
54+
```
55+
56+
* `CanonicalVars`翻译是“规范值”,指卡牌的基础数值。添加一个`DamageVar`意为指定卡牌的基础伤害是多少,例如这里是`12`
57+
58+
* `ValueProp`表示数值的属性,例如`ValueProp.Move`表示是通过卡牌造成的伤害/格挡,`ValueProp.Unpowered`表示不受修正影响(如力量等),`ValueProp.Unblockable`表示伤害不可被格挡,`ValueProp.SkipHurtAnim`表示跳过受伤动画。这是一个bitflag类型的枚举,你可以进行组合,例如`ValueProp.Unblockable | ValueProp.Unpowered`,不可被格挡也不受修正影响。
59+
60+
* 尖塔2使用了`async``await`来控制效果逻辑顺序执行,比如选择一张牌时就一直`await`不让后续代码执行,和尖塔1的`action`类似的生态位。此处的`OnPlay`中写了一个造成单体伤害的指令。
61+
62+
* 想做什么样的卡牌,看原版代码哪张有类似的效果,参考即可。
63+
64+
* 添加一个`Pool`的attribute,并指定要添加的颜色卡池,然后会自动注册。
65+
* 继承`CustomCardModel`而不是`CardModel`
66+
* <b>注意</b>:通过`baselib`添加卡牌,其id会变成`{命名空间第一段大写}-{原卡牌id}`,例如`namespace Test.Scripts;``TEST`,原始卡牌id为`TEST-CARD`,是`TestCard`的大写snake-case,最后变成`TEST-TEST_CARD`
67+
68+
## 卡图
69+
70+
可以通过在卡牌类中添加一个表达式属性来添加卡牌,这样的话可以任意指定位置:`public override string PortraitPath => $"res://{modid}/images/cards/{Id.Entry.ToLowerInvariant()}.png";`
71+
如果这样,那么路径就是`test/images/cards/test-test_card.png`(是你类名的`snake_case`命名风格加前缀,例如`TestCard`即为`test-test_card`)。当然按你的喜好组织资源路径也可。
72+
73+
> 或者你也可以使用`public override string PortraitPath => $"res://{modid}/images/cards/{nameof(TestCard)}.png";`,这样更方便,卡图名字设置为`TestCard.png`就行。
74+
75+
`modId`即为你`{modId}.json`中填写的。<b>不是你的根目录,而是一个新文件夹。</b>
76+
77+
78+
卡图任意尺寸都可,且不需要裁剪,官方使用的尺寸是普通卡1000x760,先古卡606x852。
79+
80+
```csharp
81+
public class TestCard : TestCardModel
82+
{
83+
private const int energyCost = 1;
84+
private const CardType type = CardType.Attack;
85+
private const CardRarity rarity = CardRarity.Common;
86+
private const TargetType targetType = TargetType.AnyEnemy;
87+
private const bool shouldShowInCardLibrary = true;
88+
89+
protected override IEnumerable<DynamicVar> CanonicalVars => [new DamageVar(12, ValueProp.Move)];
90+
91+
// 添加这一行,指定卡牌立绘路径,这里是test/images/cards/TestCard.png
92+
public override string PortraitPath => $"res://test/images/cards/{nameof(TestCard)}.png";
93+
94+
public TestCard() : base(energyCost, type, rarity, targetType, shouldShowInCardLibrary)
95+
{
96+
}
97+
}
98+
```
99+
100+
![示例卡图](../../../images/image10.png)
101+
102+
你也可以通过新增一个`abstract`类,避免每张卡都写一遍卡图路径,并且方便管理一些自定义功能。
103+
104+
```csharp
105+
public abstract class TestCardModel : CardModel
106+
{
107+
public override string PortraitPath => $"res://test/images/cards/{GetType().Name}.png";
108+
109+
public TestCardModel(int energyCost, CardType type, CardRarity rarity, TargetType targetType, bool shouldShowInCardLibrary) : base(energyCost, type, rarity, targetType, shouldShowInCardLibrary)
110+
{
111+
}
112+
}
113+
114+
public class TestCard : TestCardModel {}
115+
```
116+
117+
## 文本
118+
119+
此外还需要本地化文件。创建一个`{modId}/localization/{Language}/cards.json`
120+
* `modId`即为你`{modId}.json`中填写的。<b>不是你的根目录,而是一个新文件夹。</b>
121+
* `Language`可以写`zhs`表示简体中文。填写`{CardId}.title`(卡牌名)和`{CardId}.description`(卡牌描述):
122+
123+
```json
124+
{
125+
"TEST-TEST_CARD.title": "测试卡牌",
126+
"TEST-TEST_CARD.description": "造成{Damage:diff()}点伤害。"
127+
}
128+
```
129+
130+
编译打包`dll``pck`后打开游戏。如果你在对应池子中看到卡牌说明成功了。如果没有任何卡牌(或者一张在左上角的卡牌)说明出问题了。
131+
132+
`~`打开控制台输入`card TEST-TEST_CARD`获得这张卡。
133+
134+
![示例卡牌](../../../images/image11.png)
135+
136+
## 最终项目参考
137+
138+
如果报错,回头看看。最终项目结构参考:
139+
140+
```
141+
Test (你的项目文件夹)
142+
├── Scripts (你的脚本文件夹,随意)
143+
│ ├── TestCard.cs
144+
│ └── Entry.cs
145+
└── Test (不要忘了这一层文件夹,是你的modid)
146+
├── images
147+
│ └── cards
148+
│ └── test-test_card.png
149+
└── localization
150+
└── zhs
151+
└── cards.json
152+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
* 要使用此功能,需要先放一张图片到`{modId}\mod_image.png`作为mod图标,尺寸任意,否则会由于报错不显示配置。
2+
* 添加一个继承`SimpleModConfig`(或者是`ModConfig`如果你想要更复杂的设置)的类,在其中添加`public static bool`变量。支持`bool``double``enum``int``string`
3+
* 在初始化函数调用`ModConfigRegistry.Register`。字符串写你的`modId`
4+
5+
```csharp
6+
[ModInitializer("Init")]
7+
public class Entry
8+
{
9+
public static void Init()
10+
{
11+
ModConfigRegistry.Register("test", new ModConfig());
12+
}
13+
}
14+
15+
public class ModConfig : SimpleModConfig
16+
{
17+
public static bool Test1 { get; set; } = true;
18+
public static bool Test2 { get; set; } = false;
19+
public static bool Test3 { get; set; } = true;
20+
}
21+
```
22+
23+
![示例配置](../../../images/image12.png)
24+
25+
更多请参考`baselib``BaseLibConfig`类。
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
和添加卡牌类似。先新建一个类。
2+
3+
```csharp
4+
// 加入哪个遗物池,此处为通用
5+
[Pool(typeof(SharedRelicPool))]
6+
public class TestRelic : CustomRelicModel
7+
{
8+
// 稀有度
9+
public override RelicRarity Rarity => RelicRarity.Common;
10+
11+
// 遗物的数值。替换本地化中的{Cards}。
12+
protected override IEnumerable<DynamicVar> CanonicalVars => [new CardsVar(1)];
13+
14+
// 小图标
15+
public override string PackedIconPath => $"res://test/images/relics/{Id.Entry.ToLowerInvariant()}.png";
16+
// 轮廓图标
17+
protected override string PackedIconOutlinePath => $"res://test/images/relics/{Id.Entry.ToLowerInvariant()}.png";
18+
// 大图标
19+
protected override string BigIconPath => $"res://test/images/relics/{Id.Entry.ToLowerInvariant()}.png";
20+
21+
public override async Task AfterPlayerTurnStart(PlayerChoiceContext choiceContext, Player player)
22+
{
23+
// 这里的DynamicVars.Cards.IntValue为上面设置的CardsVar的数值。
24+
await CardPileCmd.Draw(choiceContext, DynamicVars.Cards.IntValue, player);
25+
}
26+
}
27+
```
28+
29+
然后放一张图片`test/images/relics/test_relic.png`。路径不一定是`test`,组织风格自定义,参考上面卡图部分。这里偷懒三张图片用了一样的,可以自己修改。
30+
31+
![示例遗物](../../../images/image13.png)
32+
33+
然后写一个本地化文件,`{modId}/localization/{Language}/relics.json`
34+
35+
```json
36+
{
37+
"TEST-TEST_RELIC.title": "测试遗物",
38+
"TEST-TEST_RELIC.description": "每回合开始时,抽[blue]{Cards}[/blue]张牌。",
39+
"TEST-TEST_RELIC.flavor": "觉得很眼熟?"
40+
}
41+
```

0 commit comments

Comments
 (0)