Skip to content

Commit 1809390

Browse files
committed
feat(SecondaryResources): Introduce secondary resource card cost color handling and UI updates
- Added a new enum `SecondaryResourceCardCostColor` to represent various cost states for secondary-resource cards, enhancing visual feedback for cost variations. - Implemented `SecondaryResourceCardCostHelper` to determine cost color based on payment line and context, improving UI responsiveness. - Updated `SecondaryResourceCardCostUi` and `SecondaryResourceUi` to incorporate new parameters for pile type and preview mode, ensuring accurate cost representation. - Enhanced `SecondaryResourceCosts` with a canonical cost property for better cost comparison and management. - Refactored related methods to utilize the new cost color logic, streamlining the overall cost handling process in the UI.
1 parent 453e865 commit 1809390

5 files changed

Lines changed: 212 additions & 26 deletions

File tree

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+
}

src/Combat/SecondaryResources/SecondaryResourceCardCostUi.cs

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Godot;
22
using MegaCrit.Sts2.addons.mega_text;
33
using MegaCrit.Sts2.Core.Assets;
4+
using MegaCrit.Sts2.Core.Entities.Cards;
45
using MegaCrit.Sts2.Core.Helpers;
56
using MegaCrit.Sts2.Core.Models;
67
using STS2RitsuLib.Cards.FreePlay;
@@ -146,7 +147,9 @@ public partial class NSecondaryResourceCardCostUi : Control
146147

147148
private MegaLabel _label = null!;
148149
private SecondaryResourcePaymentLine? _line;
150+
private PileType _pileType = PileType.Hand;
149151
private SecondaryResourcePaymentPlan? _plan;
152+
private CardPreviewMode _previewMode = CardPreviewMode.Normal;
150153
private string? _resourceId;
151154
private SecondaryResourceCardCostUiStyle _style = SecondaryResourceCardCostUiStyle.Default;
152155
private TextureRect _texture = null!;
@@ -317,7 +320,7 @@ public void BindUse(string useId, SecondaryResourceDefinition definition)
317320
public void Refresh<TParent>(SecondaryResourceCardUiContext<TParent, NSecondaryResourceCardCostUi> context)
318321
where TParent : Node
319322
{
320-
Refresh(context.Card, context.Plan);
323+
Refresh(context.Card, context.Plan, context.PileType, context.PreviewMode);
321324
}
322325

