Skip to content

Commit 5fa4106

Browse files
author
REME
committed
2026-04-11 附魔、事件、启动参数
1 parent bc64adc commit 5fa4106

13 files changed

Lines changed: 346 additions & 23 deletions

File tree

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
## 环境配置
2-
31
以防你有网络问题下载工具:https://pan.baidu.com/s/1yuxPkDpCV8EVLkDubqiirg?pwd=apar
42

3+
## 编程基础
4+
5+
阅读本教程你至少需要:
6+
7+
* C#语言基础(或者其他语言的基础)
8+
* json文本基础
9+
* 使用Godot编辑器的简单功能
10+
* 图片编辑处理能力
11+
* 懂得使用电脑
12+
513
## 其他教程和mod模板
614

715
https://github.com/Alchyr/ModTemplate-StS2

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,18 @@ https://github.com/GDRETools/gdsdecomp
4747

4848
你可以查询一个命令的帮助,使用`help card`等。
4949

50-
## DEBUG
50+
## 查看log
5151

52-
尖塔根目录有许多`launch_xxx.bat`,选择一个合适的,右键记事本编辑,在其中加一个`--log`,例如`@echo off
53-
"%~dp0SlayTheSpire2.exe" --log --rendering-driver opengl3 %*`。
52+
方法任选:
5453

55-
然后在根目录创建一个`steam_appid.txt`,里面写`2868840`,然后双击修改的bat文件运行即可以一个能输出log的命令行的方式打开游戏。或者添加`--force-steam=off`参数。
54+
* 按下`~`(tab上方那个键)打开控制台,输入`open logs`或者`showlog`(没有baselib的话无效)。
55+
56+
* 尖塔根目录有许多`launch_xxx.bat`,选择一个合适的,右键记事本编辑,在其中加一个`--log`,例如`@echo off
57+
"%~dp0SlayTheSpire2.exe" --log --rendering-driver opengl3 %*`。然后在根目录创建一个`steam_appid.txt`,里面写`2868840`,然后双击修改的bat文件运行即可以一个能输出log的命令行的方式打开游戏。或者添加`--force-steam=off`参数。
5658

5759
## 本地联机测试
5860

59-
复制出两个新的`bat`,其中一个添加`--fastmp=host`参数,作为主机,另一个添加`--fastmp=join -clientId=1001`参数,作为非主机玩家。当然你可以添加更多,记得修改`clientId`
61+
复制出两个新的`bat`,其中一个添加`--fastmp=host`参数,作为主机,另一个添加`--fastmp=join --clientId=1001`参数,作为非主机玩家。当然你可以添加更多,记得修改`clientId`
6062

6163
如果你打完一层遇到保存问题,记得以管理员模式启动bat。
6264

Basics/03 - BaseLib接口/03 - 添加新遗物/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ public class TestRelic : CustomRelicModel
1111
// 遗物的数值。替换本地化中的{Cards}。
1212
protected override IEnumerable<DynamicVar> CanonicalVars => [new CardsVar(1)];
1313

14-
// 小图标
14+
// 小图标(原版85x85)
1515
public override string PackedIconPath => $"res://test/images/relics/{Id.Entry.ToLowerInvariant()}.png";
16-
// 轮廓图标
16+
// 轮廓图标(原版85x85)
1717
protected override string PackedIconOutlinePath => $"res://test/images/relics/{Id.Entry.ToLowerInvariant()}.png";
18-
// 大图标
18+
// 大图标(原版256x256)
1919
protected override string BigIconPath => $"res://test/images/relics/{Id.Entry.ToLowerInvariant()}.png";
2020

2121
public override async Task AfterPlayerTurnStart(PlayerChoiceContext choiceContext, Player player)

Basics/03 - BaseLib接口/05 - 添加新能力/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class TestPower : CustomPowerModel
88
// 叠加类型,Counter表示可叠加,Single表示不可叠加
99
public override PowerStackType StackType => PowerStackType.Counter;
1010

