Skip to content

Commit 00052cf

Browse files
committed
chore(release): merge dev into main for v0.4.37
2 parents f1b739e + a5a7239 commit 00052cf

37 files changed

Lines changed: 1785 additions & 55 deletions

STS2-RitsuLib.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
<PropertyGroup Label="NuGet package">
4343
<IsPackable>true</IsPackable>
44-
<Version>0.4.36</Version>
44+
<Version>0.4.37</Version>
4545
<Authors>OLC</Authors>
4646
<Description>Shared framework library for Slay the Spire 2 mods.</Description>
4747
<PackageReadmeFile>README.md</PackageReadmeFile>
@@ -142,6 +142,9 @@
142142
</ItemGroup>
143143
<ItemGroup>
144144
<EmbeddedResource Include="resources\mod_image.png" LogicalName="STS2RitsuLib.Assets.mod_image.png"/>
145+
<EmbeddedResource Include="resources\mod_image_ex.png" LogicalName="STS2RitsuLib.Assets.mod_image_ex.png"/>
146+
<EmbeddedResource Include="src\Settings\Localization\EasterEggs\*.json"
147+
LogicalName="STS2RitsuLib.Settings.Localization.EasterEggs.%(Filename)%(Extension)"/>
145148
<EmbeddedResource Include="src\Settings\Localization\JoinDiagnostics\*.json"
146149
LogicalName="STS2RitsuLib.Settings.Localization.JoinDiagnostics.%(Filename)%(Extension)"/>
147150
<EmbeddedResource Include="src\Settings\Localization\ModSettingsUi\*.json"

mod_manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"name": "RitsuLib",
55
"author": "OLC",
66
"description": "A shared Slay the Spire 2 mod framework library providing reusable patching, persistence, lifecycle, localization, and utility APIs for other mods.",
7-
"version": "0.4.36",
7+
"version": "0.4.37",
88
"has_pck": false,
99
"has_dll": true,
1010
"affects_gameplay": false,

resources/mod_image_ex.png

172 KB
Loading

src/Combat/CardTargeting/Patches/CustomTargetTypePotionPatches.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#if !STS2_AT_LEAST_0_104_0
2+
using CombatStateCompat = MegaCrit.Sts2.Core.Combat.CombatState;
3+
#else
4+
using CombatStateCompat = MegaCrit.Sts2.Core.Combat.ICombatState;
5+
#endif
16
using System.Reflection;
27
using System.Reflection.Emit;
38
using Godot;
@@ -161,6 +166,7 @@ private static async Task TargetCreature(NPotionHolder holder, PotionModel potio
161166
}
162167
}
163168

169+
#if STS2_AT_LEAST_0_106_0
164170
/// <summary>
165171
/// Validates custom single-target potion targets through their registered predicate.
166172
/// 通过注册谓词校验自定义单体目标药水的目标。
@@ -198,6 +204,7 @@ public static bool Prefix(PotionModel __instance, Creature? target, ref bool __r
198204
return false;
199205
}
200206
}
207+
#endif
201208

