Skip to content

Commit fa474eb

Browse files
committed
feat(card piles): add FlightStartPositionResolver for dynamic shuffle fly visuals
- Introduced `FlightStartPositionResolver` to `ModCardPileDefinition`, enabling mods to specify dynamic start positions for shuffle-style fly visuals. - Added `ModCardPileFlightStartContext` to encapsulate context for the resolver, including source and target piles. - Updated relevant methods in `ModCardPileLayout` and `ModCardPileSpec` to support the new functionality, enhancing flexibility in visual animations. - Registered a new patch, `ModCardPileShuffleVfxStartPositionPatch`, to override start positions for shuffle fly visuals based on the resolver.
1 parent 38673fa commit fa474eb

7 files changed

Lines changed: 147 additions & 4 deletions

CardPiles/ModCardPileDefinition.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public sealed record ModCardPileDefinition
4444
/// <param name="flightTargetPositionResolver">
4545
/// Optional dynamic fly-in target resolver (see <see cref="FlightTargetPositionResolver" />).
4646
/// </param>
47+
/// <param name="flightStartPositionResolver">
48+
/// Optional dynamic fly-out source/start resolver (see <see cref="FlightStartPositionResolver" />).
49+
/// </param>
4750
public ModCardPileDefinition(
4851
string modId,
4952
string id,
@@ -58,7 +61,8 @@ public ModCardPileDefinition(
5861
Vector2 hoverTipScreenOffset,
5962
ModCardPileHoverTipPlacement hoverTipPlacement,
6063
Func<ModCardPileVisibilityContext, bool>? visibleWhen,
61-
Func<ModCardPileFlightTargetContext, Vector2?>? flightTargetPositionResolver)
64+
Func<ModCardPileFlightTargetContext, Vector2?>? flightTargetPositionResolver,
65+
Func<ModCardPileFlightStartContext, Vector2?>? flightStartPositionResolver)
6266
{
6367
ModId = modId;
6468
Id = id;
@@ -74,6 +78,7 @@ public ModCardPileDefinition(
7478
HoverTipPlacement = hoverTipPlacement;
7579
VisibleWhen = visibleWhen;
7680
FlightTargetPositionResolver = flightTargetPositionResolver;
81+
FlightStartPositionResolver = flightStartPositionResolver;
7782
}
7883

7984
/// <summary>
@@ -94,7 +99,30 @@ public ModCardPileDefinition(
9499
ModCardPileHoverTipPlacement hoverTipPlacement,
95100
Func<ModCardPileVisibilityContext, bool>? visibleWhen)
96101
: this(modId, id, pileType, scope, style, anchor, iconPath, hotkeys, cardShouldBeVisible, onOpen,
97-
hoverTipScreenOffset, hoverTipPlacement, visibleWhen, null)
102+
hoverTipScreenOffset, hoverTipPlacement, visibleWhen, null, null)
103+
{
104+
}
105+
106+
/// <summary>
107+
/// Compatibility overload that omitted <see cref="FlightStartPositionResolver" />; forwards with null.
108+
/// </summary>
109+
public ModCardPileDefinition(
110+
string modId,
111+
string id,
112+
PileType pileType,
113+
ModCardPileScope scope,
114+
ModCardPileUiStyle style,
115+
ModCardPileAnchor anchor,
116+
string? iconPath,
117+
string[]? hotkeys,
118+
bool cardShouldBeVisible,
119+
Action<ModCardPileOpenContext>? onOpen,
120+
Vector2 hoverTipScreenOffset,
121+
ModCardPileHoverTipPlacement hoverTipPlacement,
122+
Func<ModCardPileVisibilityContext, bool>? visibleWhen,
123+
Func<ModCardPileFlightTargetContext, Vector2?>? flightTargetPositionResolver)
124+
: this(modId, id, pileType, scope, style, anchor, iconPath, hotkeys, cardShouldBeVisible, onOpen,
125+
hoverTipScreenOffset, hoverTipPlacement, visibleWhen, flightTargetPositionResolver, null)
98126
{
99127
}
100128