11-
// 自定义图标路径,自己指定,或者创建一个基类来统一指定图标路径
11+
// 自定义图标路径。1:1即可。原版游戏大图256x256,小图64x64。
1212
public override string? CustomPackedIconPath => "res://test/powers/test_power.png";
1313
public override string? CustomBigIconPath => "res://test/powers/test_power.png";
1414

Basics/03 - BaseLib接口/08 - 添加充能球/README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class TestOrb : CustomOrbModel
2828
// 球的场景的路径。如果你使用这个,你必须要有一个名称为SpineSkeleton并且是SpineSprite类型的节点
2929
// public override string? CustomSpritePath => "res://test/scenes/test_orb.tscn";
3030
31-
// 可以继承这个并自行搭建场景,这样就没有上述限制。代码上优先使用这个
31+
// 可以继承这个并自行搭建场景,只需父节点是Node2D即可。这样就没有上述限制。代码上优先使用这个
3232
public override Node2D? CreateCustomSprite()
3333
{
3434
return PreloadManager.Cache.GetScene("res://test/scenes/test_orb.tscn").Instantiate<Node2D>();
@@ -67,4 +67,20 @@ public class TestOrb : CustomOrbModel
6767
}
6868
```
6969

70-
![alt text](../../../images/image28.png)
70+
使用`await OrbCmd.Channel<TestOrb>(choiceContext, cardPlay.Card.Owner)`以生成。
71+
72+
![alt text](../../../images/image28.png)
73+
74+
`test_orb.tscn`:
75+
76+
```
77+
[gd_scene load_steps=2 format=3 uid="uid://megsnq8c4cxc"]
78+
79+
[ext_resource type="Texture2D" uid="uid://ddxmxgyyfy8mn" path="res://icon.svg" id="1_voa3m"]
80+
81+
[node name="TestOrb" type="Node2D"]
82+
83+
[node name="Icon" type="Sprite2D" parent="."]
84+
texture = ExtResource("1_voa3m")
85+
86+
```
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
## 简单多阶段事件
2+
3+
首先创建类:
4+
5+
```csharp
6+
using BaseLib.Abstracts;
7+
using MegaCrit.Sts2.Core.Commands;
8+
using MegaCrit.Sts2.Core.Entities.Gold;
9+
using MegaCrit.Sts2.Core.Events;
10+
using MegaCrit.Sts2.Core.GameActions.Multiplayer;
11+
using MegaCrit.Sts2.Core.Localization.DynamicVars;
12+
using MegaCrit.Sts2.Core.Rewards;
13+
using MegaCrit.Sts2.Core.Runs;
14+
using MegaCrit.Sts2.Core.ValueProps;
15+
16+
namespace Test.Scripts;
17+
18+
public sealed class TestEvent : CustomEventModel
19+
{
20+
// 背景图位置
21+
public override string? CustomInitialPortraitPath => "res://images/events/battleworn_dummy.png";
22+
23+
// 设置一些数值
24+
protected override IEnumerable<DynamicVar> CanonicalVars =>
25+
[
26+
new DamageVar(10m, ValueProp.Unblockable | ValueProp.Unpowered),
27+
new GoldVar(60)
28+
];
29+
30+
// 什么时候会遇到。这里的条件是所有玩家的金币都大于等于60
31+
public override bool IsAllowed(RunState runState) => runState.Players.All(p => p.Gold >= DynamicVars.Gold.BaseValue);
32+
33+
// 事件开始前的逻辑。这里是禁止玩家移除药水
34+
protected override Task BeforeEventStarted(bool isPreFinished)
35+
{
36+
Owner!.CanRemovePotions = false;
37+
return Task.CompletedTask;
38+
}
39+
40+
// 事件结束后的逻辑。这里是允许玩家移除药水
41+
protected override void OnEventFinished()
42+
{
43+
Owner!.CanRemovePotions = true;
44+
}
45+
46+
// 生成事件初始选项。这里是两个选项:失去生命值或者失去金币,然后进入选择奖励阶段
47+
protected override IReadOnlyList<EventOption> GenerateInitialOptions() =>
48+
[
49+
Option(TakeDamage),
50+
Option(LoseGold),
51+
];
52+
53+
// 失去生命
54+
private async Task TakeDamage()
55+
{
56+
await CreatureCmd.Damage(new ThrowingPlayerChoiceContext(), Owner!.Creature, DynamicVars.Damage, null, null);
57+
ChooseRewardTypePage();
58+
}
59+
60+
// 失去金币
61+
private async Task LoseGold()
62+
{
63+
await PlayerCmd.LoseGold(DynamicVars.Gold.BaseValue, Owner!, GoldLossType.Stolen);
64+
ChooseRewardTypePage();
65+
}
66+
67+
// 进入事件第二阶段,两个选项:选择药水或者选择卡牌
68+
private void ChooseRewardTypePage()
69+
{
70+
SetEventState(PageDescription("CHOOSE_TYPE"), [
71+
Option(ChoosePotions, "CHOOSE_TYPE"), // 第二个参数代表该选项所在页面
72+
Option(ChooseCards, "CHOOSE_TYPE"),
73+
]);
74+
}
75+
76+
// 选择药水奖励,然后结束事件
77+
private async Task ChoosePotions()
78+
{
79+
await RewardsCmd.OfferCustom(Owner!, [new PotionReward(Owner!)]);
80+
SetEventFinished(PageDescription("POTIONS_CHOSEN")); // 结束事件时调用这个
81+
}
82+
83+
// 选择卡牌奖励,然后结束事件
84+
private async Task ChooseCards()
85+
{
86+
await RewardsCmd.OfferCustom(Owner!, [new CardReward(CardCreationOptions.ForNonCombatWithDefaultOdds([Owner!.Character.CardPool]), 3, Owner)]);
87+
SetEventFinished(PageDescription("CARDS_CHOSEN"));
88+
}
89+
}
90+
```
91+
92+
以上代码的字符串基本都和json中的文本键有关。
93+
94+
创建`{modId}/localization/{Language}/events.json`
95+
96+
```json
97+
{
98+
// 事件标题
99+
"TEST-TEST_EVENT.title": "与戈多相遇",
100+
// INITIAL是初始页面。这是初始页面的描述
101+
"TEST-TEST_EVENT.pages.INITIAL.description": "岔路口的长椅上空无一人,只有风掠过草丛。\n\n[sine]然后你看见了他。[/sine]\n\n那个小小的、蓝蓝的剪影静静坐着,像在等一封永远不会寄到的信,又像在等某个永远「快好了」的构建结束。\n\n[gold]戈多[/gold]抬起眼睛——如果那算是眼睛——语气平淡得近乎温柔:\n\n「[sine]时间还早……也还很长。你愿意先付一点代价,换一点……打发等待的东西吗?[/sine]」",
102+
// 这是选项的标题。这个TAKE_DAMAGE是从你的函数生成的id名字。(从TakeDamage生成)
103+
"TEST-TEST_EVENT.pages.INITIAL.options.TAKE_DAMAGE.title": "用疼痛记住这一刻",
104+
// 选项的描述。
105+
"TEST-TEST_EVENT.pages.INITIAL.options.TAKE_DAMAGE.description": "受到[red]{Damage}[/red]点伤害。",
106+
"TEST-TEST_EVENT.pages.INITIAL.options.LOSE_GOLD.title": "留下过路费",
107+
"TEST-TEST_EVENT.pages.INITIAL.options.LOSE_GOLD.description": "失去[gold]{Gold}[/gold]枚金币。",
108+
// 这是第二页。CHOOSE_TYPE是我们自己设置的。
109+
"TEST-TEST_EVENT.pages.CHOOSE_TYPE.description": "戈多从长椅底下摸出一个布包,又像是摸出了整个宇宙的耐心。\n\n「[sine]可以喝点什么……也可以领几张牌。反正,[/sine]」他顿了顿,「[sine]我们哪儿也不去。[/sine]」",
110+
"TEST-TEST_EVENT.pages.CHOOSE_TYPE.options.CHOOSE_POTIONS.title": "接过一瓶药水",
111+
"TEST-TEST_EVENT.pages.CHOOSE_TYPE.options.CHOOSE_POTIONS.description": "领取药水奖励,然后与这次等待道别。",
112+
"TEST-TEST_EVENT.pages.CHOOSE_TYPE.options.CHOOSE_CARDS.title": "领张牌再走",
113+
"TEST-TEST_EVENT.pages.CHOOSE_TYPE.options.CHOOSE_CARDS.description": "领取卡牌奖励,然后与这次等待道别。",
114+
// 结束页。POTIONS_CHOSEN也是我们设置的。
115+
"TEST-TEST_EVENT.pages.POTIONS_CHOSEN.description": "液体在瓶里轻轻晃荡,像远处引擎空转的节奏。\n\n[gold]戈多[/gold]把空瓶口朝你举了举,像在敬酒,又像在敬时间本身。\n\n[sine]……好了。剩下的,你自己慢慢等吧。[/sine]",
116+
"TEST-TEST_EVENT.pages.CARDS_CHOSEN.description": "纸牌边缘划过指缝,留下一点脆响——至少比沉默更热闹。\n\n[gold]戈多[/gold]望着你把牌收好,点点头。\n\n[sine]带走它们。路还长,别让自己……等得太安静。[/sine]"
117+
}
118+
119+
```
120+
121+
![alt text](../../../images/image33.png)
122+
123+
## 战斗事件
124+
125+
TODO
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
首先创建附魔类:
2+
3+
```csharp
4+
using BaseLib.Abstracts;
5+
using MegaCrit.Sts2.Core.Commands;
6+
using MegaCrit.Sts2.Core.Entities.Cards;
7+
using MegaCrit.Sts2.Core.Entities.Enchantments;
8+
using MegaCrit.Sts2.Core.GameActions.Multiplayer;
9+
using MegaCrit.Sts2.Core.HoverTips;
10+
using MegaCrit.Sts2.Core.Localization.DynamicVars;
11+
using MegaCrit.Sts2.Core.Models;
12+
using MegaCrit.Sts2.Core.ValueProps;
13+
14+
namespace Test.Scripts;
15+
16+
public class TestEnchantment : CustomEnchantmentModel
17+
{
18+
// 是否在卡牌上显示数值
19+
public override bool ShowAmount => true;
20+
21+
// 重载这个以改变显示的数字
22+
// public override int DisplayAmount => DynamicVars.Cards.IntValue;
23+
24+
// 是否会添加额外的卡牌描述文本
25+
public override bool HasExtraCardText => true;
26+
27+
// 像卡牌、遗物、药水等一样,可以使用DynamicVars和ExtraHoverTips
28+
protected override IEnumerable<DynamicVar> CanonicalVars => [new CardsVar(2)];
29+
protected override IEnumerable<IHoverTip> ExtraHoverTips => [HoverTipFactory.FromKeyword(CardKeyword.Retain)];
30+
31+
// 图标位置。大小1:1就行,原版是64x64
32+
protected override string? CustomIconPath => "res://icon.svg";
33+
34+
// 决定是否可以附魔到某张卡牌上,这里我们让它只能附魔到获得格挡的卡牌上。
35+
public override bool CanEnchant(CardModel card)
36+
{
37+
if (base.CanEnchant(card))
38+
{
39+
return card.GainsBlock;
40+
}
41+
return false;
42+
}
43+
44+
// 当附魔被应用时调用,这里我们给卡牌添加保留。
45+
protected override void OnEnchant()
46+
{
47+
Card.AddKeyword(CardKeyword.Retain);
48+
}
49+
50+
// 修改卡牌获得的格挡值,返回增加的改变量。
51+
public override decimal EnchantBlockAdditive(decimal originalBlock, ValueProp props)
52+
{
53+
if (!props.IsPoweredCardOrMonsterMoveBlock())
54+
{
55+
return 0m;
56+
}
57+
// 获得格挡额外增加Amount数量。这个数量是你给予附魔时指定的。
58+
return Amount;
59+
}
60+
61+
// 当附魔的卡牌被打出时调用。
62+
public override async Task OnPlay(PlayerChoiceContext choiceContext, CardPlay? cardPlay)
63+
{
64+
// 只有可用时打出才能抽牌。打出后设置为Disabled。
65+
if (Status == EnchantmentStatus.Normal)
66+
{
67+
await CardPileCmd.Draw(choiceContext, DynamicVars.Cards.IntValue, Card.Owner);
68+
Status = EnchantmentStatus.Disabled;
69+
}
70+
}
71+
}
72+
73+
```
74+
75+
然后创建`{modId}/localization/{Language}/enchantments.json`
76+
77+
```json
78+
{
79+
"TEST-TEST_ENCHANTMENT.title": "戈多",
80+
"TEST-TEST_ENCHANTMENT.extraCardText": "你第一次打出这张牌时,抽{Cards}张牌。", // 额外添加在卡牌上的文本
81+
"TEST-TEST_ENCHANTMENT.description": "这张牌获得[gold]保留[/gold]。\n这张牌获得的[gold]格挡[/gold]值增加[blue]{Amount}[/blue]点。\n第一次打出时抽{Cards}张牌。" // 附魔介绍
82+
}
83+
```
84+
85+
如何使用:
86+
* 控制台里输入`enchant TEST-TEST_ENCHANTMENT [数量] [给予手牌的编号]`
87+
* 在效果里,使用`CardCmd.Enchant<TestEhchantment>(card, 2m)`。第二个参数用于修改Amount。
88+
89+
![alt text](../../../images/image32.png)

Basics/03 - BaseLib接口/README.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,4 @@ https://github.com/Alchyr/BaseLib-StS2
3939

4040
```json
4141
"dependencies": ["BaseLib"],
42-
```
43-
44-
## 添加新怪物
45-
TODO:虽然可以加,但是最好等一个baselib
46-
47-
## 添加新事件
48-
TODO:不方便添加,等一个baselib
49-
50-
## 添加新附魔
51-
TODO:虽然可以加,但是最好等一个baselib
42+
```