323326
/// <summary>
@@ -337,11 +340,26 @@ public void Refresh(CardModel card)
337340
/// 根据已解析支付计划刷新。
338341
/// </summary>
339342
public void Refresh(CardModel card, SecondaryResourcePaymentPlan plan)
343+
{
344+
Refresh(card, plan, _pileType, _previewMode);
345+
}
346+
347+
/// <summary>
348+
/// Refreshes from a resolved payment plan and card visual context.
349+
/// 根据已解析支付计划和卡牌视觉上下文刷新。
350+
/// </summary>
351+
public void Refresh(
352+
CardModel card,
353+
SecondaryResourcePaymentPlan plan,
354+
PileType pileType,
355+
CardPreviewMode previewMode)
340356
{
341357
ArgumentNullException.ThrowIfNull(card);
342358
ArgumentNullException.ThrowIfNull(plan);
343359

344360
_boundCard = card;
361+
_pileType = pileType;
362+
_previewMode = previewMode;
345363
if (string.IsNullOrWhiteSpace(_resourceId))
346364
{
347365
Visible = false;
@@ -382,7 +400,7 @@ public void Refresh(SecondaryResourcePaymentPlan plan, SecondaryResourcePaymentL
382400

383401
Visible = true;
384402
_label.SetTextAutoSize(_style.Format(line));
385-
var (fontColor, outlineColor) = ResolveLabelColors(line);
403+
var (fontColor, outlineColor) = ResolveLabelColors(line, _pileType, _previewMode);
386404
_label.AddThemeColorOverride(ThemeConstants.Label.FontColor, fontColor);
387405
_label.AddThemeColorOverride(ThemeConstants.Label.FontOutlineColor, outlineColor);
388406
}
@@ -476,22 +494,28 @@ private void ApplyLabelTheme()
476494
_label.AddThemeConstantOverride(ThemeConstants.Label.OutlineSize, _style.OutlineSize);
477495
}
478496

479-
private (Color FontColor, Color OutlineColor) ResolveLabelColors(SecondaryResourcePaymentLine line)
497+
private (Color FontColor, Color OutlineColor) ResolveLabelColors(
498+
SecondaryResourcePaymentLine line,
499+
PileType pileType,
500+
CardPreviewMode previewMode)
480501
{
481-
if (!line.CanPlay && line.BlocksPlay)
482-
return (_style.UnaffordableColor, _style.UnaffordableOutlineColor);
483-
484-
if (line is { IsOptional: true, Activated: false } &&
485-
_style.OptionalUnavailableColor is { } optionalUnavailableColor &&
486-
_style.OptionalUnavailableOutlineColor is { } optionalUnavailableOutlineColor)
487-
return (optionalUnavailableColor, optionalUnavailableOutlineColor);
488-
489-
return line.CostsX switch
490-
{
491-
false when line.Cost > line.BaseCost => (_style.IncreasedColor, _style.IncreasedOutlineColor),
492-
false when line.Cost < line.BaseCost => (_style.DecreasedColor, _style.DecreasedOutlineColor),
493-
_ => (_style.AffordableColor, _style.AffordableOutlineColor),
494-
};
502+
var useOptionalUnavailable =
503+
_style.OptionalUnavailableColor.HasValue &&
504+
_style.OptionalUnavailableOutlineColor.HasValue;
505+
return SecondaryResourceCardCostHelper.GetCostColor(
506+
line,
507+
pileType,
508+
previewMode,
509+
includeOptionalUnavailable: useOptionalUnavailable) switch
510+
{
511+
SecondaryResourceCardCostColor.Increased => (_style.IncreasedColor, _style.IncreasedOutlineColor),
512+
SecondaryResourceCardCostColor.Decreased => (_style.DecreasedColor, _style.DecreasedOutlineColor),
513+
SecondaryResourceCardCostColor.InsufficientResources =>
514+
(_style.UnaffordableColor, _style.UnaffordableOutlineColor),
515+
SecondaryResourceCardCostColor.OptionalUnavailable =>
516+
(_style.OptionalUnavailableColor!.Value, _style.OptionalUnavailableOutlineColor!.Value),
517+
_ => (_style.AffordableColor, _style.AffordableOutlineColor),
518+
};
495519
}
496520
}
497521
}

src/Combat/SecondaryResources/SecondaryResourceCosts.cs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,16 @@ public sealed record SecondaryResourcePaymentLine(
390390
public bool IsPreview { get; init; }
391391

392392
/// <summary>
393-
/// Unmodified fixed cost used for card-cost color comparison.
394-
/// 用于卡牌费用颜色比较的未修改固定费用
393+
/// Current unmodified fixed cost used for card-cost color comparison.
394+
/// 用于卡牌费用颜色比较的当前未修改固定费用
395395
/// </summary>
396396
public int BaseCost { get; init; } = Cost;
397+
398+
/// <summary>
399+
/// Canonical fixed cost before upgrade-preview changes.
400+
/// 升级预览变化前的 canonical 固定费用。
401+
/// </summary>
402+
public int CanonicalCost { get; init; } = Cost;
397403
}
398404

399405
/// <summary>
@@ -621,16 +627,19 @@ private static SecondaryResourcePaymentLine ResolvePreviewLine(
621627
var cost = use.Cost;
622628
var localCost = ModifyLocalCost(card, definition, use, cost.Amount);
623629
var baseCost = Math.Max(0, cost.Amount);
630+
var canonicalCost = ResolveCanonicalCost(card, use);
624631
var fixedCost = Math.Max(0, (int)Math.Ceiling(localCost));
632+
var displayCost = isFree ? 0 : fixedCost;
625633
if (!cost.CostsX)
626-
return new(definition.Id, definition, fixedCost, 0, isFree ? 0 : fixedCost, fixedCost, false, isFree)
634+
return new(definition.Id, definition, displayCost, 0, isFree ? 0 : fixedCost, fixedCost, false, isFree)
627635
{
628636
UseId = use.Id,
629637
Kind = use.Kind,
630638
BlocksPlay = use.Kind == SecondaryResourceUseKind.RequiredCost,
631639
Activated = use.Kind == SecondaryResourceUseKind.RequiredCost || isFree,
632640
IsPreview = true,
633641
BaseCost = baseCost,
642+
CanonicalCost = canonicalCost,
634643
};
635644

636645
return new(definition.Id, definition, fixedCost, 0, 0, 0, true, isFree)
@@ -641,6 +650,7 @@ private static SecondaryResourcePaymentLine ResolvePreviewLine(
641650
Activated = isFree,
642651
IsPreview = true,
643652
BaseCost = baseCost,
653+
CanonicalCost = canonicalCost,
644654
};
645655
}
646656