@@ -115,7 +143,7 @@ public ModCardPileDefinition(
115143
Vector2 hoverTipScreenOffset,
116144
ModCardPileHoverTipPlacement hoverTipPlacement)
117145
: this(modId, id, pileType, scope, style, anchor, iconPath, hotkeys, cardShouldBeVisible, onOpen,
118-
hoverTipScreenOffset, hoverTipPlacement, null, null)
146+
hoverTipScreenOffset, hoverTipPlacement, null, null, null)
119147
{
120148
}
121149

@@ -272,5 +300,10 @@ public ModCardPileDefinition(
272300
/// Optional resolver invoked for each fly-in targeting request to this pile.
273301
/// </summary>
274302
public Func<ModCardPileFlightTargetContext, Vector2?>? FlightTargetPositionResolver { get; }
303+
304+
/// <summary>
305+
/// Optional resolver invoked when a shuffle-style fly visual starts from this pile.
306+
/// </summary>
307+
public Func<ModCardPileFlightStartContext, Vector2?>? FlightStartPositionResolver { get; }
275308
}
276309
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Godot;
2+
using MegaCrit.Sts2.Core.Entities.Cards;
3+
4+
namespace STS2RitsuLib.CardPiles
5+
{
6+
/// <summary>
7+
/// Context passed to <see cref="ModCardPileSpec.FlightStartPositionResolver" /> when shuffle-style fly
8+
/// visuals need a source/start position for a mod pile.
9+
/// </summary>
10+
public sealed class ModCardPileFlightStartContext
11+
{
12+
internal ModCardPileFlightStartContext(
13+
ModCardPileDefinition definition,
14+
CardPile startPile,
15+
CardPile targetPile,
16+
Vector2 defaultStartPosition)
17+
{
18+
Definition = definition;
19+
StartPile = startPile;
20+
TargetPile = targetPile;
21+
DefaultStartPosition = defaultStartPosition;
22+
}
23+
24+
/// <summary>Definition of the source pile.</summary>
25+
public ModCardPileDefinition Definition { get; }
26+
27+
/// <summary>Source pile for this shuffle fly visual.</summary>
28+
public CardPile StartPile { get; }
29+
30+
/// <summary>Destination pile for this shuffle fly visual.</summary>
31+
public CardPile TargetPile { get; }
32+
33+
/// <summary>Ritsulib's default start position for this request.</summary>
34+
public Vector2 DefaultStartPosition { get; }
35+
}
36+
}

CardPiles/ModCardPileLayout.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Godot;
22
using MegaCrit.Sts2.Core.Combat;
3+
using MegaCrit.Sts2.Core.Entities.Cards;
34
using MegaCrit.Sts2.Core.Nodes;
45
using MegaCrit.Sts2.Core.Nodes.Cards;
56
using MegaCrit.Sts2.Core.Nodes.Rooms;
@@ -32,6 +33,20 @@ public static Vector2 GetTargetPosition(ModCardPileDefinition definition, NCard?
3233
return resolver(context) ?? defaultPosition;
3334
}
3435

36+
public static Vector2 GetShuffleStartPosition(
37+
ModCardPileDefinition definition,
38+
CardPile startPile,
39+
CardPile targetPile)
40+
{
41+
var defaultPosition = GetDefaultTargetPosition(definition, null);
42+
var resolver = definition.FlightStartPositionResolver;
43+
if (resolver == null)
44+
return defaultPosition;
45+
46+
var context = new ModCardPileFlightStartContext(definition, startPile, targetPile, defaultPosition);
47+
return resolver(context) ?? defaultPosition;
48+
}
49+
3550
private static Vector2 GetDefaultTargetPosition(ModCardPileDefinition definition, NCard? node)
3651
{
3752
var fallback = FallbackPosition();

CardPiles/ModCardPileRegistry.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ private ModCardPileDefinition RegisterCore(string id, ModCardPileSpec spec)
237237
spec.HoverTipScreenOffset,
238238
spec.HoverTipPlacement,
239239
spec.VisibleWhen,
240-
spec.FlightTargetPositionResolver);
240+
spec.FlightTargetPositionResolver,
241+
spec.FlightStartPositionResolver);
241242