Basics/04 - 添加新人物/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,34 @@ public class TestPotionPool : CustomPotionPoolModel
7474
public class TestCard : CustomCardModel
7575
```
7676

77+
## 初始遗物和卡牌升级
78+
79+
`古老牙齿`可以把一张初始卡变成先古升级。需要实现`ITranscendenceCard`接口,在你的卡牌类添加以下代码:
80+
81+
```csharp
82+
[Pool(typeof(TestCardPool))]
83+
public class TestCard : CustomCardModel, ITranscendenceCard // 实现接口
84+
{
85+
// 其余省略
86+
87+
public CardModel GetTranscendenceTransformedCard() => ModelDb.Card<TestCard2>().ToMutable(); // 实现方法。自己更改类型。
88+
}
89+
```
90+
91+
`欧洛巴斯之触`可以把初始遗物升级。
92+
93+
```csharp
94+
[Pool(typeof(TestRelicPool))]
95+
public class TestRelic : CustomRelicModel
96+
{
97+
// 其余省略
98+
99+
public override RelicModel? GetUpgradeReplacement() => ModelDb.Relic<TestRelic2>().ToMutable(); // 实现方法。自己更改类型。
100+
}
101+
```
102+
103+
`尘封魔典`可以获得一张先古卡。这个结果是从你池子里选出所有先古卡,然后去除`古老牙齿`的那张得到的。所以只需再创建一张先古卡即可。
104+
77105
## 创建人物
78106

79107
人物需要极其大量的资源,推荐新建类继承`PlaceholderCharacterModel`而不是`CustomCharacterModel`。你没有的资源直接注释掉以使用原版。教程提供的资源在最下方。

Basics/07 - 快速调试&热重载/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
"program": "${config:sts2.installDir}/${config:sts2.gameExeName}",
5454
"cwd": "${config:sts2.installDir}",
5555
"console": "internalConsole",
56+
"sourceFileMap": {
57+
".\\": "${workspaceFolder}/"
58+
},
5659
"stopAtEntry": false
5760
}
5861
]

0 commit comments

Comments
 (0)