202209
/// <summary>
203210
/// Uses custom multi-target predicates to place potion throw VFX over the affected creature group.
@@ -206,7 +213,7 @@ public static bool Prefix(PotionModel __instance, Creature? target, ref bool __r
206213
internal sealed class PotionModelOnUseWrapperCustomMultiTargetVfxPatch : IPatchMethod
207214
{
208215
private static readonly MethodInfo? GetCreaturesOnSideMethod =
209-
AccessTools.DeclaredMethod(typeof(ICombatState), nameof(ICombatState.GetCreaturesOnSide),
216+
AccessTools.DeclaredMethod(typeof(CombatStateCompat), nameof(CombatStateCompat.GetCreaturesOnSide),
210217
[typeof(CombatSide)]);
211218

212219
private static readonly MethodInfo? GetPotionVfxTargetsMethod =
@@ -268,7 +275,7 @@ public static IEnumerable<CodeInstruction> Transpiler(
268275
}
269276

270277
private static IReadOnlyList<Creature> GetPotionVfxTargets(
271-
ICombatState combatState,
278+
CombatStateCompat combatState,
272279
CombatSide side,
273280
PotionModel potion)
274281
{

src/Combat/CardTargeting/PotionModelTargetingExtensions.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static List<Creature> GetTargets(this PotionModel potion, Creature? selec
4040
{
4141
if (selectedTarget == null)
4242
return [];
43-
return potion.IsValidTarget(selectedTarget) ? [selectedTarget] : [];
43+
return IsValidTarget(potion, selectedTarget) ? [selectedTarget] : [];
4444
}
4545
case TargetType.AllAllies:
4646
return state?.GetCreaturesOnSide(owner.Side).Where(c => c.IsAlive).ToList() ?? [];
@@ -58,7 +58,7 @@ public static List<Creature> GetTargets(this PotionModel potion, Creature? selec
5858
case TargetType.TargetedNoCreature:
5959
return [];
6060
case TargetType.Self:
61-
return potion.IsValidTarget(selectedTarget ?? owner) ? [owner] : [];
61+
return IsValidTarget(potion, selectedTarget ?? owner) ? [owner] : [];
6262
default:
6363
{
6464
if (CustomTargetTypeResolver.IsCustomSingleTargetType(potion.TargetType))
@@ -87,5 +87,32 @@ public static List<Creature> GetTargets(this PotionModel potion, Creature? selec
8787
}
8888
}
8989
}
90+
91+
private static bool IsValidTarget(PotionModel potion, Creature? target)
92+
{
93+
#if STS2_AT_LEAST_0_106_0
94+
return potion.IsValidTarget(target);
95+
#else
96+
if (target == null)
97+
return potion.TargetType == TargetType.TargetedNoCreature || !potion.TargetType.IsSingleTarget();
98+
99+
if (!target.IsAlive)
100+
return false;
101+
102+
if (potion.TargetType == TargetType.AnyEnemy)
103+
return target.Side != potion.Owner.Creature.Side;
104+
105+
if (potion.TargetType == TargetType.AnyAlly)
106+
return target.Side == potion.Owner.Creature.Side && target != potion.Owner.Creature;
107+
108+
if (potion.TargetType == TargetType.AnyPlayer)
109+
return target.IsPlayer;
110+
111+
if (potion.TargetType == TargetType.Self)
112+
return target == potion.Owner.Creature;
113+
114+
return false;
115+
#endif
116+
}
90117
}
91118
}

src/Combat/SecondaryResources/Patches/SecondaryResourceCardPatches.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using MegaCrit.Sts2.Core.Hooks;
1212
using MegaCrit.Sts2.Core.Models;
1313
using STS2RitsuLib.Cards.FreePlay;
14-
using STS2RitsuLib.Lifecycle.Patches;
1514
using STS2RitsuLib.Patching.Models;
1615

1716
namespace STS2RitsuLib.Combat.SecondaryResources.Patches
@@ -116,24 +115,42 @@ public static ModPatchTarget[] GetTargets()
116115
];
117116
}
118117

119-
public static void Prefix(CardModel __instance, bool isAutoPlay)
118+
public static void Prefix(CardModel __instance, bool isAutoPlay, out IDisposable? __state)
120119
{
121-
if (!isAutoPlay ||
122-
!ModSecondaryResourceRegistry.HasAny ||
120+
__state = null;
121+
if (!ModSecondaryResourceRegistry.HasAny ||
123122
!__instance.HasMaterialSecondaryCosts())
124123
return;
125124

126-
var plan = SecondaryResourcePaymentResolver.Plan(__instance, true);
127-
if (plan.HasLines)
128-
SecondaryResourcePaymentResolver.CommitFree(plan);
125+
if (isAutoPlay && !SecondaryResourcePlayLedgerRuntime.HasPending(__instance))
126+
{
127+
var plan = SecondaryResourcePaymentResolver.Plan(__instance, true);
128+
if (plan.HasLines)
129+
SecondaryResourcePaymentResolver.CommitFree(plan);
130+
}
131+
132+
__state = SecondaryResourcePlayLedgerRuntime.BeginPendingScope(__instance);
129133
}
130134

131-
public static void Postfix(CardModel __instance, ref Task __result)
135+
public static void Postfix(CardModel __instance, IDisposable? __state, ref Task __result)
132136
{
133137
if (!ModSecondaryResourceRegistry.HasAny)
134138
return;
135139

136-
__result = LifecyclePatchTaskBridge.After(__result, () => { __instance.ClearSecondaryCostsUntilPlayed(); });
140+
__result = After(__instance, __state, __result);
141+
}
142+
143+
private static async Task After(CardModel card, IDisposable? pendingScope, Task original)
144+
{
145+
try
146+
{
147+
await original;
148+
card.ClearSecondaryCostsUntilPlayed();
149+
}
150+
finally
151+
{
152+
pendingScope?.Dispose();
153+
}
137154
}
138155
}
139156

src/Combat/SecondaryResources/Patches/SecondaryResourceUiPatches.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,13 @@ public static ModPatchTarget[] GetTargets()
127127
return [new(typeof(NCard), nameof(NCard.UpdateVisuals), [typeof(PileType), typeof(CardPreviewMode)])];
128128
}
129129