242243
lock (SyncRoot)
243244
{

CardPiles/ModCardPileSpec.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,11 @@ public ModCardPileSpec()
108108
/// target position for the tail/trail endpoint. Return null to use the default layout position.
109109
/// </summary>
110110
public Func<ModCardPileFlightTargetContext, Vector2?>? FlightTargetPositionResolver { get; init; }
111+
112+
/// <summary>
113+
/// Optional resolver called when a shuffle-style fly visual starts from this pile, allowing mods to
114+
/// provide a dynamic source/start position. Return null to use the default layout position.
115+
/// </summary>
116+
public Func<ModCardPileFlightStartContext, Vector2?>? FlightStartPositionResolver { get; init; }
111117
}
112118
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Reflection;
2+
using HarmonyLib;
3+
using MegaCrit.Sts2.Core.Entities.Cards;
4+
using MegaCrit.Sts2.Core.Nodes.Vfx;
5+
using STS2RitsuLib.Patching.Models;
6+
7+
namespace STS2RitsuLib.CardPiles.Patches
8+
{
9+
/// <summary>
10+
/// Overrides the source/start position for shuffle fly visuals when the source pile is a mod pile and
11+
/// that pile registered a <see cref="ModCardPileSpec.FlightStartPositionResolver" />.
12+
/// </summary>
13+
public sealed class ModCardPileShuffleVfxStartPositionPatch : IPatchMethod
14+
{
15+
private static readonly FieldInfo? StartPositionField =
16+
AccessTools.Field(typeof(NCardFlyShuffleVfx), "_startPos");
17+
18+
/// <inheritdoc />
19+
public static string PatchId => "ritsulib_mod_pile_shuffle_vfx_start_position";
20+
21+
/// <inheritdoc />
22+
public static string Description =>
23+
"Allow mod card piles to customize shuffle-fly source positions";
24+
25+
/// <inheritdoc />
26+
public static bool IsCritical => false;
27+
28+
/// <inheritdoc />
29+
public static ModPatchTarget[] GetTargets()
30+
{
31+
return [new(typeof(NCardFlyShuffleVfx), nameof(NCardFlyShuffleVfx.Create))];
32+
}
33+
34+
// ReSharper disable InconsistentNaming
35+
/// <summary>
36+
/// Rewrites the freshly created shuffle-vfx start position when <paramref name="startPile" /> is a
37+
/// mod pile and it provided a custom start resolver.
38+
/// </summary>
39+
public static void Postfix(CardPile startPile, CardPile targetPile, ref NCardFlyShuffleVfx? __result)
40+
{
41+
if (__result == null || StartPositionField == null)
42+
return;
43+
if (!ModCardPileRegistry.TryGetByPileType(startPile.Type, out var definition))
44+
return;
45+
46+
var resolved = ModCardPileLayout.GetShuffleStartPosition(definition, startPile, targetPile);
47+
StartPositionField.SetValue(__result, resolved);
48+
}
49+
// ReSharper restore InconsistentNaming
50+
}
51+
}

RitsuLibFramework.PatcherSetup.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ private static void RegisterLifecyclePatches()
145145
patcher.RegisterPatch<ModCardPileGetPatch>();
146146
patcher.RegisterPatch<ModCardPileIsCombatPatch>();
147147
patcher.RegisterPatch<ModCardPileGetTargetPositionPatch>();
148+
patcher.RegisterPatch<ModCardPileShuffleVfxStartPositionPatch>();
148149
patcher.RegisterPatch<ModCardPileAllPilesPatch>();
149150
patcher.RegisterPatch<ModCardPileFindOnTablePatch>();
150151
patcher.RegisterPatch<ModCardPileCombatPilesContainerReadyPatch>();

0 commit comments

Comments
 (0)