Skip to content

Commit 02c0cb7

Browse files
committed
feat(CardTargeting): implement unified custom target type resolver
- Introduced CustomTargetTypeResolver to unify the handling of custom target types across RitsuLib and BaseLib. - Updated existing methods to utilize the new resolver, enhancing compatibility and reducing redundancy in target type checks. - Added BaseLibTargetTypeBridge to facilitate integration with BaseLib's custom target predicates, improving overall targeting logic.
1 parent 0db23f3 commit 02c0cb7

5 files changed

Lines changed: 210 additions & 17 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using System.Reflection;
2+
using MegaCrit.Sts2.Core.Entities.Cards;
3+
using MegaCrit.Sts2.Core.Entities.Creatures;
4+
using STS2RitsuLib.Compat;
5+
6+
namespace STS2RitsuLib.Combat.CardTargeting
7+
{
8+
/// <summary>
9+
/// Optional bridge for BaseLib custom target predicates.
10+
/// </summary>
11+
internal static class BaseLibTargetTypeBridge
12+
{
13+
private const string BaseLibCustomTargetTypeName = "BaseLib.Patches.Features.CustomTargetType";
14+
15+
private static readonly Lock Gate = new();
16+
17+
private static IReadOnlyDictionary<TargetType, Func<Creature, bool>>? _singleTargeting;
18+
private static IReadOnlyDictionary<TargetType, Func<Creature, bool>>? _multiTargeting;
19+
private static bool _loggedMissingType;
20+
private static bool _loggedMissingFields;
21+
22+
internal static bool IsCustomSingleTargetType(TargetType targetType)
23+
{
24+
return TryGetSingleTargeting(out var singleTargeting) && singleTargeting.ContainsKey(targetType);
25+
}
26+
27+
internal static bool IsCustomMultiTargetType(TargetType targetType)
28+
{
29+
return TryGetMultiTargeting(out var multiTargeting) && multiTargeting.ContainsKey(targetType);
30+
}
31+
32+
internal static bool TryIsAllowedSingleTarget(TargetType targetType, Creature creature, out bool allowed)
33+
{
34+
allowed = false;
35+
if (!TryGetSingleTargeting(out var singleTargeting) ||
36+
!singleTargeting.TryGetValue(targetType, out var predicate))
37+
return false;
38+
39+
try
40+
{
41+
allowed = predicate(creature);
42+
return true;
43+
}
44+
catch (Exception ex)
45+
{
46+
RitsuLibFramework.Logger.Warn(
47+
$"[CardTargeting] BaseLib single-target predicate failed for {(int)targetType}: {ex.Message}");
48+
return false;
49+
}
50+
}
51+
52+
internal static bool TryShouldIncludeMultiTarget(TargetType targetType, Creature creature, out bool include)
53+
{
54+
include = false;
55+
if (!TryGetMultiTargeting(out var multiTargeting) ||
56+
!multiTargeting.TryGetValue(targetType, out var predicate))
57+
return false;
58+
59+
try
60+
{
61+
include = predicate(creature);
62+
return true;
63+
}
64+
catch (Exception ex)
65+
{
66+
RitsuLibFramework.Logger.Warn(
67+
$"[CardTargeting] BaseLib multi-target predicate failed for {(int)targetType}: {ex.Message}");
68+
return false;
69+
}
70+
}
71+
72+
private static bool TryGetSingleTargeting(
73+
out IReadOnlyDictionary<TargetType, Func<Creature, bool>> singleTargeting)
74+
{
75+
EnsureResolved();
76+
singleTargeting = _singleTargeting!;
77+
return singleTargeting != null;
78+
}
79+
80+
private static bool TryGetMultiTargeting(
81+
out IReadOnlyDictionary<TargetType, Func<Creature, bool>> multiTargeting)
82+
{
83+
EnsureResolved();
84+
multiTargeting = _multiTargeting!;
85+
return multiTargeting != null;
86+
}
87+
88+
private static void EnsureResolved()
89+
{
90+
if (_singleTargeting != null && _multiTargeting != null)
91+
return;
92+
93+
lock (Gate)
94+
{
95+
if (_singleTargeting != null && _multiTargeting != null)
96+
return;
97+
98+
var type = ResolveBaseLibCustomTargetType();
99+
if (type == null)
100+
return;
101+
102+
_singleTargeting = ReadPredicateMap(type, "SingleTargeting");
103+
_multiTargeting = ReadPredicateMap(type, "MultiTargeting");
104+
105+
if (_singleTargeting != null && _multiTargeting != null)
106+
{
107+
RitsuLibFramework.Logger.Info("[CardTargeting] BaseLib custom TargetType bridge resolved.");
108+
return;
109+
}
110+
111+
if (_loggedMissingFields)
112+
return;
113+
_loggedMissingFields = true;
114+
RitsuLibFramework.Logger.Info(
115+
"[CardTargeting] BaseLib custom TargetType registry fields were not found.");
116+
}
117+
}
118+
119+
private static Type? ResolveBaseLibCustomTargetType()
120+
{
121+
var byQualifiedName = ExternalFrameworkRegistry.ResolveType(BaseLibCustomTargetTypeName);
122+
if (byQualifiedName != null)
123+
return byQualifiedName;
124+
125+
foreach (var mod in Sts2ModManagerCompat.EnumerateLoadedModsWithAssembly())
126+
{
127+
var assembly = mod.assembly;
128+
if (assembly == null)
129+
continue;
130+
131+
var type = assembly.GetType(BaseLibCustomTargetTypeName, false);
132+
if (type != null)
133+
return type;
134+
}
135+
136+
var fallback = AppDomain.CurrentDomain.GetAssemblies()
137+
.Select(assembly => assembly.GetType(BaseLibCustomTargetTypeName, false))
138+
.OfType<Type>()
139+
.FirstOrDefault();
140+
if (fallback != null)
141+
return fallback;
142+
143+
if (_loggedMissingType)
144+
return null;
145+
_loggedMissingType = true;
146+
RitsuLibFramework.Logger.Info("[CardTargeting] BaseLib custom TargetType type not found.");
147+
return null;
148+
}
149+
150+
private static IReadOnlyDictionary<TargetType, Func<Creature, bool>>? ReadPredicateMap(
151+
Type type,
152+
string fieldName)
153+
{
154+
var field = type.GetField(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
155+
return field?.GetValue(null) as IReadOnlyDictionary<TargetType, Func<Creature, bool>>;
156+
}
157+
}
158+
}

Combat/CardTargeting/CardModelTargetingExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ public static List<Creature> GetTargets(this CardModel card, Creature? selectedT
5959
return [card.Owner.Creature];
6060
default:
6161
{
62-
if (CustomTargetTypeRegistry.IsCustomSingleTargetType(card.TargetType))
62+
if (CustomTargetTypeResolver.IsCustomSingleTargetType(card.TargetType))
6363
{
6464
if (selectedTarget == null)
6565
return [];
66-
return CustomTargetTypeRegistry.TryIsAllowedSingleTarget(
66+
return CustomTargetTypeResolver.TryIsAllowedSingleTarget(
6767
card.TargetType,
6868
selectedTarget,
6969
out var allowed) &&
@@ -72,12 +72,12 @@ public static List<Creature> GetTargets(this CardModel card, Creature? selectedT
7272
: [];
7373
}
7474

75-
if (!CustomTargetTypeRegistry.IsCustomMultiTargetType(card.TargetType))
75+
if (!CustomTargetTypeResolver.IsCustomMultiTargetType(card.TargetType))
7676
return [];
7777

7878
return state?.Creatures
7979
.Where(c =>
80-
CustomTargetTypeRegistry.TryShouldIncludeMultiTarget(card.TargetType, c,
80+
CustomTargetTypeResolver.TryShouldIncludeMultiTarget(card.TargetType, c,
8181
out var include) && include)
8282
.ToList() ??
8383
[];

Combat/CardTargeting/CustomTargetType.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public static bool IsRitsuCustom(TargetType type)
104104
/// </summary>
105105
public static bool IsCustomSingleTargetType(TargetType type)
106106
{
107-
return CustomTargetTypeRegistry.IsCustomSingleTargetType(type);
107+
return CustomTargetTypeResolver.IsCustomSingleTargetType(type);
108108
}
109109

110110
/// <summary>
@@ -113,7 +113,7 @@ public static bool IsCustomSingleTargetType(TargetType type)
113113
/// </summary>
114114
public static bool IsCustomMultiTargetType(TargetType type)
115115
{
116-
return CustomTargetTypeRegistry.IsCustomMultiTargetType(type);
116+
return CustomTargetTypeResolver.IsCustomMultiTargetType(type);
117117
}
118118

119119
/// <summary>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using MegaCrit.Sts2.Core.Entities.Cards;
2+
using MegaCrit.Sts2.Core.Entities.Creatures;
3+
4+
namespace STS2RitsuLib.Combat.CardTargeting
5+
{
6+
/// <summary>
7+
/// Unified custom TargetType lookup across RitsuLib and compatible external registries.
8+
/// </summary>
9+
internal static class CustomTargetTypeResolver
10+
{
11+
internal static bool IsCustomSingleTargetType(TargetType type)
12+
{
13+
return CustomTargetTypeRegistry.IsCustomSingleTargetType(type)
14+
|| BaseLibTargetTypeBridge.IsCustomSingleTargetType(type);
15+
}
16+
17+
internal static bool IsCustomMultiTargetType(TargetType type)
18+
{
19+
return CustomTargetTypeRegistry.IsCustomMultiTargetType(type)
20+
|| BaseLibTargetTypeBridge.IsCustomMultiTargetType(type);
21+
}
22+
23+
internal static bool TryIsAllowedSingleTarget(TargetType type, Creature creature, out bool allowed)
24+
{
25+
return CustomTargetTypeRegistry.TryIsAllowedSingleTarget(type, creature, out allowed) ||
26+
BaseLibTargetTypeBridge.TryIsAllowedSingleTarget(type, creature, out allowed);
27+
}
28+
29+
internal static bool TryShouldIncludeMultiTarget(TargetType type, Creature creature, out bool include)
30+
{
31+
return CustomTargetTypeRegistry.TryShouldIncludeMultiTarget(type, creature, out include) ||
32+
BaseLibTargetTypeBridge.TryShouldIncludeMultiTarget(type, creature, out include);
33+
}
34+
}
35+
}

Combat/CardTargeting/Patches/CustomTargetTypeGeneralPatches.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static void Postfix(NCardPlay __instance)
4747
if (__instance.Card is not { TargetType: var targetType })
4848
return;
4949

50-
if (!CustomTargetTypeRegistry.IsCustomMultiTargetType(targetType))
50+
if (!CustomTargetTypeResolver.IsCustomMultiTargetType(targetType))
5151
return;
5252

5353
__instance.CardNode?.UpdateVisuals(
@@ -59,7 +59,7 @@ public static void Postfix(NCardPlay __instance)
5959
return;
6060

6161
foreach (var creatureNode in room.CreatureNodes)
62-
if (CustomTargetTypeRegistry.TryShouldIncludeMultiTarget(targetType, creatureNode.Entity,
62+
if (CustomTargetTypeResolver.TryShouldIncludeMultiTarget(targetType, creatureNode.Entity,
6363
out var include) &&
6464
include)
6565
creatureNode.ShowMultiselectReticle();
@@ -97,7 +97,7 @@ public static void Postfix(TargetType targetType, ref bool __result)
9797
if (__result)
9898
return;
9999

100-
if (CustomTargetTypeRegistry.IsCustomSingleTargetType(targetType))
100+
if (CustomTargetTypeResolver.IsCustomSingleTargetType(targetType))
101101
__result = true;
102102
}
103103
}
@@ -129,7 +129,7 @@ public static ModPatchTarget[] GetTargets()
129129
public static bool Prefix(NTargetManager __instance, Creature creature, ref bool __result)
130130
// ReSharper restore InconsistentNaming
131131
{
132-
if (!CustomTargetTypeRegistry.TryIsAllowedSingleTarget(__instance._validTargetsType, creature,
132+
if (!CustomTargetTypeResolver.TryIsAllowedSingleTarget(__instance._validTargetsType, creature,
133133
out var allowed))
134134
return true;
135135

@@ -168,7 +168,7 @@ public static bool Prefix(CardModel __instance, Creature? target, ref bool __res
168168
if (target == null)
169169
return true;
170170

171-
if (!CustomTargetTypeRegistry.TryIsAllowedSingleTarget(__instance.TargetType, target, out var allowed))
171+
if (!CustomTargetTypeResolver.TryIsAllowedSingleTarget(__instance.TargetType, target, out var allowed))
172172
return true;
173173

174174
__result = allowed;
@@ -206,7 +206,7 @@ public static bool Prefix(CardModel __instance, Creature? target, ref bool __res
206206
if (target == null)
207207
return true;
208208

209-
if (!CustomTargetTypeRegistry.TryIsAllowedSingleTarget(__instance.TargetType, target, out var allowed))
209+
if (!CustomTargetTypeResolver.TryIsAllowedSingleTarget(__instance.TargetType, target, out var allowed))
210210
return true;
211211

212212
__result = allowed;
@@ -259,7 +259,7 @@ public static bool Prefix(NMouseCardPlay __instance, TargetMode targetMode, ref
259259
// ReSharper restore InconsistentNaming
260260
{
261261
var card = GetCard(__instance);
262-
if (card == null || !CustomTargetTypeRegistry.IsCustomSingleTargetType(card.TargetType))
262+
if (card == null || !CustomTargetTypeResolver.IsCustomSingleTargetType(card.TargetType))
263263
return true;
264264

265265
__result = RunTargeting(__instance, targetMode, card.TargetType);
@@ -336,7 +336,7 @@ public static ModPatchTarget[] GetTargets()
336336
public static bool Prefix(NControllerCardPlay __instance)
337337
{
338338
var card = GetCard(__instance);
339-
if (card == null || !CustomTargetTypeRegistry.IsCustomSingleTargetType(card.TargetType))
339+
if (card == null || !CustomTargetTypeResolver.IsCustomSingleTargetType(card.TargetType))
340340
return true;
341341

342342
var cardNode = GetCardNode(__instance);
@@ -414,7 +414,7 @@ public static ModPatchTarget[] GetTargets()
414414
public static bool Prefix(NControllerCardPlay __instance, TargetType targetType, ref Task __result)
415415
// ReSharper restore InconsistentNaming
416416
{
417-
if (!CustomTargetTypeRegistry.IsCustomSingleTargetType(targetType))
417+
if (!CustomTargetTypeResolver.IsCustomSingleTargetType(targetType))
418418
return true;
419419

420420
__result = RunTargeting(__instance, targetType);
@@ -444,7 +444,7 @@ private static async Task RunTargeting(NControllerCardPlay instance, TargetType
444444

445445
var nodes = room.CreatureNodes
446446
.Where(n =>
447-
CustomTargetTypeRegistry.TryIsAllowedSingleTarget(targetType, n.Entity, out var allowed) &&
447+
CustomTargetTypeResolver.TryIsAllowedSingleTarget(targetType, n.Entity, out var allowed) &&
448448
allowed)
449449
.ToList();
450450

@@ -526,7 +526,7 @@ public static ModPatchTarget[] GetTargets()
526526
public static bool Prefix(NCardPlay __instance, Creature? target)
527527
{
528528
var card = __instance.Card;
529-
if (card == null || !CustomTargetTypeRegistry.IsCustomSingleTargetType(card.TargetType))
529+
if (card == null || !CustomTargetTypeResolver.IsCustomSingleTargetType(card.TargetType))
530530
return true;
531531

532532
if (target == null || __instance.Holder.CardModel == null)

0 commit comments

Comments
 (0)