130-
public static void Postfix(NCard __instance)
130+
public static void Postfix(NCard __instance, PileType pileType, CardPreviewMode previewMode)
131131
{
132132
if (!ModSecondaryResourceRegistry.HasAny ||
133133
__instance.Model == null)
134134
return;
135135

136-
SecondaryResourceUiRuntime.UpdateCardUi(__instance, __instance.Model);
136+
SecondaryResourceUiRuntime.UpdateCardUi(__instance, __instance.Model, pileType, previewMode);
137137
}
138138
}
139139
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using MegaCrit.Sts2.Core.Entities.Cards;
2+
3+
namespace STS2RitsuLib.Combat.SecondaryResources
4+
{
5+
/// <summary>
6+
/// Color state for a secondary-resource card cost.
7+
/// 次级资源卡牌费用的颜色状态。
8+
/// </summary>
9+
public enum SecondaryResourceCardCostColor
10+
{
11+
/// <summary>
12+
/// Use the default cost color.
13+
/// 使用默认费用颜色。
14+
/// </summary>
15+
Unmodified,
16+
17+
/// <summary>
18+
/// Cost is higher than the current base cost.
19+
/// 费用高于当前基础费用。
20+
/// </summary>
21+
Increased,
22+
23+
/// <summary>
24+
/// Cost is lower than the current base cost, or upgrade preview lowered the base cost.
25+
/// 费用低于当前基础费用,或升级预览降低了基础费用。
26+
/// </summary>
27+
Decreased,
28+
29+
/// <summary>
30+
/// A required cost cannot be paid.
31+
/// 必需费用无法支付。
32+
/// </summary>
33+
InsufficientResources,
34+
35+
/// <summary>
36+
/// An optional spend is unavailable but does not block card play.
37+
/// 可选支付不可用,但不阻止卡牌打出。
38+
/// </summary>
39+
OptionalUnavailable,
40+
}
41+
42+
/// <summary>
43+
/// Mirrors the game's card cost color rules for secondary-resource card UI.
44+
/// 为次级资源卡牌 UI 对齐游戏原版费用颜色规则。
45+
/// </summary>
46+
public static class SecondaryResourceCardCostHelper
47+
{
48+
/// <summary>
49+
/// Gets the color state for a resolved secondary-resource payment line.
50+
/// 获取已解析次级资源支付行的颜色状态。
51+
/// </summary>
52+
public static SecondaryResourceCardCostColor GetCostColor(
53+
SecondaryResourcePaymentLine line,
54+
PileType pileType,
55+
CardPreviewMode previewMode,
56+
bool pretendCardCanBePlayed = false,
57+
bool includeOptionalUnavailable = true)
58+
{
59+
ArgumentNullException.ThrowIfNull(line);
60+
61+
if (line.CostsX)
62+
return SecondaryResourceCardCostColor.Unmodified;
63+
64+
if (previewMode == CardPreviewMode.Upgrade && line.BaseCost < line.CanonicalCost)
65+
return SecondaryResourceCardCostColor.Decreased;
66+
67+
if (pileType != PileType.Hand)
68+
return SecondaryResourceCardCostColor.Unmodified;
69+
70+
if (line is { CanPlay: false, BlocksPlay: true })
71+
return pretendCardCanBePlayed
72+
? SecondaryResourceCardCostColor.Unmodified
73+
: SecondaryResourceCardCostColor.InsufficientResources;
74+
75+
if (includeOptionalUnavailable && line is { IsOptional: true, Activated: false })
76+
return SecondaryResourceCardCostColor.OptionalUnavailable;
77+
78+
if (line.Cost > line.BaseCost)
79+
return SecondaryResourceCardCostColor.Increased;
80+
81+
// ReSharper disable once ConvertIfStatementToReturnStatement
82+
if (line.Cost < line.BaseCost)
83+
return SecondaryResourceCardCostColor.Decreased;
84+
85+
return SecondaryResourceCardCostColor.Unmodified;
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)