@@ -657,10 +667,12 @@ private static SecondaryResourcePaymentLine ResolveLine(
657667
var cost = use.Cost;
658668
var localCost = ModifyLocalCost(card, definition, use, cost.Amount);
659669
var baseCost = Math.Max(0, cost.Amount);
670+
var canonicalCost = ResolveCanonicalCost(card, use);
660671
var modifiedCost = SecondaryResourceHook.ModifyCost(
661672
new(combatState, player, card, definition, localCost),
662673
localCost);
663674
var fixedCost = Math.Max(0, (int)Math.Ceiling(modifiedCost));
675+
var displayCost = isFree ? 0 : fixedCost;
664676
var isRequired = use.Kind == SecondaryResourceUseKind.RequiredCost;
665677

666678
if (!cost.CostsX)
@@ -679,14 +691,15 @@ private static SecondaryResourcePaymentLine ResolveLine(
679691
}
680692

681693
var value = !isRequired && !activated ? 0 : fixedCost;
682-
return new(definition.Id, definition, fixedCost, available, amountToSpend, value, false, isFree)
694+
return new(definition.Id, definition, displayCost, available, amountToSpend, value, false, isFree)
683695
{
684696
UseId = use.Id,
685697
Kind = use.Kind,
686698
BlocksPlay = isRequired,
687699
Activated = activated,
688700
SpendAllowed = spendAllowed,
689701
BaseCost = baseCost,
702+
CanonicalCost = canonicalCost,
690703
};
691704
}
692705

@@ -722,6 +735,7 @@ private static SecondaryResourcePaymentLine ResolveLine(
722735
Activated = xActivated,
723736
SpendAllowed = xSpendAllowed,
724737
BaseCost = baseCost,
738+
CanonicalCost = canonicalCost,
725739
};
726740
}
727741

@@ -742,6 +756,45 @@ private static IEnumerable<SecondaryResourcePlayUse> GetCapabilityUses(CardModel
742756
}
743757
}
744758

759+
private static int ResolveCanonicalCost(CardModel card, SecondaryResourcePlayUse use)
760+
{
761+
var fallback = Math.Max(0, use.Cost.Amount);
762+
if (card.CanonicalInstance == card)
763+
return fallback;
764+
765+
return TryFindCanonicalUseCost(card.CanonicalInstance, use, out var canonicalCost)
766+
? Math.Max(0, canonicalCost.Amount)
767+
: fallback;
768+
}
769+
770+
private static bool TryFindCanonicalUseCost(
771+
CardModel canonicalCard,
772+
SecondaryResourcePlayUse use,
773+
out SecondaryResourceCost cost)
774+
{
775+
if (canonicalCard.TryGetSecondaryResourceUses(out var uses))
776+
{
777+
var canonicalUse = uses.Snapshot().FirstOrDefault(candidate =>
778+
string.Equals(candidate.Id, use.Id, StringComparison.OrdinalIgnoreCase) &&
779+
string.Equals(candidate.ResourceId, use.ResourceId, StringComparison.OrdinalIgnoreCase) &&
780+
candidate.Kind == use.Kind);
781+
if (canonicalUse != null)
782+
{
783+
cost = canonicalUse.Cost;
784+
return true;
785+
}
786+
}
787+
788+
if (use.Kind == SecondaryResourceUseKind.RequiredCost &&
789+
string.Equals(use.Id, use.ResourceId, StringComparison.OrdinalIgnoreCase) &&
790+
canonicalCard.TryGetSecondaryCosts(out var costs) &&
791+
costs.Snapshot().TryGetValue(use.ResourceId, out cost!))
792+
return true;
793+
794+
cost = null!;
795+
return false;
796+
}
797+
745798
private static decimal ModifyLocalCost(
746799
CardModel card,
747800
SecondaryResourceDefinition definition,

0 commit comments

Comments
 (0)